reactpy_django 3.8.1__tar.gz → 4.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reactpy_django-3.8.1/src/reactpy_django.egg-info → reactpy_django-4.0.0}/PKG-INFO +4 -3
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/README.md +3 -2
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/setup.py +21 -3
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/__init__.py +12 -2
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/components.py +61 -6
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/config.py +3 -2
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/hooks.py +66 -93
- reactpy_django-4.0.0/src/reactpy_django/html.py +3 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/management/commands/clean_reactpy.py +2 -2
- reactpy_django-4.0.0/src/reactpy_django/pyscript/component_template.py +26 -0
- reactpy_django-4.0.0/src/reactpy_django/pyscript/layout_handler.py +138 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/morphdom/morphdom-esm.js +764 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/morphdom/morphdom-factory.js +700 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/morphdom/morphdom-umd.js +772 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/morphdom/morphdom-umd.min.js +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/morphdom/morphdom.js +766 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror-CdY86QAP.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror-CdY86QAP.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_commands-Cg5rCf1I.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_commands-Cg5rCf1I.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_lang-python-BVX9Mc4s.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_lang-python-BVX9Mc4s.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_language-BFA8854-.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_language-BFA8854-.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_state-D1qTXrff.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_state-D1qTXrff.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_view-CH0UDCz6.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_view-CH0UDCz6.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/core-z7AxuHhC.js +3 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/core-z7AxuHhC.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/core.css +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/core.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/core.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/deprecations-manager-ru7MT5Is.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/deprecations-manager-ru7MT5Is.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/error-Dno1huoi.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/error-Dno1huoi.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/index-C02Lyjc0.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/index-C02Lyjc0.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/mpy-C1lmhZWt.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/mpy-C1lmhZWt.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-BLNbtGqo.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-BLNbtGqo.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-editor-eO3NBU8y.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-editor-eO3NBU8y.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-terminal-hOF-sQ2I.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-terminal-hOF-sQ2I.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/toml-CvAfdf9_.js +3 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/toml-CvAfdf9_.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/toml-DiUM0_qs.js +3 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/toml-DiUM0_qs.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm-BY7uk_OU.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm-BY7uk_OU.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm-readline-DjufFwfn.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm-readline-DjufFwfn.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm.css +7 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm_addon-fit--gyF3PcZ.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm_addon-fit--gyF3PcZ.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm_addon-web-links-Cnej-nJ6.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm_addon-web-links-Cnej-nJ6.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/zip-BxogYCy6.js +2 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/zip-BxogYCy6.js.map +1 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript-custom.css +3 -0
- reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript-hide-debug.css +3 -0
- reactpy_django-4.0.0/src/reactpy_django/templates/reactpy/pyscript_component.html +2 -0
- reactpy_django-4.0.0/src/reactpy_django/templates/reactpy/pyscript_setup.html +8 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/templatetags/reactpy.py +91 -62
- reactpy_django-4.0.0/src/reactpy_django/types.py +81 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/utils.py +192 -16
- reactpy_django-4.0.0/src/reactpy_django/websocket/__init__.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/websocket/consumer.py +17 -8
- {reactpy_django-3.8.1 → reactpy_django-4.0.0/src/reactpy_django.egg-info}/PKG-INFO +4 -3
- reactpy_django-4.0.0/src/reactpy_django.egg-info/SOURCES.txt +108 -0
- reactpy_django-3.8.1/src/reactpy_django/types.py +0 -121
- reactpy_django-3.8.1/src/reactpy_django.egg-info/SOURCES.txt +0 -49
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/LICENSE.md +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/MANIFEST.in +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/pyproject.toml +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/setup.cfg +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/apps.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/checks.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/clean.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/database.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/decorators.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/exceptions.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/http/__init__.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/http/urls.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/http/views.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/management/__init__.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/management/commands/__init__.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0001_initial.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0002_rename_created_at_componentparams_last_accessed.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0003_componentsession_delete_componentparams.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0004_config.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0005_alter_componentsession_last_accessed.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0006_userdatamodel.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/__init__.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/models.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/py.typed +0 -0
- {reactpy_django-3.8.1/src/reactpy_django/templatetags → reactpy_django-4.0.0/src/reactpy_django/pyscript}/__init__.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/router/__init__.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/router/converters.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/router/resolvers.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/static/reactpy_django/client.js +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/templates/reactpy/component.html +0 -0
- {reactpy_django-3.8.1/src/reactpy_django/websocket → reactpy_django-4.0.0/src/reactpy_django/templatetags}/__init__.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/websocket/paths.py +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django.egg-info/dependency_links.txt +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django.egg-info/not-zip-safe +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django.egg-info/requires.txt +0 -0
- {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: reactpy_django
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: It's React, but in Python. Now with Django integration.
|
|
5
5
|
Home-page: https://github.com/reactive-python/reactpy-django
|
|
6
6
|
Author: Mark Bakhit
|
|
@@ -34,7 +34,7 @@ Requires-Dist: orjson>=3.6.0
|
|
|
34
34
|
Requires-Dist: nest_asyncio>=1.5.0
|
|
35
35
|
Requires-Dist: typing_extensions
|
|
36
36
|
|
|
37
|
-
# <img src="https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg" align="left" height="45"/> ReactPy
|
|
37
|
+
# <img src="https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg" align="left" height="45"/> ReactPy-Django
|
|
38
38
|
|
|
39
39
|
<p>
|
|
40
40
|
<a href="https://github.com/reactive-python/reactpy-django/actions?query=workflow%3ATest">
|
|
@@ -57,6 +57,7 @@ Requires-Dist: typing_extensions
|
|
|
57
57
|
[ReactPy-Django](https://github.com/reactive-python/reactpy-django) is used to add [ReactPy](https://reactpy.dev/) support to an existing **Django project**. This package also turbocharges ReactPy with features such as...
|
|
58
58
|
|
|
59
59
|
- [SEO compatible rendering](https://reactive-python.github.io/reactpy-django/latest/reference/settings/#reactpy_prerender)
|
|
60
|
+
- [Client-Side Python components](https://reactive-python.github.io/reactpy-django/latest/reference/template-tag/#pyscript-component)
|
|
60
61
|
- [Single page application (SPA) capabilities](https://reactive-python.github.io/reactpy-django/latest/reference/router/#django-router)
|
|
61
62
|
- [Distributed computing](https://reactive-python.github.io/reactpy-django/latest/reference/settings/#reactpy_default_hosts)
|
|
62
63
|
- [Performance enhancements](https://reactive-python.github.io/reactpy-django/latest/reference/settings/#performance-settings)
|
|
@@ -118,7 +119,7 @@ def hello_world(recipient: str):
|
|
|
118
119
|
|
|
119
120
|
<!--py-code-end-->
|
|
120
121
|
|
|
121
|
-
## [`my_app/templates/
|
|
122
|
+
## [`my_app/templates/my_template.html`](https://docs.djangoproject.com/en/dev/topics/templates/)
|
|
122
123
|
|
|
123
124
|
<!--html-header-start-->
|
|
124
125
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# <img src="https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg" align="left" height="45"/> ReactPy
|
|
1
|
+
# <img src="https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg" align="left" height="45"/> ReactPy-Django
|
|
2
2
|
|
|
3
3
|
<p>
|
|
4
4
|
<a href="https://github.com/reactive-python/reactpy-django/actions?query=workflow%3ATest">
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
[ReactPy-Django](https://github.com/reactive-python/reactpy-django) is used to add [ReactPy](https://reactpy.dev/) support to an existing **Django project**. This package also turbocharges ReactPy with features such as...
|
|
22
22
|
|
|
23
23
|
- [SEO compatible rendering](https://reactive-python.github.io/reactpy-django/latest/reference/settings/#reactpy_prerender)
|
|
24
|
+
- [Client-Side Python components](https://reactive-python.github.io/reactpy-django/latest/reference/template-tag/#pyscript-component)
|
|
24
25
|
- [Single page application (SPA) capabilities](https://reactive-python.github.io/reactpy-django/latest/reference/router/#django-router)
|
|
25
26
|
- [Distributed computing](https://reactive-python.github.io/reactpy-django/latest/reference/settings/#reactpy_default_hosts)
|
|
26
27
|
- [Performance enhancements](https://reactive-python.github.io/reactpy-django/latest/reference/settings/#performance-settings)
|
|
@@ -82,7 +83,7 @@ def hello_world(recipient: str):
|
|
|
82
83
|
|
|
83
84
|
<!--py-code-end-->
|
|
84
85
|
|
|
85
|
-
## [`my_app/templates/
|
|
86
|
+
## [`my_app/templates/my_template.html`](https://docs.djangoproject.com/en/dev/topics/templates/)
|
|
86
87
|
|
|
87
88
|
<!--html-header-start-->
|
|
88
89
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations, print_function
|
|
2
2
|
|
|
3
|
+
import shutil
|
|
3
4
|
import sys
|
|
4
5
|
import traceback
|
|
5
6
|
from distutils import log
|
|
@@ -16,7 +17,9 @@ from setuptools.command.sdist import sdist
|
|
|
16
17
|
name = "reactpy_django"
|
|
17
18
|
root_dir = Path(__file__).parent
|
|
18
19
|
src_dir = root_dir / "src"
|
|
20
|
+
js_dir = src_dir / "js"
|
|
19
21
|
package_dir = src_dir / name
|
|
22
|
+
static_dir = package_dir / "static" / name
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
# -----------------------------------------------------------------------------
|
|
@@ -97,22 +100,37 @@ package["long_description_content_type"] = "text/markdown"
|
|
|
97
100
|
def build_javascript_first(build_cls: type):
|
|
98
101
|
class Command(build_cls):
|
|
99
102
|
def run(self):
|
|
100
|
-
js_dir = str(src_dir / "js")
|
|
101
103
|
|
|
102
104
|
log.info("Installing Javascript...")
|
|
103
|
-
result = npm.call(["install"], cwd=js_dir)
|
|
105
|
+
result = npm.call(["install"], cwd=str(js_dir))
|
|
104
106
|
if result != 0:
|
|
105
107
|
log.error(traceback.format_exc())
|
|
106
108
|
log.error("Failed to install Javascript")
|
|
107
109
|
raise RuntimeError("Failed to install Javascript")
|
|
108
110
|
|
|
109
111
|
log.info("Building Javascript...")
|
|
110
|
-
result = npm.call(["run", "build"], cwd=js_dir)
|
|
112
|
+
result = npm.call(["run", "build"], cwd=str(js_dir))
|
|
111
113
|
if result != 0:
|
|
112
114
|
log.error(traceback.format_exc())
|
|
113
115
|
log.error("Failed to build Javascript")
|
|
114
116
|
raise RuntimeError("Failed to build Javascript")
|
|
115
117
|
|
|
118
|
+
log.info("Copying @pyscript/core distribution")
|
|
119
|
+
pyscript_dist = js_dir / "node_modules" / "@pyscript" / "core" / "dist"
|
|
120
|
+
pyscript_static_dir = static_dir / "pyscript"
|
|
121
|
+
if not pyscript_static_dir.exists():
|
|
122
|
+
pyscript_static_dir.mkdir()
|
|
123
|
+
for file in pyscript_dist.iterdir():
|
|
124
|
+
shutil.copy(file, pyscript_static_dir / file.name)
|
|
125
|
+
|
|
126
|
+
log.info("Copying Morphdom distribution")
|
|
127
|
+
morphdom_dist = js_dir / "node_modules" / "morphdom" / "dist"
|
|
128
|
+
morphdom_static_dir = static_dir / "morphdom"
|
|
129
|
+
if not morphdom_static_dir.exists():
|
|
130
|
+
morphdom_static_dir.mkdir()
|
|
131
|
+
for file in morphdom_dist.iterdir():
|
|
132
|
+
shutil.copy(file, morphdom_static_dir / file.name)
|
|
133
|
+
|
|
116
134
|
log.info("Successfully built Javascript")
|
|
117
135
|
super().run()
|
|
118
136
|
|
|
@@ -2,16 +2,26 @@ import contextlib
|
|
|
2
2
|
|
|
3
3
|
import nest_asyncio
|
|
4
4
|
|
|
5
|
-
from reactpy_django import
|
|
5
|
+
from reactpy_django import (
|
|
6
|
+
checks,
|
|
7
|
+
components,
|
|
8
|
+
decorators,
|
|
9
|
+
hooks,
|
|
10
|
+
html,
|
|
11
|
+
router,
|
|
12
|
+
types,
|
|
13
|
+
utils,
|
|
14
|
+
)
|
|
6
15
|
from reactpy_django.websocket.paths import (
|
|
7
16
|
REACTPY_WEBSOCKET_PATH,
|
|
8
17
|
REACTPY_WEBSOCKET_ROUTE,
|
|
9
18
|
)
|
|
10
19
|
|
|
11
|
-
__version__ = "
|
|
20
|
+
__version__ = "4.0.0"
|
|
12
21
|
__all__ = [
|
|
13
22
|
"REACTPY_WEBSOCKET_PATH",
|
|
14
23
|
"REACTPY_WEBSOCKET_ROUTE",
|
|
24
|
+
"html",
|
|
15
25
|
"hooks",
|
|
16
26
|
"components",
|
|
17
27
|
"decorators",
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
from typing import Any, Callable, Sequence, Union, cast, overload
|
|
6
6
|
from urllib.parse import urlencode
|
|
7
|
+
from uuid import uuid4
|
|
7
8
|
from warnings import warn
|
|
8
9
|
|
|
9
10
|
from django.contrib.staticfiles.finders import find
|
|
@@ -12,10 +13,17 @@ from django.http import HttpRequest
|
|
|
12
13
|
from django.urls import reverse
|
|
13
14
|
from django.views import View
|
|
14
15
|
from reactpy import component, hooks, html, utils
|
|
15
|
-
from reactpy.types import Key, VdomDict
|
|
16
|
+
from reactpy.types import ComponentType, Key, VdomDict
|
|
16
17
|
|
|
17
18
|
from reactpy_django.exceptions import ViewNotRegisteredError
|
|
18
|
-
from reactpy_django.
|
|
19
|
+
from reactpy_django.html import pyscript
|
|
20
|
+
from reactpy_django.utils import (
|
|
21
|
+
generate_obj_name,
|
|
22
|
+
import_module,
|
|
23
|
+
render_pyscript_template,
|
|
24
|
+
render_view,
|
|
25
|
+
vdom_or_component_to_string,
|
|
26
|
+
)
|
|
19
27
|
|
|
20
28
|
|
|
21
29
|
# Type hints for:
|
|
@@ -27,8 +35,7 @@ def view_to_component(
|
|
|
27
35
|
compatibility: bool = False,
|
|
28
36
|
transforms: Sequence[Callable[[VdomDict], Any]] = (),
|
|
29
37
|
strict_parsing: bool = True,
|
|
30
|
-
) -> Any:
|
|
31
|
-
...
|
|
38
|
+
) -> Any: ...
|
|
32
39
|
|
|
33
40
|
|
|
34
41
|
# Type hints for:
|
|
@@ -39,8 +46,7 @@ def view_to_component(
|
|
|
39
46
|
compatibility: bool = False,
|
|
40
47
|
transforms: Sequence[Callable[[VdomDict], Any]] = (),
|
|
41
48
|
strict_parsing: bool = True,
|
|
42
|
-
) -> Callable[[Callable], Any]:
|
|
43
|
-
...
|
|
49
|
+
) -> Callable[[Callable], Any]: ...
|
|
44
50
|
|
|
45
51
|
|
|
46
52
|
def view_to_component(
|
|
@@ -148,6 +154,29 @@ def django_js(static_path: str, key: Key | None = None):
|
|
|
148
154
|
return _django_js(static_path=static_path, key=key)
|
|
149
155
|
|
|
150
156
|
|
|
157
|
+
def pyscript_component(
|
|
158
|
+
*file_paths: str,
|
|
159
|
+
initial: str | VdomDict | ComponentType = "",
|
|
160
|
+
root: str = "root",
|
|
161
|
+
):
|
|
162
|
+
"""
|
|
163
|
+
Args:
|
|
164
|
+
file_paths: File path to your client-side component. If multiple paths are \
|
|
165
|
+
provided, the contents are automatically merged.
|
|
166
|
+
|
|
167
|
+
Kwargs:
|
|
168
|
+
initial: The initial HTML that is displayed prior to the PyScript component \
|
|
169
|
+
loads. This can either be a string containing raw HTML, a \
|
|
170
|
+
`#!python reactpy.html` snippet, or a non-interactive component.
|
|
171
|
+
root: The name of the root component function.
|
|
172
|
+
"""
|
|
173
|
+
return _pyscript_component(
|
|
174
|
+
*file_paths,
|
|
175
|
+
initial=initial,
|
|
176
|
+
root=root,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
151
180
|
@component
|
|
152
181
|
def _view_to_component(
|
|
153
182
|
view: Callable | View | str,
|
|
@@ -284,3 +313,29 @@ def _cached_static_contents(static_path: str) -> str:
|
|
|
284
313
|
)
|
|
285
314
|
|
|
286
315
|
return file_contents
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@component
|
|
319
|
+
def _pyscript_component(
|
|
320
|
+
*file_paths: str,
|
|
321
|
+
initial: str | VdomDict | ComponentType = "",
|
|
322
|
+
root: str = "root",
|
|
323
|
+
):
|
|
324
|
+
rendered, set_rendered = hooks.use_state(False)
|
|
325
|
+
uuid = uuid4().hex.replace("-", "")
|
|
326
|
+
initial = vdom_or_component_to_string(initial, uuid=uuid)
|
|
327
|
+
executor = render_pyscript_template(file_paths, uuid, root)
|
|
328
|
+
|
|
329
|
+
if not rendered:
|
|
330
|
+
# FIXME: This is needed to properly re-render PyScript during a WebSocket
|
|
331
|
+
# disconnection / reconnection. There may be a better way to do this in the future.
|
|
332
|
+
set_rendered(True)
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
return html._(
|
|
336
|
+
html.div(
|
|
337
|
+
{"id": f"pyscript-{uuid}", "className": "pyscript", "data-uuid": uuid},
|
|
338
|
+
initial,
|
|
339
|
+
),
|
|
340
|
+
pyscript({"async": ""}, executor),
|
|
341
|
+
)
|
|
@@ -7,7 +7,7 @@ from django.conf import settings
|
|
|
7
7
|
from django.core.cache import DEFAULT_CACHE_ALIAS
|
|
8
8
|
from django.db import DEFAULT_DB_ALIAS
|
|
9
9
|
from django.views import View
|
|
10
|
-
from reactpy.config import REACTPY_DEBUG_MODE
|
|
10
|
+
from reactpy.config import REACTPY_DEBUG_MODE as _REACTPY_DEBUG_MODE
|
|
11
11
|
from reactpy.core.types import ComponentConstructor
|
|
12
12
|
|
|
13
13
|
from reactpy_django.types import (
|
|
@@ -17,7 +17,8 @@ from reactpy_django.types import (
|
|
|
17
17
|
from reactpy_django.utils import import_dotted_path
|
|
18
18
|
|
|
19
19
|
# Non-configurable values
|
|
20
|
-
|
|
20
|
+
_REACTPY_DEBUG_MODE.set_current(getattr(settings, "DEBUG"))
|
|
21
|
+
REACTPY_DEBUG_MODE = _REACTPY_DEBUG_MODE.current
|
|
21
22
|
REACTPY_REGISTERED_COMPONENTS: dict[str, ComponentConstructor] = {}
|
|
22
23
|
REACTPY_FAILED_COMPONENTS: set[str] = set()
|
|
23
24
|
REACTPY_REGISTERED_IFRAME_VIEWS: dict[str, Callable | View] = {}
|
|
@@ -11,7 +11,6 @@ from typing import (
|
|
|
11
11
|
Sequence,
|
|
12
12
|
Union,
|
|
13
13
|
cast,
|
|
14
|
-
overload,
|
|
15
14
|
)
|
|
16
15
|
from uuid import uuid4
|
|
17
16
|
|
|
@@ -29,16 +28,16 @@ from reactpy_django.exceptions import UserNotFoundError
|
|
|
29
28
|
from reactpy_django.types import (
|
|
30
29
|
AsyncMessageReceiver,
|
|
31
30
|
AsyncMessageSender,
|
|
31
|
+
AsyncPostprocessor,
|
|
32
32
|
ConnectionType,
|
|
33
33
|
FuncParams,
|
|
34
34
|
Inferred,
|
|
35
35
|
Mutation,
|
|
36
|
-
MutationOptions,
|
|
37
36
|
Query,
|
|
38
|
-
|
|
37
|
+
SyncPostprocessor,
|
|
39
38
|
UserData,
|
|
40
39
|
)
|
|
41
|
-
from reactpy_django.utils import generate_obj_name, get_pk
|
|
40
|
+
from reactpy_django.utils import django_query_postprocessor, generate_obj_name, get_pk
|
|
42
41
|
|
|
43
42
|
if TYPE_CHECKING:
|
|
44
43
|
from channels_redis.core import RedisChannelLayer
|
|
@@ -51,7 +50,6 @@ _REFETCH_CALLBACKS: DefaultDict[Callable[..., Any], set[Callable[[], None]]] = (
|
|
|
51
50
|
)
|
|
52
51
|
|
|
53
52
|
|
|
54
|
-
# TODO: Deprecate this once the equivalent hook gets moved to reactpy.hooks.*
|
|
55
53
|
def use_location() -> Location:
|
|
56
54
|
"""Get the current route as a `Location` object"""
|
|
57
55
|
return _use_location()
|
|
@@ -85,7 +83,6 @@ def use_origin() -> str | None:
|
|
|
85
83
|
return None
|
|
86
84
|
|
|
87
85
|
|
|
88
|
-
# TODO: Deprecate this once the equivalent hook gets moved to reactpy.hooks.*
|
|
89
86
|
def use_scope() -> dict[str, Any]:
|
|
90
87
|
"""Get the current ASGI scope dictionary"""
|
|
91
88
|
scope = _use_scope()
|
|
@@ -96,55 +93,55 @@ def use_scope() -> dict[str, Any]:
|
|
|
96
93
|
raise TypeError(f"Expected scope to be a dict, got {type(scope)}")
|
|
97
94
|
|
|
98
95
|
|
|
99
|
-
# TODO: Deprecate this once the equivalent hook gets moved to reactpy.hooks.*
|
|
100
96
|
def use_connection() -> ConnectionType:
|
|
101
97
|
"""Get the current `Connection` object"""
|
|
102
98
|
return _use_connection()
|
|
103
99
|
|
|
104
100
|
|
|
105
|
-
@overload
|
|
106
101
|
def use_query(
|
|
107
|
-
options: QueryOptions,
|
|
108
|
-
/,
|
|
109
102
|
query: Callable[FuncParams, Awaitable[Inferred]] | Callable[FuncParams, Inferred],
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
*args: FuncParams.args,
|
|
119
|
-
**kwargs: FuncParams.kwargs,
|
|
120
|
-
) -> Query[Inferred]: ...
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def use_query(*args, **kwargs) -> Query[Inferred]:
|
|
103
|
+
kwargs: dict[str, Any] | None = None,
|
|
104
|
+
*,
|
|
105
|
+
thread_sensitive: bool = True,
|
|
106
|
+
postprocessor: (
|
|
107
|
+
AsyncPostprocessor | SyncPostprocessor | None
|
|
108
|
+
) = django_query_postprocessor,
|
|
109
|
+
postprocessor_kwargs: dict[str, Any] | None = None,
|
|
110
|
+
) -> Query[Inferred]:
|
|
124
111
|
"""This hook is used to execute functions in the background and return the result, \
|
|
125
112
|
typically to read data the Django ORM.
|
|
126
113
|
|
|
127
114
|
Args:
|
|
128
|
-
|
|
129
|
-
query: A callable that returns a Django `Model` or `QuerySet`.
|
|
130
|
-
*args: Positional arguments to pass into `query`.
|
|
115
|
+
query: A function that executes a query and returns some data.
|
|
131
116
|
|
|
132
|
-
|
|
133
|
-
|
|
117
|
+
Kwargs:
|
|
118
|
+
kwargs: Keyword arguments to passed into the `query` function.
|
|
119
|
+
thread_sensitive: Whether to run the query in thread sensitive mode. \
|
|
120
|
+
This mode only applies to sync query functions, and is turned on by default \
|
|
121
|
+
due to Django ORM limitations.
|
|
122
|
+
postprocessor: A callable that processes the query `data` before it is returned. \
|
|
123
|
+
The first argument of postprocessor function must be the query `data`. All \
|
|
124
|
+
proceeding arguments are optional `postprocessor_kwargs`. This postprocessor \
|
|
125
|
+
function must return the modified `data`. \
|
|
126
|
+
\
|
|
127
|
+
If unset, `REACTPY_DEFAULT_QUERY_POSTPROCESSOR` is used. By default, this \
|
|
128
|
+
is used to prevent Django's lazy query execution and supports `many_to_many` \
|
|
129
|
+
and `many_to_one` as `postprocessor_kwargs`.
|
|
130
|
+
postprocessor_kwargs: Keyworded arguments passed into the `postprocessor` function.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
An object containing `loading`/`#!python error` states, your `data` (if the query \
|
|
134
|
+
has successfully executed), and a `refetch` callable that can be used to re-run the query.
|
|
135
|
+
"""
|
|
134
136
|
|
|
135
137
|
should_execute, set_should_execute = use_state(True)
|
|
136
138
|
data, set_data = use_state(cast(Inferred, None))
|
|
137
139
|
loading, set_loading = use_state(True)
|
|
138
140
|
error, set_error = use_state(cast(Union[Exception, None], None))
|
|
139
|
-
if isinstance(args[0], QueryOptions):
|
|
140
|
-
query_options, query, query_args, query_kwargs = _use_query_args_1(
|
|
141
|
-
*args, **kwargs
|
|
142
|
-
)
|
|
143
|
-
else:
|
|
144
|
-
query_options, query, query_args, query_kwargs = _use_query_args_2(
|
|
145
|
-
*args, **kwargs
|
|
146
|
-
)
|
|
147
141
|
query_ref = use_ref(query)
|
|
142
|
+
kwargs = kwargs or {}
|
|
143
|
+
postprocessor_kwargs = postprocessor_kwargs or {}
|
|
144
|
+
|
|
148
145
|
if query_ref.current is not query:
|
|
149
146
|
raise ValueError(f"Query function changed from {query_ref.current} to {query}.")
|
|
150
147
|
|
|
@@ -153,31 +150,27 @@ def use_query(*args, **kwargs) -> Query[Inferred]:
|
|
|
153
150
|
try:
|
|
154
151
|
# Run the query
|
|
155
152
|
if asyncio.iscoroutinefunction(query):
|
|
156
|
-
new_data = await query(
|
|
153
|
+
new_data = await query(**kwargs)
|
|
157
154
|
else:
|
|
158
155
|
new_data = await database_sync_to_async(
|
|
159
|
-
query,
|
|
160
|
-
|
|
161
|
-
)(*query_args, **query_kwargs)
|
|
156
|
+
query, thread_sensitive=thread_sensitive
|
|
157
|
+
)(**kwargs)
|
|
162
158
|
|
|
163
159
|
# Run the postprocessor
|
|
164
|
-
if
|
|
165
|
-
if asyncio.iscoroutinefunction(
|
|
166
|
-
new_data = await
|
|
167
|
-
new_data, **query_options.postprocessor_kwargs
|
|
168
|
-
)
|
|
160
|
+
if postprocessor:
|
|
161
|
+
if asyncio.iscoroutinefunction(postprocessor):
|
|
162
|
+
new_data = await postprocessor(new_data, **postprocessor_kwargs)
|
|
169
163
|
else:
|
|
170
164
|
new_data = await database_sync_to_async(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)(new_data, **query_options.postprocessor_kwargs)
|
|
165
|
+
postprocessor, thread_sensitive=thread_sensitive
|
|
166
|
+
)(new_data, **postprocessor_kwargs)
|
|
174
167
|
|
|
175
168
|
# Log any errors and set the error state
|
|
176
169
|
except Exception as e:
|
|
177
170
|
set_data(cast(Inferred, None))
|
|
178
171
|
set_loading(False)
|
|
179
172
|
set_error(e)
|
|
180
|
-
_logger.exception(
|
|
173
|
+
_logger.exception("Failed to execute query: %s", generate_obj_name(query))
|
|
181
174
|
return
|
|
182
175
|
|
|
183
176
|
# Query was successful
|
|
@@ -212,30 +205,18 @@ def use_query(*args, **kwargs) -> Query[Inferred]:
|
|
|
212
205
|
_REFETCH_CALLBACKS[query].add(refetch)
|
|
213
206
|
return lambda: _REFETCH_CALLBACKS[query].remove(refetch)
|
|
214
207
|
|
|
215
|
-
#
|
|
208
|
+
# Return Query user API
|
|
216
209
|
return Query(data, loading, error, refetch)
|
|
217
210
|
|
|
218
211
|
|
|
219
|
-
@overload
|
|
220
|
-
def use_mutation(
|
|
221
|
-
options: MutationOptions,
|
|
222
|
-
mutation: (
|
|
223
|
-
Callable[FuncParams, bool | None] | Callable[FuncParams, Awaitable[bool | None]]
|
|
224
|
-
),
|
|
225
|
-
refetch: Callable[..., Any] | Sequence[Callable[..., Any]] | None = None,
|
|
226
|
-
) -> Mutation[FuncParams]: ...
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
@overload
|
|
230
212
|
def use_mutation(
|
|
231
213
|
mutation: (
|
|
232
214
|
Callable[FuncParams, bool | None] | Callable[FuncParams, Awaitable[bool | None]]
|
|
233
215
|
),
|
|
216
|
+
*,
|
|
217
|
+
thread_sensitive: bool = True,
|
|
234
218
|
refetch: Callable[..., Any] | Sequence[Callable[..., Any]] | None = None,
|
|
235
|
-
) -> Mutation[FuncParams]:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def use_mutation(*args: Any, **kwargs: Any) -> Mutation[FuncParams]:
|
|
219
|
+
) -> Mutation[FuncParams]:
|
|
239
220
|
"""This hook is used to modify data in the background, typically to create/update/delete \
|
|
240
221
|
data from the Django ORM.
|
|
241
222
|
|
|
@@ -246,18 +227,24 @@ def use_mutation(*args: Any, **kwargs: Any) -> Mutation[FuncParams]:
|
|
|
246
227
|
mutation: A callable that performs Django ORM create, update, or delete \
|
|
247
228
|
functionality. If this function returns `False`, then your `refetch` \
|
|
248
229
|
function will not be used.
|
|
230
|
+
|
|
231
|
+
Kwargs:
|
|
232
|
+
thread_sensitive: Whether to run the mutation in thread sensitive mode. \
|
|
233
|
+
This mode only applies to sync mutation functions, and is turned on by default \
|
|
234
|
+
due to Django ORM limitations.
|
|
249
235
|
refetch: A query function (the function you provide to your `use_query` \
|
|
250
236
|
hook) or a sequence of query functions that need a `refetch` if the \
|
|
251
237
|
mutation succeeds. This is useful for refreshing data after a mutation \
|
|
252
238
|
has been performed.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
An object containing `#!python loading`/`#!python error` states, and a \
|
|
242
|
+
`#!python reset` callable that will set `#!python loading`/`#!python error` \
|
|
243
|
+
states to defaults. This object can be called to run the query.
|
|
253
244
|
"""
|
|
254
245
|
|
|
255
246
|
loading, set_loading = use_state(False)
|
|
256
247
|
error, set_error = use_state(cast(Union[Exception, None], None))
|
|
257
|
-
if isinstance(args[0], MutationOptions):
|
|
258
|
-
mutation_options, mutation, refetch = _use_mutation_args_1(*args, **kwargs)
|
|
259
|
-
else:
|
|
260
|
-
mutation_options, mutation, refetch = _use_mutation_args_2(*args, **kwargs)
|
|
261
248
|
|
|
262
249
|
# The main "running" function for `use_mutation`
|
|
263
250
|
async def execute_mutation(exec_args, exec_kwargs) -> None:
|
|
@@ -267,7 +254,7 @@ def use_mutation(*args: Any, **kwargs: Any) -> Mutation[FuncParams]:
|
|
|
267
254
|
should_refetch = await mutation(*exec_args, **exec_kwargs)
|
|
268
255
|
else:
|
|
269
256
|
should_refetch = await database_sync_to_async(
|
|
270
|
-
mutation, thread_sensitive=
|
|
257
|
+
mutation, thread_sensitive=thread_sensitive
|
|
271
258
|
)(*exec_args, **exec_kwargs)
|
|
272
259
|
|
|
273
260
|
# Log any errors and set the error state
|
|
@@ -275,7 +262,7 @@ def use_mutation(*args: Any, **kwargs: Any) -> Mutation[FuncParams]:
|
|
|
275
262
|
set_loading(False)
|
|
276
263
|
set_error(e)
|
|
277
264
|
_logger.exception(
|
|
278
|
-
|
|
265
|
+
"Failed to execute mutation: %s", generate_obj_name(mutation)
|
|
279
266
|
)
|
|
280
267
|
|
|
281
268
|
# Mutation was successful
|
|
@@ -311,7 +298,7 @@ def use_mutation(*args: Any, **kwargs: Any) -> Mutation[FuncParams]:
|
|
|
311
298
|
set_loading(False)
|
|
312
299
|
set_error(None)
|
|
313
300
|
|
|
314
|
-
#
|
|
301
|
+
# Return mutation user API
|
|
315
302
|
return Mutation(schedule_mutation, loading, error, reset)
|
|
316
303
|
|
|
317
304
|
|
|
@@ -355,11 +342,13 @@ def use_user_data(
|
|
|
355
342
|
await model.asave()
|
|
356
343
|
|
|
357
344
|
query: Query[dict | None] = use_query(
|
|
358
|
-
QueryOptions(postprocessor=None),
|
|
359
345
|
_get_user_data,
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
346
|
+
kwargs={
|
|
347
|
+
"user": user,
|
|
348
|
+
"default_data": default_data,
|
|
349
|
+
"save_default_data": save_default_data,
|
|
350
|
+
},
|
|
351
|
+
postprocessor=None,
|
|
363
352
|
)
|
|
364
353
|
mutation = use_mutation(_set_user_data, refetch=_get_user_data)
|
|
365
354
|
|
|
@@ -444,22 +433,6 @@ def use_root_id() -> str:
|
|
|
444
433
|
return scope["reactpy"]["id"]
|
|
445
434
|
|
|
446
435
|
|
|
447
|
-
def _use_query_args_1(options: QueryOptions, /, query: Query, *args, **kwargs):
|
|
448
|
-
return options, query, args, kwargs
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
def _use_query_args_2(query: Query, *args, **kwargs):
|
|
452
|
-
return QueryOptions(), query, args, kwargs
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
def _use_mutation_args_1(options: MutationOptions, mutation: Mutation, refetch=None):
|
|
456
|
-
return options, mutation, refetch
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
def _use_mutation_args_2(mutation, refetch=None):
|
|
460
|
-
return MutationOptions(), mutation, refetch
|
|
461
|
-
|
|
462
|
-
|
|
463
436
|
async def _get_user_data(
|
|
464
437
|
user: AbstractUser, default_data: None | dict, save_default_data: bool
|
|
465
438
|
) -> dict | None:
|
|
@@ -28,10 +28,10 @@ class Command(BaseCommand):
|
|
|
28
28
|
parser.add_argument(
|
|
29
29
|
"--sessions",
|
|
30
30
|
action="store_true",
|
|
31
|
-
help="
|
|
31
|
+
help="Clean session data. This value can be combined with other cleaning options.",
|
|
32
32
|
)
|
|
33
33
|
parser.add_argument(
|
|
34
34
|
"--user-data",
|
|
35
35
|
action="store_true",
|
|
36
|
-
help="
|
|
36
|
+
help="Clean user data. This value can be combined with other cleaning options.",
|
|
37
37
|
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
import asyncio
|
|
5
|
+
|
|
6
|
+
from reactpy_django.pyscript.layout_handler import ReactPyLayoutHandler
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# User component is inserted below by regex replacement
|
|
10
|
+
def user_workspace_UUID():
|
|
11
|
+
"""Encapsulate the user's code with a completely unique function (workspace)
|
|
12
|
+
to prevent overlapping imports and variable names between different components.
|
|
13
|
+
|
|
14
|
+
This code is designed to be run directly by PyScript, and is not intended to be run
|
|
15
|
+
in a normal Python environment.
|
|
16
|
+
|
|
17
|
+
ReactPy-Django performs string substitutions to turn this file into valid PyScript.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def root(): ...
|
|
21
|
+
|
|
22
|
+
return root()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Create a task to run the user's component workspace
|
|
26
|
+
task_UUID = asyncio.create_task(ReactPyLayoutHandler("UUID").run(user_workspace_UUID))
|