Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d906dc8

Browse filesBrowse files
authored
ReactPy ASGI Middleware and standalone ReactPy ASGI App (#1113)
- `ReactPy` class has been created as the standalone API for ReactPy. - `ReactPyMiddleware` has been created as the version of ReactPy that can wrap other ASGI web frameworks. - Added a template tag to use alongside our middleware. - `@reactpy/client` has been rewritten to be more modular. - `reactpy.backends.*` is removed.
1 parent 178fc05 commit d906dc8
Copy full SHA for d906dc8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Dismiss banner
Expand file treeCollapse file tree

84 files changed

+1811
-2665
lines changed

‎.github/workflows/.hatch-run.yml

Copy file name to clipboardExpand all lines: .github/workflows/.hatch-run.yml
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
with:
4444
python-version: ${{ matrix.python-version }}
4545
- name: Install Python Dependencies
46-
run: pip install --upgrade pip hatch uv
46+
run: pip install --upgrade hatch uv
4747
- name: Run Scripts
4848
env:
4949
NPM_CONFIG_TOKEN: ${{ secrets.node-auth-token }}

‎.github/workflows/check.yml

Copy file name to clipboardExpand all lines: .github/workflows/check.yml
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
- main
77
pull_request:
88
branches:
9-
- main
9+
- "*"
1010
schedule:
1111
- cron: "0 0 * * 0"
1212

@@ -27,8 +27,10 @@ jobs:
2727
job-name: "python-{0} {1}"
2828
run-cmd: "hatch test"
2929
runs-on: '["ubuntu-latest", "macos-latest", "windows-latest"]'
30-
python-version: '["3.9", "3.10", "3.11"]'
30+
python-version: '["3.10", "3.11", "3.12", "3.13"]'
3131
test-documentation:
32+
# Temporarily disabled
33+
if: 0
3234
uses: ./.github/workflows/.hatch-run.yml
3335
with:
3436
job-name: "python-{0}"

‎.gitignore

Copy file name to clipboardExpand all lines: .gitignore
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# --- Build Artifacts ---
2-
src/reactpy/static/**/index.js*
2+
src/reactpy/static/*
33

44
# --- Jupyter ---
55
*.ipynb_checkpoints
@@ -15,8 +15,8 @@ src/reactpy/static/**/index.js*
1515

1616
# --- Python ---
1717
.hatch
18-
.venv
19-
venv
18+
.venv*
19+
venv*
2020
MANIFEST
2121
build
2222
dist

‎docs/Dockerfile

Copy file name to clipboardExpand all lines: docs/Dockerfile
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ RUN sphinx-build -v -W -b html source build
3333
# Define Entrypoint
3434
# -----------------
3535
ENV PORT=5000
36-
ENV REACTPY_DEBUG_MODE=1
36+
ENV REACTPY_DEBUG=1
3737
ENV REACTPY_CHECK_VDOM_SPEC=0
3838
CMD ["python", "main.py"]

‎docs/docs_app/app.py

Copy file name to clipboardExpand all lines: docs/docs_app/app.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from docs_app.examples import get_normalized_example_name, load_examples
77
from reactpy import component
88
from reactpy.backend.sanic import Options, configure, use_request
9-
from reactpy.core.types import ComponentConstructor
9+
from reactpy.types import ComponentConstructor
1010

1111
THIS_DIR = Path(__file__).parent
1212
DOCS_DIR = THIS_DIR.parent

‎docs/source/about/changelog.rst

Copy file name to clipboardExpand all lines: docs/source/about/changelog.rst
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,36 @@ Changelog
1515
Unreleased
1616
----------
1717

18+
**Added**
19+
- :pull:`1113` - Added ``reactpy.ReactPy`` that can be used to run ReactPy in standalone mode.
20+
- :pull:`1113` - Added ``reactpy.ReactPyMiddleware`` that can be used to run ReactPy with any ASGI compatible framework.
21+
- :pull:`1113` - Added ``reactpy.jinja.Component`` that can be used alongside ``ReactPyMiddleware`` to embed several ReactPy components into your existing application.
22+
- :pull:`1113` - Added ``standard``, ``uvicorn``, ``jinja`` installation extras (for example ``pip install reactpy[standard]``).
23+
- :pull:`1113` - Added support for Python 3.12 and 3.13.
24+
1825
**Changed**
1926

2027
- :pull:`1251` - Substitute client-side usage of ``react`` with ``preact``.
2128
- :pull:`1239` - Script elements no longer support behaving like effects. They now strictly behave like plain HTML script elements.
2229
- :pull:`1255` - The ``reactpy.html`` module has been modified to allow for auto-creation of any HTML nodes. For example, you can create a ``<data-table>`` element by calling ``html.data_table()``.
2330
- :pull:`1256` - Change ``set_state`` comparison method to check equality with ``==`` more consistently.
2431
- :pull:`1257` - Add support for rendering ``@component`` children within ``vdom_to_html``.
32+
- :pull:`1113` - Renamed the ``use_location`` hook's ``search`` attribute to ``query_string``.
33+
- :pull:`1113` - Renamed the ``use_location`` hook's ``pathname`` attribute to ``path``.
34+
- :pull:`1113` - Renamed ``reactpy.config.REACTPY_DEBUG_MODE`` to ``reactpy.config.REACTPY_DEBUG``.
2535

2636
**Removed**
2737

2838
- :pull:`1255` - Removed the ability to import ``reactpy.html.*`` elements directly. You must now call ``html.*`` to access the elements.
2939
- :pull:`1255` - Removed ``reactpy.sample`` module.
3040
- :pull:`1255` - Removed ``reactpy.svg`` module. Contents previously within ``reactpy.svg.*`` can now be accessed via ``html.svg.*``.
3141
- :pull:`1255` - Removed ``reactpy.html._`` function. Use ``html.fragment`` instead.
42+
- :pull:`1113` - Removed ``reactpy.run``. See the documentation for the new method to run ReactPy applications.
43+
- :pull:`1113` - Removed ``reactpy.backend.*``. See the documentation for the new method to run ReactPy applications.
44+
- :pull:`1113` - Removed ``reactpy.core.types`` module. Use ``reactpy.types`` instead.
45+
- :pull:`1113` - All backend related installation extras (such as ``pip install reactpy[starlette]``) have been removed.
46+
- :pull:`1113` - Removed deprecated function ``module_from_template``.
47+
- :pull:`1113` - Removed support for Python 3.9.
3248

3349
**Fixed**
3450

‎docs/source/guides/escape-hatches/distributing-javascript.rst

Copy file name to clipboardExpand all lines: docs/source/guides/escape-hatches/distributing-javascript.rst
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ loaded with :func:`~reactpy.web.module.export`.
188188
189189
.. note::
190190

191-
When :data:`reactpy.config.REACTPY_DEBUG_MODE` is active, named exports will be validated.
191+
When :data:`reactpy.config.REACTPY_DEBUG` is active, named exports will be validated.
192192

193193
The remaining files that we need to create are concerned with creating a Python package.
194194
We won't cover all the details here, so refer to the Setuptools_ documentation for

‎docs/source/guides/getting-started/running-reactpy.rst

Copy file name to clipboardExpand all lines: docs/source/guides/getting-started/running-reactpy.rst
+4-4Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,29 +103,29 @@ Running ReactPy in Debug Mode
103103
-----------------------------
104104

105105
ReactPy provides a debug mode that is turned off by default. This can be enabled when you
106-
run your application by setting the ``REACTPY_DEBUG_MODE`` environment variable.
106+
run your application by setting the ``REACTPY_DEBUG`` environment variable.
107107

108108
.. tab-set::
109109

110110
.. tab-item:: Unix Shell
111111

112112
.. code-block::
113113
114-
export REACTPY_DEBUG_MODE=1
114+
export REACTPY_DEBUG=1
115115
python my_reactpy_app.py
116116
117117
.. tab-item:: Command Prompt
118118

119119
.. code-block:: text
120120
121-
set REACTPY_DEBUG_MODE=1
121+
set REACTPY_DEBUG=1
122122
python my_reactpy_app.py
123123
124124
.. tab-item:: PowerShell
125125

126126
.. code-block:: powershell
127127
128-
$env:REACTPY_DEBUG_MODE = "1"
128+
$env:REACTPY_DEBUG = "1"
129129
python my_reactpy_app.py
130130
131131
.. danger::

‎pyproject.toml

Copy file name to clipboardExpand all lines: pyproject.toml
+27-46Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ classifiers = [
2929
dependencies = [
3030
"exceptiongroup >=1.0",
3131
"typing-extensions >=3.10",
32-
"mypy-extensions >=0.4.3",
3332
"anyio >=3",
3433
"jsonpatch >=1.32",
3534
"fastjsonschema >=2.14.5",
3635
"requests >=2",
3736
"colorlog >=6",
3837
"asgiref >=3",
3938
"lxml >=4",
39+
"servestatic >=3.0.0",
40+
"orjson >=3",
4041
]
4142
dynamic = ["version"]
4243
urls.Changelog = "https://reactpy.dev/docs/about/changelog.html"
@@ -69,24 +70,15 @@ commands = [
6970
'bun run --cwd "src/js/packages/@reactpy/client" build',
7071
'bun install --cwd "src/js/packages/@reactpy/app"',
7172
'bun run --cwd "src/js/packages/@reactpy/app" build',
72-
'python "src/build_scripts/copy_dir.py" "src/js/packages/@reactpy/app/dist" "src/reactpy/static/assets"',
73+
'python "src/build_scripts/copy_dir.py" "src/js/packages/@reactpy/app/dist" "src/reactpy/static"',
7374
]
7475
artifacts = []
7576

7677
[project.optional-dependencies]
77-
# TODO: Nuke backends from the optional deps
78-
all = ["reactpy[starlette,sanic,fastapi,flask,tornado,testing]"]
79-
starlette = ["starlette >=0.13.6", "uvicorn[standard] >=0.19.0"]
80-
sanic = [
81-
"sanic>=21",
82-
"sanic-cors",
83-
"tracerite>=1.1.1",
84-
"setuptools",
85-
"uvicorn[standard]>=0.19.0",
86-
]
87-
fastapi = ["fastapi >=0.63.0", "uvicorn[standard] >=0.19.0"]
88-
flask = ["flask", "markupsafe>=1.1.1,<2.1", "flask-cors", "flask-sock"]
89-
tornado = ["tornado"]
78+
all = ["reactpy[jinja,uvicorn,testing]"]
79+
standard = ["reactpy[jinja,uvicorn]"]
80+
jinja = ["jinja2-simple-tags", "jinja2 >=3"]
81+
uvicorn = ["uvicorn[standard]"]
9082
testing = ["playwright"]
9183

9284

@@ -103,45 +95,31 @@ extra-dependencies = [
10395
"responses",
10496
"playwright",
10597
"jsonpointer",
106-
# TODO: Nuke everything past this point after removing backends from deps
107-
"starlette >=0.13.6",
108-
"uvicorn[standard] >=0.19.0",
109-
"sanic>=21",
110-
"sanic-cors",
111-
"sanic-testing",
112-
"tracerite>=1.1.1",
113-
"setuptools",
114-
"uvicorn[standard]>=0.19.0",
115-
"fastapi >=0.63.0",
116-
"uvicorn[standard] >=0.19.0",
117-
"flask",
118-
"markupsafe>=1.1.1,<2.1",
119-
"flask-cors",
120-
"flask-sock",
121-
"tornado",
98+
"uvicorn[standard]",
99+
"jinja2-simple-tags",
100+
"jinja2 >=3",
101+
"starlette",
122102
]
123103

124104
[[tool.hatch.envs.hatch-test.matrix]]
125-
python = ["3.9", "3.10", "3.11", "3.12"]
105+
python = ["3.10", "3.11", "3.12", "3.13"]
126106

127107
[tool.pytest.ini_options]
128108
addopts = """\
129-
--strict-config
130-
--strict-markers
131-
"""
109+
--strict-config
110+
--strict-markers
111+
"""
112+
filterwarnings = """
113+
ignore::DeprecationWarning:uvicorn.*
114+
ignore::DeprecationWarning:websockets.*
115+
ignore::UserWarning:tests.test_core.test_vdom
116+
"""
132117
testpaths = "tests"
133118
xfail_strict = true
134119
asyncio_mode = "auto"
120+
asyncio_default_fixture_loop_scope = "function"
135121
log_cli_level = "INFO"
136122

137-
[tool.hatch.envs.default.scripts]
138-
test-cov = "playwright install && coverage run -m pytest {args:tests}"
139-
cov-report = ["coverage report"]
140-
cov = ["test-cov {args}", "cov-report"]
141-
142-
[tool.hatch.envs.default.env-vars]
143-
REACTPY_DEBUG_MODE = "1"
144-
145123
#######################################
146124
# >>> Hatch Documentation Scripts <<< #
147125
#######################################
@@ -256,10 +234,14 @@ warn_unused_ignores = true
256234
source_pkgs = ["reactpy"]
257235
branch = false
258236
parallel = false
259-
omit = ["reactpy/__init__.py"]
237+
omit = [
238+
"src/reactpy/__init__.py",
239+
"src/reactpy/_console/*",
240+
"src/reactpy/__main__.py",
241+
]
260242

261243
[tool.coverage.report]
262-
fail_under = 98
244+
fail_under = 100
263245
show_missing = true
264246
skip_covered = true
265247
sort = "Name"
@@ -269,7 +251,6 @@ exclude_also = [
269251
"if __name__ == .__main__.:",
270252
"if TYPE_CHECKING:",
271253
]
272-
omit = ["**/reactpy/__main__.py"]
273254

274255
[tool.ruff]
275256
target-version = "py39"

‎src/js/packages/@reactpy/app/package.json

Copy file name to clipboardExpand all lines: src/js/packages/@reactpy/app/package.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"typescript": "^5.7.3"
1212
},
1313
"scripts": {
14-
"build": "bun build \"src/index.ts\" --outdir \"dist\" --minify --sourcemap=linked",
14+
"build": "bun build \"src/index.ts\" --outdir=\"dist\" --minify --sourcemap=\"linked\"",
1515
"checkTypes": "tsc --noEmit"
1616
}
1717
}
+1-19Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1 @@
1-
import { mount, SimpleReactPyClient } from "@reactpy/client";
2-
3-
function app(element: HTMLElement) {
4-
const client = new SimpleReactPyClient({
5-
serverLocation: {
6-
url: document.location.origin,
7-
route: document.location.pathname,
8-
query: document.location.search,
9-
},
10-
});
11-
mount(element, client);
12-
}
13-
14-
const element = document.getElementById("app");
15-
if (element) {
16-
app(element);
17-
} else {
18-
console.error("Element with id 'app' not found");
19-
}
1+
export { mountReactPy } from "@reactpy/client";
+83Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import logger from "./logger";
2+
import {
3+
ReactPyClientInterface,
4+
ReactPyModule,
5+
GenericReactPyClientProps,
6+
ReactPyUrls,
7+
} from "./types";
8+
import { createReconnectingWebSocket } from "./websocket";
9+
10+
export abstract class BaseReactPyClient implements ReactPyClientInterface {
11+
private readonly handlers: { [key: string]: ((message: any) => void)[] } = {};
12+
protected readonly ready: Promise<void>;
13+
private resolveReady: (value: undefined) => void;
14+
15+
constructor() {
16+
this.resolveReady = () => {};
17+
this.ready = new Promise((resolve) => (this.resolveReady = resolve));
18+
}
19+
20+
onMessage(type: string, handler: (message: any) => void): () => void {
21+
(this.handlers[type] || (this.handlers[type] = [])).push(handler);
22+
this.resolveReady(undefined);
23+
return () => {
24+
this.handlers[type] = this.handlers[type].filter((h) => h !== handler);
25+
};
26+
}
27+
28+
abstract sendMessage(message: any): void;
29+
abstract loadModule(moduleName: string): Promise<ReactPyModule>;
30+
31+
/**
32+
* Handle an incoming message.
33+
*
34+
* This should be called by subclasses when a message is received.
35+
*
36+
* @param message The message to handle. The message must have a `type` property.
37+
*/
38+
protected handleIncoming(message: any): void {
39+
if (!message.type) {
40+
logger.warn("Received message without type", message);
41+
return;
42+
}
43+
44+
const messageHandlers: ((m: any) => void)[] | undefined =
45+
this.handlers[message.type];
46+
if (!messageHandlers) {
47+
logger.warn("Received message without handler", message);
48+
return;
49+
}
50+
51+
messageHandlers.forEach((h) => h(message));
52+
}
53+
}
54+
55+
export class ReactPyClient
56+
extends BaseReactPyClient
57+
implements ReactPyClientInterface
58+
{
59+
urls: ReactPyUrls;
60+
socket: { current?: WebSocket };
61+
mountElement: HTMLElement;
62+
63+
constructor(props: GenericReactPyClientProps) {
64+
super();
65+
66+
this.urls = props.urls;
67+
this.mountElement = props.mountElement;
68+
this.socket = createReconnectingWebSocket({
69+
url: this.urls.componentUrl,
70+
readyPromise: this.ready,
71+
...props.reconnectOptions,
72+
onMessage: (event) => this.handleIncoming(JSON.parse(event.data)),
73+
});
74+
}
75+
76+
sendMessage(message: any): void {
77+
this.socket.current?.send(JSON.stringify(message));
78+
}
79+
80+
loadModule(moduleName: string): Promise<ReactPyModule> {
81+
return import(`${this.urls.jsModulesPath}${moduleName}`);
82+
}
83+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.