diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index d762951b3..a55532008 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,14 +1,14 @@
-By submitting this pull request you agree that all contributions to this project are made under the MIT license.
+## Description
-## Issues
+
-
-
-## Solution
+## Checklist
-
+Please update this checklist as you complete each item:
-## Checklist
+- [ ] Tests have been developed for bug fixes or new functionality.
+- [ ] The changelog has been updated, if necessary.
+- [ ] Documentation has been updated, if necessary.
+- [ ] GitHub Issues closed by this PR have been linked.
-- [ ] Tests have been included for all bug fixes or added functionality.
-- [ ] The `changelog.rst` has been updated with any significant changes.
+By submitting this pull request I agree that all contributions comply with this project's open source license(s).
diff --git a/.github/workflows/.hatch-run.yml b/.github/workflows/.hatch-run.yml
index 1b21e4202..1630378b9 100644
--- a/.github/workflows/.hatch-run.yml
+++ b/.github/workflows/.hatch-run.yml
@@ -38,15 +38,15 @@ jobs:
runs-on: ${{ fromJson(inputs.runs-on-array) }}
runs-on: ${{ matrix.runs-on }}
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
with:
- node-version: "14.x"
+ node-version: "23.x"
registry-url: ${{ inputs.node-registry-url }}
- name: Pin NPM Version
run: npm install -g npm@8.19.3
- name: Use Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install Python Dependencies
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index 7337f505b..f9f9431c6 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -4,27 +4,27 @@
name: deploy-docs
on:
- push:
- branches:
- - "main"
- tags:
- - "*"
+ push:
+ branches:
+ - "main"
+ tags:
+ - "*"
jobs:
- deploy-documentation:
- runs-on: ubuntu-latest
- steps:
- - name: Check out src from Git
- uses: actions/checkout@v2
- - name: Get history and tags for SCM versioning to work
- run: |
- git fetch --prune --unshallow
- git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- - name: Login to Heroku Container Registry
- run: echo ${{ secrets.HEROKU_API_KEY }} | docker login -u ${{ secrets.HEROKU_EMAIL }} --password-stdin registry.heroku.com
- - name: Build Docker Image
- run: docker build . --file docs/Dockerfile --tag registry.heroku.com/${{ secrets.HEROKU_APP_NAME }}/web
- - name: Push Docker Image
- run: docker push registry.heroku.com/${{ secrets.HEROKU_APP_NAME }}/web
- - name: Deploy
- run: HEROKU_API_KEY=${{ secrets.HEROKU_API_KEY }} heroku container:release web --app ${{ secrets.HEROKU_APP_NAME }}
+ deploy-documentation:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out src from Git
+ uses: actions/checkout@v4
+ - name: Get history and tags for SCM versioning to work
+ run: |
+ git fetch --prune --unshallow
+ git fetch --depth=1 origin +refs/tags/*:refs/tags/*
+ - name: Login to Heroku Container Registry
+ run: echo ${{ secrets.HEROKU_API_KEY }} | docker login -u ${{ secrets.HEROKU_EMAIL }} --password-stdin registry.heroku.com
+ - name: Build Docker Image
+ run: docker build . --file docs/Dockerfile --tag registry.heroku.com/${{ secrets.HEROKU_APP_NAME }}/web
+ - name: Push Docker Image
+ run: docker push registry.heroku.com/${{ secrets.HEROKU_APP_NAME }}/web
+ - name: Deploy
+ run: HEROKU_API_KEY=${{ secrets.HEROKU_API_KEY }} heroku container:release web --app ${{ secrets.HEROKU_APP_NAME }}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index e9271cbd5..8e523ce04 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -4,17 +4,17 @@
name: publish
on:
- release:
- types: [published]
+ release:
+ types: [published]
jobs:
- publish:
- uses: ./.github/workflows/.hatch-run.yml
- with:
- job-name: "publish"
- hatch-run: "publish"
- node-registry-url: "https://registry.npmjs.org"
- secrets:
- node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
- pypi-username: ${{ secrets.PYPI_USERNAME }}
- pypi-password: ${{ secrets.PYPI_PASSWORD }}
+ publish:
+ uses: ./.github/workflows/.hatch-run.yml
+ with:
+ job-name: "publish"
+ hatch-run: "publish"
+ node-registry-url: "https://registry.npmjs.org"
+ secrets:
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
+ pypi-username: ${{ secrets.PYPI_USERNAME }}
+ pypi-password: ${{ secrets.PYPI_PASSWORD }}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 000000000..7471953dc
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,12 @@
+{
+ "recommendations": [
+ "wholroyd.jinja",
+ "esbenp.prettier-vscode",
+ "ms-python.vscode-pylance",
+ "ms-python.python",
+ "charliermarsh.ruff",
+ "dbaeumer.vscode-eslint",
+ "ms-python.black-formatter",
+ "ms-python.mypy-type-checker"
+ ]
+}
diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst
index feecbd1f0..325481f9a 100644
--- a/docs/source/about/changelog.rst
+++ b/docs/source/about/changelog.rst
@@ -3,15 +3,10 @@ Changelog
.. note::
- The ReactPy team manages their short and long term plans with `GitHub Projects
- `__. If you have questions about what
- the team are working on, or have feedback on how issues should be prioritized, feel
- free to :discussion-type:`open up a discussion `.
-
-All notable changes to this project will be recorded in this document. The style of
-which is based on `Keep a Changelog `__. The versioning
-scheme for the project adheres to `Semantic Versioning `__. For
-more info, see the :ref:`Contributor Guide `.
+ All notable changes to this project will be recorded in this document. The style of
+ which is based on `Keep a Changelog `__. The versioning
+ scheme for the project adheres to `Semantic Versioning `__. For
+ more info, see the :ref:`Contributor Guide `.
.. INSTRUCTIONS FOR CHANGELOG CONTRIBUTORS
@@ -25,15 +20,18 @@ Unreleased
**Fixed**
-- :pull:`1118` - `module_from_template` is broken with a recent release of `requests`
-- :pull:`1131` - `module_from_template` did not work when using Flask backend
+- :pull:`1118` - ``module_from_template`` is broken with a recent release of ``requests``
+- :pull:`1131` - ``module_from_template`` did not work when using Flask backend
+- :pull:`1200` - Fixed ``UnicodeDecodeError`` when using ``reactpy.web.export``
+- :pull:`1224` - Fixes needless unmounting of JavaScript components during each ReactPy render.
+- :pull:`1126` - Fixed missing ``event["target"]["checked"]`` on checkbox inputs
**Added**
-- :pull:`1165` - Allow concurrent renders of discrete component tree - enable this
- experimental feature by setting `REACTPY_ASYNC_RENDERING=true`. This should improve
- the overall responsiveness of your app, particularly when handling larger renders
- that would otherwise block faster renders from being processed.
+- :pull:`1165` - Allow concurrently rendering discrete component trees - enable this
+ experimental feature by setting ``REACTPY_ASYNC_RENDERING=true``. This improves
+ the overall responsiveness of your app in situations where larger renders would
+ otherwise block smaller renders from executing.
**Changed**
@@ -44,12 +42,15 @@ Unreleased
fragment to conditionally render an element by writing
``something if condition else html._()``. Now you can simply write
``something if condition else None``.
+- :pull:`1210` - Move hooks from ``reactpy.backend.hooks`` into ``reactpy.core.hooks``.
**Deprecated**
- :pull:`1171` - The ``Stop`` exception. Recent releases of ``anyio`` have made this
exception difficult to use since it now raises an ``ExceptionGroup``. This exception
was primarily used for internal testing purposes and so is now deprecated.
+- :pull:`1210` - Deprecate ``reactpy.backend.hooks`` since the hooks have been moved into
+ ``reactpy.core.hooks``.
v1.0.2
diff --git a/pyproject.toml b/pyproject.toml
index 775ab01a2..1745a3dfe 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,12 +12,11 @@ detached = true
dependencies = [
"invoke",
# lint
- "black==24.1.1", # Pin lint tools we don't control to avoid breaking changes
- "ruff==0.0.278", # Pin lint tools we don't control to avoid breaking changes
+ "black==24.1.1", # Pin lint tools we don't control to avoid breaking changes
+ "ruff==0.0.278", # Pin lint tools we don't control to avoid breaking changes
"toml",
- "flake8==7.0.0", # Pin lint tools we don't control to avoid breaking changes
+ "flake8==7.0.0", # Pin lint tools we don't control to avoid breaking changes
"flake8-pyproject",
- "reactpy-flake8 >=0.7",
# types
"mypy",
"types-toml",
diff --git a/src/js/app/package-lock.json b/src/js/app/package-lock.json
index 9794c53d6..5af5f0fd8 100644
--- a/src/js/app/package-lock.json
+++ b/src/js/app/package-lock.json
@@ -13,7 +13,7 @@
"@types/react": "^17.0",
"@types/react-dom": "^17.0",
"typescript": "^4.9.5",
- "vite": "^3.2.7"
+ "vite": "^3.2.11"
}
},
"node_modules/@esbuild/android-arm": {
@@ -540,9 +540,9 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
- "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
@@ -579,9 +579,9 @@
"dev": true
},
"node_modules/postcss": {
- "version": "8.4.21",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
- "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"dev": true,
"funding": [
{
@@ -591,10 +591,14 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
- "nanoid": "^3.3.4",
+ "nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@@ -715,9 +719,9 @@
}
},
"node_modules/vite": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
- "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
+ "version": "3.2.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz",
+ "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==",
"dev": true,
"dependencies": {
"esbuild": "^0.15.9",
@@ -1064,9 +1068,9 @@
}
},
"nanoid": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
- "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true
},
"object-assign": {
@@ -1088,12 +1092,12 @@
"dev": true
},
"postcss": {
- "version": "8.4.21",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
- "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"dev": true,
"requires": {
- "nanoid": "^3.3.4",
+ "nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
@@ -1173,9 +1177,9 @@
"dev": true
},
"vite": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
- "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
+ "version": "3.2.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz",
+ "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==",
"dev": true,
"requires": {
"esbuild": "^0.15.9",
diff --git a/src/js/app/package.json b/src/js/app/package.json
index 40ce94739..cecc15083 100644
--- a/src/js/app/package.json
+++ b/src/js/app/package.json
@@ -12,7 +12,7 @@
"@types/react": "^17.0",
"@types/react-dom": "^17.0",
"typescript": "^4.9.5",
- "vite": "^3.2.7"
+ "vite": "^3.2.11"
},
"repository": {
"type": "git",
diff --git a/src/js/package-lock.json b/src/js/package-lock.json
index 2edfdd260..83d0d5c08 100644
--- a/src/js/package-lock.json
+++ b/src/js/package-lock.json
@@ -28,7 +28,7 @@
"@types/react": "^17.0",
"@types/react-dom": "^17.0",
"typescript": "^4.9.5",
- "vite": "^3.1.8"
+ "vite": "^3.2.11"
}
},
"app/node_modules/@reactpy/client": {
@@ -664,12 +664,12 @@
}
},
"node_modules/braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -1581,9 +1581,9 @@
}
},
"node_modules/fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
@@ -2429,9 +2429,9 @@
"dev": true
},
"node_modules/nanoid": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
- "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true,
"funding": [
{
@@ -2712,9 +2712,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.24",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
- "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"dev": true,
"funding": [
{
@@ -2731,7 +2731,7 @@
}
],
"dependencies": {
- "nanoid": "^3.3.6",
+ "nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@@ -3328,9 +3328,9 @@
}
},
"node_modules/vite": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
- "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
+ "version": "3.2.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz",
+ "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==",
"dev": true,
"dependencies": {
"esbuild": "^0.15.9",
@@ -3474,9 +3474,9 @@
}
},
"node_modules/word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -3955,7 +3955,7 @@
"@types/react-dom": "^17.0",
"preact": "^10.7.0",
"typescript": "^4.9.5",
- "vite": "^3.1.8"
+ "vite": "^3.2.11"
},
"dependencies": {
"@reactpy/client": {
@@ -4058,12 +4058,12 @@
}
},
"braces": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
- "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"requires": {
- "fill-range": "^7.0.1"
+ "fill-range": "^7.1.1"
}
},
"call-bind": {
@@ -4676,9 +4676,9 @@
}
},
"fill-range": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
- "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
@@ -5285,9 +5285,9 @@
"dev": true
},
"nanoid": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
- "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"dev": true
},
"natural-compare": {
@@ -5476,12 +5476,12 @@
"dev": true
},
"postcss": {
- "version": "8.4.24",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
- "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"dev": true,
"requires": {
- "nanoid": "^3.3.6",
+ "nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
@@ -5888,9 +5888,9 @@
}
},
"vite": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
- "integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
+ "version": "3.2.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz",
+ "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==",
"dev": true,
"requires": {
"esbuild": "^0.15.9",
@@ -5976,9 +5976,9 @@
}
},
"word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"dev": true
},
"wrappy": {
diff --git a/src/js/packages/@reactpy/client/src/components.tsx b/src/js/packages/@reactpy/client/src/components.tsx
index 728c4cec7..2319f81c7 100644
--- a/src/js/packages/@reactpy/client/src/components.tsx
+++ b/src/js/packages/@reactpy/client/src/components.tsx
@@ -177,7 +177,7 @@ function useForceUpdate() {
function useImportSource(model: ReactPyVdom): MutableRefObject {
const vdomImportSource = model.importSource;
-
+ const vdomImportSourceJsonString = JSON.stringify(vdomImportSource);
const mountPoint = useRef(null);
const client = React.useContext(ClientContext);
const [binding, setBinding] = useState(null);
@@ -203,7 +203,7 @@ function useImportSource(model: ReactPyVdom): MutableRefObject {
binding.unmount();
}
};
- }, [client, vdomImportSource, setBinding, mountPoint.current]);
+ }, [client, vdomImportSourceJsonString, setBinding, mountPoint.current]);
// this effect must run every time in case the model has changed
useEffect(() => {
diff --git a/src/js/packages/event-to-object/src/index.ts b/src/js/packages/event-to-object/src/index.ts
index 9a40a2128..22fb7154d 100644
--- a/src/js/packages/event-to-object/src/index.ts
+++ b/src/js/packages/event-to-object/src/index.ts
@@ -303,7 +303,10 @@ const elementConverters: { [key: string]: (element: any) => any } = {
FORM: (element: HTMLFormElement) => ({
elements: Array.from(element.elements).map(convertElement),
}),
- INPUT: (element: HTMLInputElement) => ({ value: element.value }),
+ INPUT: (element: HTMLInputElement) => ({
+ value: element.value,
+ checked: element.checked,
+ }),
METER: (element: HTMLMeterElement) => ({ value: element.value }),
OPTION: (element: HTMLOptionElement) => ({ value: element.value }),
OUTPUT: (element: HTMLOutputElement) => ({ value: element.value }),
diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml
index 533616da8..f93ddb776 100644
--- a/src/py/reactpy/pyproject.toml
+++ b/src/py/reactpy/pyproject.toml
@@ -92,10 +92,9 @@ REACTPY_DEBUG_MODE = "1"
[tool.hatch.envs.lint]
features = ["all"]
dependencies = [
- "mypy>=1.0.0",
+ "mypy==1.8",
"types-click",
"types-tornado",
- "types-pkg-resources",
"types-flask",
"types-requests",
]
diff --git a/src/py/reactpy/reactpy/__init__.py b/src/py/reactpy/reactpy/__init__.py
index 49e357441..c47142cd8 100644
--- a/src/py/reactpy/reactpy/__init__.py
+++ b/src/py/reactpy/reactpy/__init__.py
@@ -1,5 +1,4 @@
from reactpy import backend, config, html, logging, sample, svg, types, web, widgets
-from reactpy.backend.hooks import use_connection, use_location, use_scope
from reactpy.backend.utils import run
from reactpy.core import hooks
from reactpy.core.component import component
@@ -7,12 +6,15 @@
from reactpy.core.hooks import (
create_context,
use_callback,
+ use_connection,
use_context,
use_debug_value,
use_effect,
+ use_location,
use_memo,
use_reducer,
use_ref,
+ use_scope,
use_state,
)
from reactpy.core.layout import Layout
diff --git a/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py
index e5d1860c2..d706adecf 100644
--- a/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py
+++ b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py
@@ -29,7 +29,7 @@ def rewrite_camel_case_props(paths: list[str]) -> None:
for p in map(Path, paths):
for f in [p] if p.is_file() else p.rglob("*.py"):
- result = generate_rewrite(file=f, source=f.read_text())
+ result = generate_rewrite(file=f, source=f.read_text(encoding="utf-8"))
if result is not None:
f.write_text(result)
diff --git a/src/py/reactpy/reactpy/_console/rewrite_keys.py b/src/py/reactpy/reactpy/_console/rewrite_keys.py
index 64ed42f33..08db9e227 100644
--- a/src/py/reactpy/reactpy/_console/rewrite_keys.py
+++ b/src/py/reactpy/reactpy/_console/rewrite_keys.py
@@ -51,7 +51,7 @@ def rewrite_keys(paths: list[str]) -> None:
for p in map(Path, paths):
for f in [p] if p.is_file() else p.rglob("*.py"):
- result = generate_rewrite(file=f, source=f.read_text())
+ result = generate_rewrite(file=f, source=f.read_text(encoding="utf-8"))
if result is not None:
f.write_text(result)
diff --git a/src/py/reactpy/reactpy/backend/flask.py b/src/py/reactpy/reactpy/backend/flask.py
index faa979aa9..4401fb6f7 100644
--- a/src/py/reactpy/reactpy/backend/flask.py
+++ b/src/py/reactpy/reactpy/backend/flask.py
@@ -35,9 +35,9 @@
safe_client_build_dir_path,
safe_web_modules_dir_path,
)
-from reactpy.backend.hooks import ConnectionContext
-from reactpy.backend.hooks import use_connection as _use_connection
from reactpy.backend.types import Connection, Location
+from reactpy.core.hooks import ConnectionContext
+from reactpy.core.hooks import use_connection as _use_connection
from reactpy.core.serve import serve_layout
from reactpy.core.types import ComponentType, RootComponentConstructor
from reactpy.utils import Ref
diff --git a/src/py/reactpy/reactpy/backend/hooks.py b/src/py/reactpy/reactpy/backend/hooks.py
index ee4ce1b5c..ef1b4a5cb 100644
--- a/src/py/reactpy/reactpy/backend/hooks.py
+++ b/src/py/reactpy/reactpy/backend/hooks.py
@@ -1,30 +1,45 @@
-from __future__ import annotations
+from __future__ import annotations # nocov
-from collections.abc import MutableMapping
-from typing import Any
+from collections.abc import MutableMapping # nocov
+from typing import Any # nocov
-from reactpy.backend.types import Connection, Location
-from reactpy.core.hooks import create_context, use_context
-from reactpy.core.types import Context
+from reactpy._warnings import warn # nocov
+from reactpy.backend.types import Connection, Location # nocov
+from reactpy.core.hooks import ConnectionContext, use_context # nocov
-# backend implementations should establish this context at the root of an app
-ConnectionContext: Context[Connection[Any] | None] = create_context(None)
-
-def use_connection() -> Connection[Any]:
+def use_connection() -> Connection[Any]: # nocov
"""Get the current :class:`~reactpy.backend.types.Connection`."""
+ warn(
+ "The module reactpy.backend.hooks has been deprecated and will be deleted in the future. ",
+ "Call reactpy.use_connection instead.",
+ DeprecationWarning,
+ )
+
conn = use_context(ConnectionContext)
- if conn is None: # nocov
+ if conn is None:
msg = "No backend established a connection."
raise RuntimeError(msg)
return conn
-def use_scope() -> MutableMapping[str, Any]:
+def use_scope() -> MutableMapping[str, Any]: # nocov
"""Get the current :class:`~reactpy.backend.types.Connection`'s scope."""
+ warn(
+ "The module reactpy.backend.hooks has been deprecated and will be deleted in the future. ",
+ "Call reactpy.use_scope instead.",
+ DeprecationWarning,
+ )
+
return use_connection().scope
-def use_location() -> Location:
+def use_location() -> Location: # nocov
"""Get the current :class:`~reactpy.backend.types.Connection`'s location."""
+ warn(
+ "The module reactpy.backend.hooks has been deprecated and will be deleted in the future. ",
+ "Call reactpy.use_location instead.",
+ DeprecationWarning,
+ )
+
return use_connection().location
diff --git a/src/py/reactpy/reactpy/backend/sanic.py b/src/py/reactpy/reactpy/backend/sanic.py
index 76eb0423e..d272fb4cf 100644
--- a/src/py/reactpy/reactpy/backend/sanic.py
+++ b/src/py/reactpy/reactpy/backend/sanic.py
@@ -24,9 +24,9 @@
safe_web_modules_dir_path,
serve_with_uvicorn,
)
-from reactpy.backend.hooks import ConnectionContext
-from reactpy.backend.hooks import use_connection as _use_connection
from reactpy.backend.types import Connection, Location
+from reactpy.core.hooks import ConnectionContext
+from reactpy.core.hooks import use_connection as _use_connection
from reactpy.core.layout import Layout
from reactpy.core.serve import RecvCoroutine, SendCoroutine, Stop, serve_layout
from reactpy.core.types import RootComponentConstructor
diff --git a/src/py/reactpy/reactpy/backend/starlette.py b/src/py/reactpy/reactpy/backend/starlette.py
index 9bc68db47..20e2b4478 100644
--- a/src/py/reactpy/reactpy/backend/starlette.py
+++ b/src/py/reactpy/reactpy/backend/starlette.py
@@ -24,10 +24,10 @@
read_client_index_html,
serve_with_uvicorn,
)
-from reactpy.backend.hooks import ConnectionContext
-from reactpy.backend.hooks import use_connection as _use_connection
from reactpy.backend.types import Connection, Location
from reactpy.config import REACTPY_WEB_MODULES_DIR
+from reactpy.core.hooks import ConnectionContext
+from reactpy.core.hooks import use_connection as _use_connection
from reactpy.core.layout import Layout
from reactpy.core.serve import RecvCoroutine, SendCoroutine, serve_layout
from reactpy.core.types import RootComponentConstructor
diff --git a/src/py/reactpy/reactpy/backend/tornado.py b/src/py/reactpy/reactpy/backend/tornado.py
index 8f540ddb4..bd339c5b9 100644
--- a/src/py/reactpy/reactpy/backend/tornado.py
+++ b/src/py/reactpy/reactpy/backend/tornado.py
@@ -24,10 +24,10 @@
CommonOptions,
read_client_index_html,
)
-from reactpy.backend.hooks import ConnectionContext
-from reactpy.backend.hooks import use_connection as _use_connection
from reactpy.backend.types import Connection, Location
from reactpy.config import REACTPY_WEB_MODULES_DIR
+from reactpy.core.hooks import ConnectionContext
+from reactpy.core.hooks import use_connection as _use_connection
from reactpy.core.layout import Layout
from reactpy.core.serve import serve_layout
from reactpy.core.types import ComponentConstructor
diff --git a/src/py/reactpy/reactpy/config.py b/src/py/reactpy/reactpy/config.py
index 8ea6aed03..d08cdc218 100644
--- a/src/py/reactpy/reactpy/config.py
+++ b/src/py/reactpy/reactpy/config.py
@@ -82,9 +82,9 @@ def boolean(value: str | bool | int) -> bool:
"""A default timeout for testing utilities in ReactPy"""
REACTPY_ASYNC_RENDERING = Option(
- "REACTPY_CONCURRENT_RENDERING",
+ "REACTPY_ASYNC_RENDERING",
default=False,
mutable=True,
validator=boolean,
)
-"""Whether to render components concurrently. This is currently an experimental feature."""
+"""Whether to render components asynchronously. This is currently an experimental feature."""
diff --git a/src/py/reactpy/reactpy/core/events.py b/src/py/reactpy/reactpy/core/events.py
index f715b7e9d..2a193ec6b 100644
--- a/src/py/reactpy/reactpy/core/events.py
+++ b/src/py/reactpy/reactpy/core/events.py
@@ -109,7 +109,7 @@ def __init__(
self.stop_propagation = stop_propagation
self.target = target
- def __eq__(self, other: Any) -> bool:
+ def __eq__(self, other: object) -> bool:
undefined = object()
for attr in (
"function",
diff --git a/src/py/reactpy/reactpy/core/hooks.py b/src/py/reactpy/reactpy/core/hooks.py
index 640cbf14c..0ece8cccf 100644
--- a/src/py/reactpy/reactpy/core/hooks.py
+++ b/src/py/reactpy/reactpy/core/hooks.py
@@ -1,7 +1,7 @@
from __future__ import annotations
import asyncio
-from collections.abc import Coroutine, Sequence
+from collections.abc import Coroutine, MutableMapping, Sequence
from logging import getLogger
from types import FunctionType
from typing import (
@@ -17,6 +17,7 @@
from typing_extensions import TypeAlias
+from reactpy.backend.types import Connection, Location
from reactpy.config import REACTPY_DEBUG_MODE
from reactpy.core._life_cycle_hook import current_hook
from reactpy.core.types import Context, Key, State, VdomDict
@@ -248,6 +249,29 @@ def use_context(context: Context[_Type]) -> _Type:
return provider.value
+# backend implementations should establish this context at the root of an app
+ConnectionContext: Context[Connection[Any] | None] = create_context(None)
+
+
+def use_connection() -> Connection[Any]:
+ """Get the current :class:`~reactpy.backend.types.Connection`."""
+ conn = use_context(ConnectionContext)
+ if conn is None: # nocov
+ msg = "No backend established a connection."
+ raise RuntimeError(msg)
+ return conn
+
+
+def use_scope() -> MutableMapping[str, Any]:
+ """Get the current :class:`~reactpy.backend.types.Connection`'s scope."""
+ return use_connection().scope
+
+
+def use_location() -> Location:
+ """Get the current :class:`~reactpy.backend.types.Connection`'s location."""
+ return use_connection().location
+
+
class _ContextProvider(Generic[_Type]):
def __init__(
self,
diff --git a/src/py/reactpy/reactpy/core/layout.py b/src/py/reactpy/reactpy/core/layout.py
index 70bdbbbff..88cb2fa35 100644
--- a/src/py/reactpy/reactpy/core/layout.py
+++ b/src/py/reactpy/reactpy/core/layout.py
@@ -89,7 +89,7 @@ async def __aenter__(self) -> Layout:
return self
- async def __aexit__(self, *exc: Any) -> None:
+ async def __aexit__(self, *exc: object) -> None:
root_csid = self._root_life_cycle_state_id
root_model_state = self._model_states_by_life_cycle_state_id[root_csid]
@@ -129,7 +129,7 @@ async def deliver(self, event: LayoutEventMessage) -> None:
async def render(self) -> LayoutUpdateMessage:
if REACTPY_ASYNC_RENDERING.current:
- return await self._concurrent_render()
+ return await self._parallel_render()
else: # nocov
return await self._serial_render()
@@ -147,8 +147,10 @@ async def _serial_render(self) -> LayoutUpdateMessage: # nocov
else:
return await self._create_layout_update(model_state)
- async def _concurrent_render(self) -> LayoutUpdateMessage:
- """Await the next available render. This will block until a component is updated"""
+ async def _parallel_render(self) -> LayoutUpdateMessage:
+ """Await to fetch the first completed render within our asyncio task group.
+ We use the `asyncio.tasks.wait` API in order to return the first completed task.
+ """
await self._render_tasks_ready.acquire()
done, _ = await wait(self._render_tasks, return_when=FIRST_COMPLETED)
update_task: Task[LayoutUpdateMessage] = done.pop()
diff --git a/src/py/reactpy/reactpy/utils.py b/src/py/reactpy/reactpy/utils.py
index 5624846a4..a20194902 100644
--- a/src/py/reactpy/reactpy/utils.py
+++ b/src/py/reactpy/reactpy/utils.py
@@ -43,7 +43,7 @@ def set_current(self, new: _RefValue) -> _RefValue:
self.current = new
return old
- def __eq__(self, other: Any) -> bool:
+ def __eq__(self, other: object) -> bool:
try:
return isinstance(other, Ref) and (other.current == self.current)
except AttributeError:
diff --git a/src/py/reactpy/reactpy/web/module.py b/src/py/reactpy/reactpy/web/module.py
index c3192da4e..e1a5db82f 100644
--- a/src/py/reactpy/reactpy/web/module.py
+++ b/src/py/reactpy/reactpy/web/module.py
@@ -145,7 +145,7 @@ def module_from_template(
raise ValueError(msg)
variables = {"PACKAGE": package, "CDN": cdn, "VERSION": template_version}
- content = Template(template_file.read_text()).substitute(variables)
+ content = Template(template_file.read_text(encoding="utf-8")).substitute(variables)
return module_from_string(
_FROM_TEMPLATE_DIR + "/" + package_name,
@@ -270,7 +270,7 @@ def module_from_string(
target_file = _web_module_path(name)
- if target_file.exists() and target_file.read_text() != content:
+ if target_file.exists() and target_file.read_text(encoding="utf-8") != content:
logger.info(
f"Existing web module {name!r} will "
f"be replaced with {target_file.resolve()}"
diff --git a/src/py/reactpy/reactpy/web/templates/react.js b/src/py/reactpy/reactpy/web/templates/react.js
index 5c6a45743..366be4fd0 100644
--- a/src/py/reactpy/reactpy/web/templates/react.js
+++ b/src/py/reactpy/reactpy/web/templates/react.js
@@ -17,11 +17,12 @@ export default ({ children, ...props }) => {
};
export function bind(node, config) {
+ const root = ReactDOM.createRoot(node);
return {
create: (component, props, children) =>
React.createElement(component, wrapEventHandlers(props), ...children),
- render: (element) => ReactDOM.render(element, node),
- unmount: () => ReactDOM.unmountComponentAtNode(node),
+ render: (element) => root.render(element),
+ unmount: () => root.unmount()
};
}
diff --git a/src/py/reactpy/reactpy/web/utils.py b/src/py/reactpy/reactpy/web/utils.py
index 295559496..338fa504a 100644
--- a/src/py/reactpy/reactpy/web/utils.py
+++ b/src/py/reactpy/reactpy/web/utils.py
@@ -29,7 +29,7 @@ def resolve_module_exports_from_file(
return set()
export_names, references = resolve_module_exports_from_source(
- file.read_text(), exclude_default=is_re_export
+ file.read_text(encoding="utf-8"), exclude_default=is_re_export
)
for ref in references:
diff --git a/src/py/reactpy/tests/test_backend/test_all.py b/src/py/reactpy/tests/test_backend/test_all.py
index dc8ec1284..cd2f371f5 100644
--- a/src/py/reactpy/tests/test_backend/test_all.py
+++ b/src/py/reactpy/tests/test_backend/test_all.py
@@ -112,7 +112,7 @@ async def test_use_location(display: DisplayFixture):
@poll
async def poll_location():
"""This needs to be async to allow the server to respond"""
- return location.current
+ return getattr(location, "current", None)
@reactpy.component
def ShowRoute():
diff --git a/src/py/reactpy/tests/test_core/test_events.py b/src/py/reactpy/tests/test_core/test_events.py
index 237c9d4ed..b6fea346a 100644
--- a/src/py/reactpy/tests/test_core/test_events.py
+++ b/src/py/reactpy/tests/test_core/test_events.py
@@ -193,7 +193,7 @@ def inner_click_no_op(event):
clicked.current = True
def outer_click_is_not_triggered(event):
- raise AssertionError()
+ raise AssertionError
outer = reactpy.html.div(
{
diff --git a/src/py/reactpy/tests/test_core/test_layout.py b/src/py/reactpy/tests/test_core/test_layout.py
index cfb544758..f93ffeb3d 100644
--- a/src/py/reactpy/tests/test_core/test_layout.py
+++ b/src/py/reactpy/tests/test_core/test_layout.py
@@ -32,7 +32,7 @@
@pytest.fixture(autouse=True, params=[True, False])
-def concurrent_rendering(request):
+def async_rendering(request):
with patch.object(REACTPY_ASYNC_RENDERING, "current", request.param):
yield request.param
@@ -1252,9 +1252,9 @@ def App():
assert c["attributes"]["color"] == "blue"
-async def test_concurrent_renders(concurrent_rendering):
- if not concurrent_rendering:
- raise pytest.skip("Concurrent rendering not enabled")
+async def test_async_renders(async_rendering):
+ if not async_rendering:
+ raise pytest.skip("Async rendering not enabled")
child_1_hook = HookCatcher()
child_2_hook = HookCatcher()