Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
2a4ed92
Improve test runner speed
Archmonger Dec 12, 2025
b5fd55a
Add pytest-timeout
Archmonger Dec 12, 2025
f44acf4
Add compatibility for test in `--parallel` mode
Archmonger Dec 12, 2025
b5ec6ad
fix coverage
Archmonger Dec 12, 2025
02cb5ab
Attempt fix for failing tests
Archmonger Dec 12, 2025
0c63629
Move test dep configuration out of Pytest and into Hatch
Archmonger Dec 12, 2025
b772582
self review
Archmonger Dec 12, 2025
9dcce14
fix tests
Archmonger Dec 12, 2025
b4d5c73
fix coverage
Archmonger Dec 13, 2025
9f318fd
Extend timeout since GITHUB_ACTIONS windows runners are slow
Archmonger Dec 13, 2025
0e60b47
Remove usage of anyio pytest marker
Archmonger Dec 13, 2025
3f92746
fix coverage script sequence
Archmonger Dec 13, 2025
5219744
Attempt different way of setting max timeout
Archmonger Dec 13, 2025
c09115f
Move playwright installation to a build script
Archmonger Dec 13, 2025
21c99a1
attempt setting timeout on `expect` global
Archmonger Dec 13, 2025
13175f3
Fix test runtime warnings
Archmonger Dec 13, 2025
d9c1163
delete old coverage files each test run
Archmonger Dec 13, 2025
f479f78
Try increasing global test timeout
Archmonger Dec 13, 2025
4acf27c
export github actions within reactpy.testing
Archmonger Dec 13, 2025
6970d50
fix `pip install -e .`
Archmonger Dec 13, 2025
bfba38c
minor f-string refactor
Archmonger Dec 13, 2025
9216c92
Add bundle flag to npm modules
Archmonger Dec 13, 2025
e1a8f93
Rewrite JS build steps. Rewrite fetching logic for remote components,…
Archmonger Dec 14, 2025
173682d
Remove copy from build script
Archmonger Dec 14, 2025
b7f533b
Fix JS linting
Archmonger Dec 14, 2025
5733044
Attempt fix for flaky ReactJS component mounting
Archmonger Dec 14, 2025
0bf49c5
attempt 2
Archmonger Dec 14, 2025
fd9b557
attempt 3
Archmonger Dec 14, 2025
b7f5dff
attempt 4
Archmonger Dec 14, 2025
34d925b
Attempt 5
Archmonger Dec 14, 2025
623596d
simplify bind logic
Archmonger Dec 14, 2025
1afde56
Use a deterministic way of waiting for elements in npm tests
Archmonger Dec 14, 2025
5c9e648
bugfix for null root element
Archmonger Dec 14, 2025
a74cee5
update docstring
Archmonger Dec 14, 2025
92ed04d
add --max-worker-restart
Archmonger Dec 14, 2025
fbe8530
remove unused import
Archmonger Dec 14, 2025
1afc57b
Attempt increasing timeout
Archmonger Dec 14, 2025
46a2651
Increase re-runs on github
Archmonger Dec 14, 2025
81e8c57
Add logging for failed http requests
Archmonger Dec 14, 2025
e1c10e0
Try setting up a RAM disk
Archmonger Dec 14, 2025
190fea2
ry setting hatch to use R drive
Archmonger Dec 14, 2025
ea1ff39
symlink playwright to ram drive
Archmonger Dec 14, 2025
7c59e11
use powershell cmd
Archmonger Dec 14, 2025
ce37028
try different path
Archmonger Dec 14, 2025
dd19d14
Remove ram drive, try using github cache
Archmonger Dec 14, 2025
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
20 changes: 10 additions & 10 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ pip install flask sanic tornado

**Run Python Tests:**

- `hatch test` -- takes 10-30 seconds for basic tests. NEVER CANCEL. Set timeout to 60+ minutes for full test suite. **All tests must always pass - failures are never expected or allowed.**
- `hatch test --cover` -- run tests with coverage reporting (used in CI)
- `hatch test -k test_name` -- run specific tests
- `hatch test tests/test_config.py` -- run specific test files
- `hatch test --parallel` -- takes 10-30 seconds for basic tests. NEVER CANCEL. Set timeout to 2 minutes for full test suite. **All tests must always pass - failures are never expected or allowed.**
- `hatch test --parallel --cover` -- run tests with coverage reporting (used in CI)
- `hatch test --parallel -k test_name` -- run specific tests
- `hatch test --parallel tests/test_config.py` -- run specific test files

