Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
64c9b63
Add `use_async_effect` shielding option
Archmonger Feb 17, 2026
95b823d
Fix edge case were `strictly_equal` could throw an exception
Archmonger Feb 17, 2026
726cd52
Add max queue size setting
Archmonger Feb 17, 2026
5171a1a
Move `GITHUB_ACTIONS` out of testing module
Archmonger Feb 17, 2026
fabfb88
Make pyscript utils more extensible
Archmonger Feb 17, 2026
15b6afb
Add missing changelog entry
Archmonger Feb 17, 2026
a13d21a
fix CI errors
Archmonger Feb 17, 2026
8c68d4a
Pyscript now supports ContextVars!
Archmonger Feb 17, 2026
b5be02e
Revert "Move `GITHUB_ACTIONS` out of testing module"
Archmonger Feb 18, 2026
247e329
Always append URL and QS to base websocket
Archmonger Feb 18, 2026
2040b45
bump version number
Archmonger Feb 18, 2026
e4dd8f6
fix test failures
Archmonger Feb 18, 2026
18791f9
Add changelog
Archmonger Feb 18, 2026
52be92a
Bump client version
Archmonger Feb 18, 2026
773bf7e
self review
Archmonger Feb 18, 2026
abbb27f
debounce user inputs to prevent character loss
Archmonger Feb 18, 2026
72e1974
Configurable debounce
Archmonger Apr 14, 2026
77bcc0b
fix formatting and type hint
Archmonger Apr 14, 2026
5148a1c
Fix flakey tests
Archmonger Apr 14, 2026
8b0c95d
expose max_queue_size within ReactPyConfig
Archmonger Apr 14, 2026
69b665f
queue backpressure
Archmonger Apr 14, 2026
db914fd
clean up http path/qs handling
Archmonger Apr 14, 2026
cc431df
Add test to ensure client input value takes priority.
Archmonger Apr 14, 2026
54526b9
More robust async event shielding
Archmonger Apr 14, 2026
8640a69
self review
Archmonger Apr 14, 2026
02a2ab7
Add coverage test
Archmonger Apr 14, 2026
dcd5ff9
Improve pyscript wheel generation
Archmonger Apr 17, 2026
a5f8d9c
Allow reactpy install string override in pyscript
Archmonger Apr 17, 2026
2d0c98f
self review 2: Add client-server reconciliation, clean-up wheel script
Archmonger Apr 18, 2026
99b3620
Tooling and dependencies
Archmonger Apr 18, 2026
ba8d321
fix CI
Archmonger Apr 18, 2026
c05b1e8
fix missing static files on `git+` installs
Archmonger Apr 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "Publish to PyPI"
run-cmd: "hatch run javascript:build && hatch build --clean && hatch publish --yes"
run-cmd: "hatch build --clean && hatch publish --yes"
secrets:
pypi-username: ${{ secrets.PYPI_USERNAME }}
pypi-password: ${{ secrets.PYPI_PASSWORD }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
src/reactpy/static/*.js*
src/reactpy/static/morphdom/
src/reactpy/static/pyscript/
src/reactpy/static/wheels/
src/js/**/*.tgz
src/js/**/LICENSE

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Don't forget to remove deprecated code on each major release!
- Added `reactpy.reactjs.component_from_string` to import ReactJS components from a string.
- Added `reactpy.reactjs.component_from_npm` to import ReactJS components from NPM.
- Added `reactpy.h` as a shorthand alias for `reactpy.html`.
- Added `reactpy.config.REACTPY_MAX_QUEUE_SIZE` to configure the maximum size of all ReactPy asyncio queues (e.g. receive buffer, send buffer, event buffer) before ReactPy begins waiting until a slot frees up. This can be used to constraint memory usage.

### Changed

Expand All @@ -61,6 +62,7 @@ Don't forget to remove deprecated code on each major release!
- `reactpy.types.VdomDictConstructor` has been renamed to `reactpy.types.VdomConstructor`.
- `REACTPY_ASYNC_RENDERING` can now de-duplicate and cascade renders where necessary.
- `REACTPY_ASYNC_RENDERING` is now defaulted to `True` for up to 40x performance improvements in environments with high concurrency.
- Events now support debounce, which can now be configured per event with `event.debounce = <milliseconds>`. Note that `input`, `select`, and `textarea` elements default to 200ms debounce.

### Deprecated

Expand All @@ -85,6 +87,7 @@ Don't forget to remove deprecated code on each major release!
- Removed `reactpy.run`. See the documentation for the new method to run ReactPy applications.
- Removed `reactpy.backend.*`. See the documentation for the new method to run ReactPy applications.
- Removed `reactpy.core.types` module. Use `reactpy.types` instead.
- Removed `reactpy.utils.str_to_bool`.
- Removed `reactpy.utils.html_to_vdom`. Use `reactpy.utils.string_to_reactpy` instead.
- Removed `reactpy.utils.vdom_to_html`. Use `reactpy.utils.reactpy_to_string` instead.
- Removed `reactpy.vdom`. Use `reactpy.Vdom` instead.
Expand All @@ -101,6 +104,7 @@ Don't forget to remove deprecated code on each major release!
- Fixed a bug where script elements would not render to the DOM as plain text.
- Fixed a bug where the `key` property provided within server-side ReactPy code was failing to propagate to the front-end JavaScript components.
- Fixed a bug where `RuntimeError("Hook stack is in an invalid state")` errors could be generated when using a webserver that reuses threads.
- Fixed a bug where events on controlled inputs (e.g. `html.input({"onChange": ...})`) could be lost during rapid actions.
- Allow for ReactPy and ReactJS components to be arbitrarily inserted onto the page with any possible hierarchy.

## [1.1.0] - 2024-11-24
Expand Down
56 changes: 28 additions & 28 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,24 +71,24 @@ installer = "uv"
reactpy = "reactpy._console.cli:entry_point"

[[tool.hatch.build.hooks.build-scripts.scripts]]
commands = []
artifacts = []
commands = ['python "src/build_scripts/build_py_wheel.py"']
artifacts = ["src/reactpy/static/wheels/*.whl"]

#############################
# >>> Hatch Test Runner <<< #
#############################
[tool.hatch.envs.hatch-test.scripts]
run = [
'hatch --env default run "src/build_scripts/install_playwright.py"',
'hatch --env default run "{root}/src/build_scripts/install_playwright.py"',
"hatch --env default run javascript:build --dev",
"hatch --env default build -t wheel",
"pytest{env:HATCH_TEST_ARGS:} {args} --max-worker-restart 10",
]
run-cov = [
'hatch --env default run "src/build_scripts/install_playwright.py"',
'hatch --env default run "{root}/src/build_scripts/install_playwright.py"',
"hatch --env default run javascript:build --dev",
"hatch --env default build -t wheel",
'hatch --env default run "src/build_scripts/delete_old_coverage.py"',
'hatch --env default run "{root}/src/build_scripts/delete_old_coverage.py"',
"coverage run -m pytest{env:HATCH_TEST_ARGS:} {args} --max-worker-restart 10",
]
cov-combine = "coverage combine"
Expand Down Expand Up @@ -136,25 +136,25 @@ detached = true

[tool.hatch.envs.docs.scripts]
build = [
"cd docs && poetry install",
"cd docs && poetry run sphinx-build -a -T -W --keep-going -b doctest source build",
'cd "{root}/docs" && poetry install',
'cd "{root}/docs" && poetry run sphinx-build -a -T -W --keep-going -b doctest "{root}/docs/source" "{root}/docs/build"',
]
docker_build = [
"hatch run docs:build",
"docker build . --file ./docs/Dockerfile --tag reactpy-docs:latest",
'docker build --file "{root}/docs/Dockerfile" --tag reactpy-docs:latest "{root}"',
]
docker_serve = [
"hatch run docs:docker_build",
"docker run --rm -p 5000:5000 reactpy-docs:latest",
]
check = [
"cd docs && poetry install",
"cd docs && poetry run sphinx-build -a -T -W --keep-going -b doctest source build",
"docker build . --file ./docs/Dockerfile",
'cd "{root}/docs" && poetry install',
'cd "{root}/docs" && poetry run sphinx-build -a -T -W --keep-going -b doctest "{root}/docs/source" "{root}/docs/build"',
'docker build --file "{root}/docs/Dockerfile" "{root}"',
]
serve = [
"cd docs && poetry install",
"cd docs && poetry run python main.py --watch=../src/ --ignore=**/_auto/* --ignore=**/custom.js --ignore=**/node_modules/* --ignore=**/package-lock.json -a -E -b html source build",
'cd "{root}/docs" && poetry install',
'cd "{root}/docs" && poetry run python "{root}/docs/main.py" --watch="{root}/src" --ignore=**/_auto/* --ignore=**/custom.js --ignore=**/node_modules/* --ignore=**/package-lock.json -a -E -b html "{root}/docs/source" "{root}/docs/build"',
]

################################
Expand All @@ -174,7 +174,7 @@ extra-dependencies = [
]

[tool.hatch.envs.python.scripts]
type_check = ["pyright src/reactpy"]
type_check = ['pyright "{root}/src/reactpy"']

############################
# >>> Hatch JS Scripts <<< #
Expand All @@ -186,39 +186,39 @@ detached = true
[tool.hatch.envs.javascript.scripts]
check = [
'hatch run javascript:build',
'bun run --cwd "src/js" lint',
'bun run --cwd "src/js/packages/event-to-object" checkTypes',
'bun run --cwd "src/js/packages/@reactpy/client" checkTypes',
'bun run --cwd "src/js/packages/@reactpy/app" checkTypes',
'bun run --cwd "{root}/src/js" lint',
'bun run --cwd "{root}/src/js/packages/event-to-object" checkTypes',
'bun run --cwd "{root}/src/js/packages/@reactpy/client" checkTypes',
'bun run --cwd "{root}/src/js/packages/@reactpy/app" checkTypes',
]
fix = ['bun install --cwd "src/js"', 'bun run --cwd "src/js" format']
fix = ['bun install --cwd "{root}/src/js"', 'bun run --cwd "{root}/src/js" format']
test = ['hatch run javascript:build_event_to_object --dev', 'bun test']
build = [
'hatch run "src/build_scripts/clean_js_dir.py"',
'bun install --cwd "src/js"',
'hatch run "{root}/src/build_scripts/clean_js_dir.py"',
'bun install --cwd "{root}/src/js"',
'hatch run javascript:build_event_to_object {args}',
'hatch run javascript:build_client {args}',
'hatch run javascript:build_app {args}',
'hatch --env default run "src/build_scripts/copy_dir.py" "src/js/node_modules/@pyscript/core/dist" "src/reactpy/static/pyscript"',
'hatch --env default run "src/build_scripts/copy_dir.py" "src/js/node_modules/morphdom/dist" "src/reactpy/static/morphdom"',
'hatch --env default run "{root}/src/build_scripts/copy_dir.py" "{root}/src/js/node_modules/@pyscript/core/dist" "{root}/src/reactpy/static/pyscript"',
'hatch --env default run "{root}/src/build_scripts/copy_dir.py" "{root}/src/js/node_modules/morphdom/dist" "{root}/src/reactpy/static/morphdom"',

]
build_event_to_object = [
'hatch run "src/build_scripts/build_js_event_to_object.py" {args}',
'hatch run "{root}/src/build_scripts/build_js_event_to_object.py" {args}',
]
build_client = ['hatch run "src/build_scripts/build_js_client.py" {args}']
build_app = ['hatch run "src/build_scripts/build_js_app.py" {args}']
build_client = ['hatch run "{root}/src/build_scripts/build_js_client.py" {args}']
build_app = ['hatch run "{root}/src/build_scripts/build_js_app.py" {args}']
publish_event_to_object = [
'hatch run javascript:build_event_to_object',
# FIXME: This is a temporary workaround. We are using `bun pm pack`->`npm publish` to fix missing "Trusted Publishing" support in `bun publish`
# See the following ticket https://github.com/oven-sh/bun/issues/15601
'cd "src/js/packages/event-to-object" && bun pm pack --filename "packages/event-to-object/dist.tgz" && bunx npm@11.8.0 publish dist.tgz --provenance --access public',
'cd "{root}/src/js/packages/event-to-object" && bun pm pack --filename "{root}/src/js/packages/event-to-object/dist.tgz" && bunx npm@11.8.0 publish "{root}/src/js/packages/event-to-object/dist.tgz" --provenance --access public',
]
publish_client = [
'hatch run javascript:build_client',
# FIXME: This is a temporary workaround. We are using `bun pm pack`->`npm publish` to fix missing "Trusted Publishing" support in `bun publish`
# See the following ticket https://github.com/oven-sh/bun/issues/15601
'cd "src/js/packages/@reactpy/client" && bun pm pack --filename "packages/@reactpy/client/dist.tgz" && bunx npm@11.8.0 publish dist.tgz --provenance --access public',
'cd "{root}/src/js/packages/@reactpy/client" && bun pm pack --filename "{root}/src/js/packages/@reactpy/client/dist.tgz" && bunx npm@11.8.0 publish "{root}/src/js/packages/@reactpy/client/dist.tgz" --provenance --access public',
]

#########################
Expand Down
150 changes: 150 additions & 0 deletions src/build_scripts/build_py_wheel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///

from __future__ import annotations

import importlib.util
import logging
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path

_logger = logging.getLogger(__name__)
_SKIP_ENV_VAR = "REACTPY_SKIP_PY_WHEEL_BUILD"


def _reactpy_version(root_dir: Path) -> str:
init_file = root_dir / "src" / "reactpy" / "__init__.py"
if match := re.search(
r'^__version__ = "([^"]+)"$',
init_file.read_text(encoding="utf-8"),
re.MULTILINE,
):
return match[1]
raise RuntimeError("Could not determine the current ReactPy version.")


def _matching_reactpy_wheel(dist_dir: Path, version: str) -> Path | None:
matching_wheels = sorted(
dist_dir.glob(f"reactpy-{version}-*.whl"),
key=lambda path: path.stat().st_mtime,
reverse=True,
)
return matching_wheels[0] if matching_wheels else None


def _hatch_command(root_dir: Path, *args: str) -> list[str] | None:
for candidate in (
root_dir / ".venv" / "Scripts" / "hatch.exe",
root_dir / ".venv" / "bin" / "hatch",
):
if candidate.exists():
return [str(candidate), *args]

if hatch_command := shutil.which("hatch"):
return [hatch_command, *args]

if importlib.util.find_spec("hatch") is not None:
return [sys.executable, "-m", "hatch", *args]

return None


def _hatch_build_command(root_dir: Path) -> list[str] | None:
return _hatch_command(root_dir, "build", "-t", "wheel")


def _without_hatch_env_vars(env: dict[str, str]) -> dict[str, str]:
cleaned_env = env.copy()
for key in tuple(cleaned_env):
if key.startswith("HATCH_ENV_"):
cleaned_env.pop(key)
return cleaned_env


def _run_hatch_command(root_dir: Path, command: list[str], failure_message: str) -> int:
result = subprocess.run( # noqa: S603
command,
capture_output=True,
text=True,
check=False,
cwd=root_dir,
env=_without_hatch_env_vars(os.environ.copy()),
)

if result.returncode != 0:
_logger.error(
"%s\nstdout:\n%s\nstderr:\n%s",
failure_message,
result.stdout,
result.stderr,
)
return result.returncode

return 0


def _build_packaged_static_assets(root_dir: Path) -> int:
hatch_command = _hatch_command(root_dir, "run", "javascript:build")
if not hatch_command:
_logger.error("Could not locate Hatch while building packaged static assets.")
return 1

return _run_hatch_command(
root_dir,
hatch_command,
"Failed to build packaged static assets.",
)


def main() -> int:
if os.environ.get(_SKIP_ENV_VAR):
print("Skipping local ReactPy wheel build.") # noqa: T201
return 0

root_dir = Path(__file__).parent.parent.parent

if static_assets_result := _build_packaged_static_assets(root_dir):
return static_assets_result

version = _reactpy_version(root_dir)
static_wheels_dir = root_dir / "src" / "reactpy" / "static" / "wheels"
dist_dir = root_dir / "dist"
hatch_build_command = _hatch_build_command(root_dir)

if not hatch_build_command:
_logger.error("Could not locate Hatch while building the embedded wheel.")
return 1

static_wheels_dir.mkdir(parents=True, exist_ok=True)
for wheel_file in static_wheels_dir.glob("reactpy-*.whl"):
wheel_file.unlink()

os.environ[_SKIP_ENV_VAR] = "1"
try:
if wheel_build_result := _run_hatch_command(
root_dir,
hatch_build_command,
"Failed to build the embedded ReactPy wheel.",
):
return wheel_build_result
finally:
os.environ.pop(_SKIP_ENV_VAR, None)

built_wheel = _matching_reactpy_wheel(dist_dir, version)
if not built_wheel:
_logger.error("Failed to locate the newly built ReactPy wheel in %s", dist_dir)
return 1

shutil.copy2(built_wheel, static_wheels_dir / built_wheel.name)
print(f"Embedded local ReactPy wheel at '{static_wheels_dir / built_wheel.name}'") # noqa: T201
return 0


if __name__ == "__main__":
raise SystemExit(main())
Binary file modified src/js/bun.lockb
Binary file not shown.
12 changes: 6 additions & 6 deletions src/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
"event-to-object": "2.0.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"bun-types": "^1.3.3",
"eslint": "^9.39.1",
"globals": "^16.5.0",
"prettier": "^3.6.2",
"typescript-eslint": "^8.47.0"
"@eslint/js": "^10.0.1",
"bun-types": "^1.3.12",
"eslint": "^10.2.1",
"globals": "^17.5.0",
"prettier": "^3.8.3",
"typescript-eslint": "^8.58.2"
},
"license": "MIT",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion src/js/packages/@reactpy/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@
"checkTypes": "tsc --noEmit"
},
"type": "module",
"version": "1.1.0"
"version": "1.1.1"
}
Loading
Loading