**Run Python Linting and Formatting:**

Expand Down Expand Up @@ -152,7 +152,7 @@ print(f"✓ Hook-based component: {type(counter)}")
- `hatch run javascript:check` -- Ensure JavaScript passes linting (never expected to fail)
- Test basic component creation and rendering as shown above
- Test server creation if working on server-related features
- Run relevant tests with `hatch test` -- **All tests must always pass - failures are never expected or allowed**
- Run relevant tests with `hatch test --parallel` -- **All tests must always pass - failures are never expected or allowed**

**Integration Testing:**

Expand Down Expand Up @@ -263,9 +263,9 @@ The following are key commands for daily development:
### Development Commands

```bash
hatch test # Run all tests (**All tests must always pass**)
hatch test --cover # Run tests with coverage (used in CI)
hatch test -k test_name # Run specific tests
hatch test --parallel # Run all tests (**All tests must always pass**)
hatch test --parallel --cover # Run tests with coverage (used in CI)
hatch test --parallel -k test_name # Run specific tests
hatch fmt # Format code with all formatters
hatch fmt --check # Check formatting without changes
hatch run python:type_check # Run Python type checker
Expand Down Expand Up @@ -303,7 +303,7 @@ Follow this step-by-step process for effective development:
3. **Run formatting**: `hatch fmt` to format code (~1 second)
4. **Run type checking**: `hatch run python:type_check` for type checking (~10 seconds)
5. **Run JavaScript linting** (if JavaScript was modified): `hatch run javascript:check` (~10 seconds)
6. **Run relevant tests**: `hatch test` with specific test selection if needed. **All tests must always pass - failures are never expected or allowed.**
6. **Run relevant tests**: `hatch test --parallel` with specific test selection if needed. **All tests must always pass - failures are never expected or allowed.**
7. **Validate component functionality** manually using validation tests above
8. **Build JavaScript** (if modified): `hatch run javascript:build` (~15 seconds)
9. **Update documentation** when making changes to Python source code (required)
Expand Down Expand Up @@ -365,7 +365,7 @@ Modern dependency management via pyproject.toml:

The repository uses GitHub Actions with these key jobs:

- `test-python-coverage` -- Python test coverage with `hatch test --cover`
- `test-python-coverage` -- Python test coverage with `hatch test --parallel --cover`
- `lint-python` -- Python linting and type checking via `hatch fmt --check` and `hatch run python:type_check`
- `test-python` -- Cross-platform Python testing across Python 3.10-3.13 and Ubuntu/macOS/Windows
- `lint-javascript` -- JavaScript linting and type checking
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/.hatch-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,20 @@ jobs:
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v4
- if: runner.os == 'Windows'
name: Cache Playwright Install
uses: actions/cache@v5
with:
path: C:\Users\runneradmin\AppData\Local\ms-playwright\
key: ${{ runner.os }}-playwright
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Use Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
- name: Install Python Dependencies
run: pip install hatch
- name: Run Scripts
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "python-{0}"
run-cmd: "hatch test --cover"
run-cmd: "hatch test --parallel --cover"
lint-python:
uses: ./.github/workflows/.hatch-run.yml
with:
Expand All @@ -25,7 +25,7 @@ jobs:
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "python-{0} {1}"
run-cmd: "hatch test"
run-cmd: "hatch test --parallel"
runs-on: '["ubuntu-latest", "macos-latest", "windows-latest"]'
python-version: '["3.11", "3.12", "3.13", "3.14"]'
test-documentation:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
uses: ./.github/workflows/.hatch-run.yml
with:
job-name: "Publish to PyPI"
run-cmd: "hatch build --clean && hatch publish --yes"
run-cmd: "hatch run javascript:build && hatch build --clean && hatch publish --yes"
secrets:
pypi-username: ${{ secrets.PYPI_USERNAME }}
pypi-password: ${{ secrets.PYPI_PASSWORD }}
Expand Down
62 changes: 33 additions & 29 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[build-system]
build-backend = "hatchling.build"
requires = ["hatchling", "hatch-build-scripts"]
requires = ["hatchling", "hatch-build-scripts", "hatch"]

##############################
# >>> Hatch Build Config <<< #
Expand Down Expand Up @@ -71,28 +71,34 @@ installer = "uv"
reactpy = "reactpy._console.cli:entry_point"

[[tool.hatch.build.hooks.build-scripts.scripts]]
# Note: `hatch` can't be called within `build-scripts` when installing packages in editable mode, so we have to write the commands long-form
commands = [
'python "src/build_scripts/clean_js_dir.py"',
'bun install --cwd "src/js/packages/event-to-object"',
'bun run --cwd "src/js/packages/event-to-object" build',
'bun install --cwd "src/js/packages/@reactpy/client"',
'bun run --cwd "src/js/packages/@reactpy/client" build',
'bun install --cwd "src/js/packages/@reactpy/app"',
'bun run --cwd "src/js/packages/@reactpy/app" build',
'python "src/build_scripts/copy_dir.py" "src/js/node_modules/@pyscript/core/dist" "src/reactpy/static/pyscript"',
'python "src/build_scripts/copy_dir.py" "src/js/node_modules/morphdom/dist" "src/reactpy/static/morphdom"',
]
commands = []
artifacts = []

#############################
# >>> Hatch Test Runner <<< #
#############################
[tool.hatch.envs.hatch-test.scripts]
run = [
'hatch --env default run "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 javascript:build --dev",
"hatch --env default build -t wheel",
'hatch --env default run "src/build_scripts/delete_old_coverage.py"',
"coverage run -m pytest{env:HATCH_TEST_ARGS:} {args} --max-worker-restart 10",
]
cov-combine = "coverage combine"
cov-report = "coverage report"

[tool.hatch.envs.hatch-test]
extra-dependencies = [
"pytest-sugar",
"pytest-asyncio",
"pytest-timeout",
"responses",
"exceptiongroup",
"jsonpointer",
Expand All @@ -115,8 +121,10 @@ filterwarnings = """
testpaths = "tests"
xfail_strict = true
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
asyncio_default_fixture_loop_scope = "session"
asyncio_default_test_loop_scope = "session"
log_cli_level = "INFO"
timeout = 120

#######################################
# >>> Hatch Documentation Scripts <<< #
Expand Down Expand Up @@ -162,6 +170,7 @@ extra-dependencies = [
"types-requests",
"types-lxml",
"jsonpointer",
"pytest",
]

[tool.hatch.envs.python.scripts]
Expand All @@ -177,33 +186,28 @@ detached = true
[tool.hatch.envs.javascript.scripts]
check = [
'hatch run javascript:build',
'bun install --cwd "src/js"',
'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',
]
fix = ['bun install --cwd "src/js"', 'bun run --cwd "src/js" format']
test = ['hatch run javascript:build_event_to_object', 'bun test']
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 javascript:build_event_to_object',
'hatch run javascript:build_client',
'hatch run javascript:build_app',
'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"',

]
build_event_to_object = [
'bun install --cwd "src/js/packages/event-to-object"',
'bun run --cwd "src/js/packages/event-to-object" build',
]
build_client = [
'bun install --cwd "src/js/packages/@reactpy/client"',
'bun run --cwd "src/js/packages/@reactpy/client" build',
]
build_app = [
'bun install --cwd "src/js/packages/@reactpy/app"',
'bun run --cwd "src/js/packages/@reactpy/app" build',
'hatch run "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}']
publish_event_to_object = [
'hatch run javascript:build_event_to_object',
'cd "src/js/packages/event-to-object" && bun publish --access public',
Expand Down
30 changes: 30 additions & 0 deletions src/build_scripts/build_js_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
import pathlib
import subprocess
import sys

dev_mode = "--dev" in sys.argv
root_dir = pathlib.Path(__file__).parent.parent.parent
build_commands = [
[
"bun",
"install",
"--cwd",
"src/js/packages/@reactpy/app",
*([] if dev_mode else ["--production"]),
],
[
"bun",
"run",
"--cwd",
"src/js/packages/@reactpy/app",
"buildDev" if dev_mode else "build",
],
]

for command in build_commands:
print(f"Running command: '{command}'...") # noqa: T201
subprocess.run(command, check=True, cwd=root_dir) # noqa: S603
30 changes: 30 additions & 0 deletions src/build_scripts/build_js_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
import pathlib
import subprocess
import sys

dev_mode = "--dev" in sys.argv
root_dir = pathlib.Path(__file__).parent.parent.parent
build_commands = [
[
"bun",
"install",
"--cwd",
"src/js/packages/@reactpy/client",
*([] if dev_mode else ["--production"]),
],
[
"bun",
"run",
"--cwd",
"src/js/packages/@reactpy/client",
"build",
],
]

for command in build_commands:
print(f"Running command: '{command}'...") # noqa: T201
subprocess.run(command, check=True, cwd=root_dir) # noqa: S603
30 changes: 30 additions & 0 deletions src/build_scripts/build_js_event_to_object.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
import pathlib
import subprocess
import sys

dev_mode = "--dev" in sys.argv
root_dir = pathlib.Path(__file__).parent.parent.parent
build_commands = [
[
"bun",
"install",
"--cwd",
"src/js/packages/event-to-object",
*([] if dev_mode else ["--production"]),
],
[
"bun",
"run",
"--cwd",
"src/js/packages/event-to-object",
"build",
],
]

for command in build_commands:
print(f"Running command: '{command}'...") # noqa: T201
subprocess.run(command, check=True, cwd=root_dir) # noqa: S603
2 changes: 2 additions & 0 deletions src/build_scripts/clean_js_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import pathlib
import shutil

print("Cleaning JS source directory...") # noqa: T201

# Get the path to the JS source directory
js_src_dir = pathlib.Path(__file__).parent.parent / "js"
static_output_dir = pathlib.Path(__file__).parent.parent / "reactpy" / "static"
Expand Down
1 change: 1 addition & 0 deletions src/build_scripts/copy_dir.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def copy_files(source: Path, destination: Path) -> None:
root_dir = Path(__file__).parent.parent.parent
src = Path(root_dir / sys.argv[1])
dest = Path(root_dir / sys.argv[2])
print(f"Copying files from '{sys.argv[1]}' to '{sys.argv[2]}'...") # noqa: T201

if not src.exists():
logging.error("Source directory %s does not exist", src)
Expand Down
21 changes: 21 additions & 0 deletions src/build_scripts/delete_old_coverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///

import logging
from glob import glob
from pathlib import Path

# Delete old `.coverage*` files in the project root
print("Deleting old coverage files...") # noqa: T201
root_dir = Path(__file__).parent.parent.parent
coverage_files = glob(str(root_dir / ".coverage*"))

for path in coverage_files:
coverage_file = Path(path)
if coverage_file.exists():
try:
coverage_file.unlink()
except Exception as e:
logging.error(f"Failed to delete {coverage_file}: {e}")
17 changes: 17 additions & 0 deletions src/build_scripts/install_playwright.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///

import subprocess

print("Installing Playwright browsers...") # noqa: T201

# Install Chromium browser for Playwright, and fail if it cannot be installed
subprocess.run(["playwright", "install", "chromium"], check=True) # noqa: S607

# Try to install system dependencies. We don't generate an exception if this fails
# as *nix systems (such as WSL) return a failure code if there are *any* dependencies
# that could be cleaned up via `sudo apt autoremove`. This occurs even if we weren't
# the ones to install those dependencies in the first place.
subprocess.run(["playwright", "install-deps"], check=False) # noqa: S607
Binary file modified src/js/bun.lockb
Binary file not shown.
5 changes: 3 additions & 2 deletions src/js/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"workspaces": [
"packages/*",
"packages/@reactpy/*"
"packages/event-to-object",
"packages/@reactpy/app",
"packages/@reactpy/client"
],
"catalog": {
"preact": "^10.27.2",
Expand Down
3 changes: 2 additions & 1 deletion src/js/packages/@reactpy/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"license": "MIT",
"name": "@reactpy/app",
"scripts": {
"build": "bun build \"src/index.ts\" \"src/react.ts\" \"src/react-dom.ts\" \"src/react-jsx-runtime.ts\" --outdir=\"../../../../reactpy/static/\" --minify --sourcemap=\"linked\" --splitting",
"build": "bun build \"src/index.ts\" \"src/preact.ts\" \"src/preact-dom.ts\" \"src/preact-jsx-runtime.ts\" --outdir=\"../../../../reactpy/static/\" --minify --production --sourcemap=\"linked\" --splitting",
"buildDev": "bun build \"src/index.ts\" \"src/preact.ts\" \"src/preact-dom.ts\" \"src/preact-jsx-runtime.ts\" --outdir=\"../../../../reactpy/static/\" --sourcemap=\"linked\" --splitting",
"checkTypes": "tsc --noEmit"
}
}
1 change: 1 addition & 0 deletions src/js/packages/@reactpy/app/src/preact-jsx-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "preact/compat/jsx-runtime";
1 change: 0 additions & 1 deletion src/js/packages/@reactpy/app/src/react-jsx-runtime.ts

This file was deleted.

Loading
Loading