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.
Files changed (111) hide show
  1. {reactpy_django-3.8.1/src/reactpy_django.egg-info → reactpy_django-4.0.0}/PKG-INFO +4 -3
  2. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/README.md +3 -2
  3. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/setup.py +21 -3
  4. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/__init__.py +12 -2
  5. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/components.py +61 -6
  6. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/config.py +3 -2
  7. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/hooks.py +66 -93
  8. reactpy_django-4.0.0/src/reactpy_django/html.py +3 -0
  9. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/management/commands/clean_reactpy.py +2 -2
  10. reactpy_django-4.0.0/src/reactpy_django/pyscript/component_template.py +26 -0
  11. reactpy_django-4.0.0/src/reactpy_django/pyscript/layout_handler.py +138 -0
  12. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/morphdom/morphdom-esm.js +764 -0
  13. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/morphdom/morphdom-factory.js +700 -0
  14. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/morphdom/morphdom-umd.js +772 -0
  15. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/morphdom/morphdom-umd.min.js +1 -0
  16. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/morphdom/morphdom.js +766 -0
  17. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror-CdY86QAP.js +2 -0
  18. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror-CdY86QAP.js.map +1 -0
  19. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_commands-Cg5rCf1I.js +2 -0
  20. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_commands-Cg5rCf1I.js.map +1 -0
  21. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_lang-python-BVX9Mc4s.js +2 -0
  22. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_lang-python-BVX9Mc4s.js.map +1 -0
  23. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_language-BFA8854-.js +2 -0
  24. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_language-BFA8854-.js.map +1 -0
  25. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_state-D1qTXrff.js +2 -0
  26. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_state-D1qTXrff.js.map +1 -0
  27. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_view-CH0UDCz6.js +2 -0
  28. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/codemirror_view-CH0UDCz6.js.map +1 -0
  29. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/core-z7AxuHhC.js +3 -0
  30. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/core-z7AxuHhC.js.map +1 -0
  31. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/core.css +1 -0
  32. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/core.js +2 -0
  33. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/core.js.map +1 -0
  34. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/deprecations-manager-ru7MT5Is.js +2 -0
  35. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/deprecations-manager-ru7MT5Is.js.map +1 -0
  36. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/error-Dno1huoi.js +2 -0
  37. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/error-Dno1huoi.js.map +1 -0
  38. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/index-C02Lyjc0.js +2 -0
  39. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/index-C02Lyjc0.js.map +1 -0
  40. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/mpy-C1lmhZWt.js +2 -0
  41. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/mpy-C1lmhZWt.js.map +1 -0
  42. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-BLNbtGqo.js +2 -0
  43. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-BLNbtGqo.js.map +1 -0
  44. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-editor-eO3NBU8y.js +2 -0
  45. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-editor-eO3NBU8y.js.map +1 -0
  46. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-terminal-hOF-sQ2I.js +2 -0
  47. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/py-terminal-hOF-sQ2I.js.map +1 -0
  48. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/toml-CvAfdf9_.js +3 -0
  49. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/toml-CvAfdf9_.js.map +1 -0
  50. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/toml-DiUM0_qs.js +3 -0
  51. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/toml-DiUM0_qs.js.map +1 -0
  52. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm-BY7uk_OU.js +2 -0
  53. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm-BY7uk_OU.js.map +1 -0
  54. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm-readline-DjufFwfn.js +2 -0
  55. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm-readline-DjufFwfn.js.map +1 -0
  56. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm.css +7 -0
  57. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm_addon-fit--gyF3PcZ.js +2 -0
  58. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm_addon-fit--gyF3PcZ.js.map +1 -0
  59. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm_addon-web-links-Cnej-nJ6.js +2 -0
  60. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/xterm_addon-web-links-Cnej-nJ6.js.map +1 -0
  61. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/zip-BxogYCy6.js +2 -0
  62. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript/zip-BxogYCy6.js.map +1 -0
  63. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript-custom.css +3 -0
  64. reactpy_django-4.0.0/src/reactpy_django/static/reactpy_django/pyscript-hide-debug.css +3 -0
  65. reactpy_django-4.0.0/src/reactpy_django/templates/reactpy/pyscript_component.html +2 -0
  66. reactpy_django-4.0.0/src/reactpy_django/templates/reactpy/pyscript_setup.html +8 -0
  67. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/templatetags/reactpy.py +91 -62
  68. reactpy_django-4.0.0/src/reactpy_django/types.py +81 -0
  69. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/utils.py +192 -16
  70. reactpy_django-4.0.0/src/reactpy_django/websocket/__init__.py +0 -0
  71. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/websocket/consumer.py +17 -8
  72. {reactpy_django-3.8.1 → reactpy_django-4.0.0/src/reactpy_django.egg-info}/PKG-INFO +4 -3
  73. reactpy_django-4.0.0/src/reactpy_django.egg-info/SOURCES.txt +108 -0
  74. reactpy_django-3.8.1/src/reactpy_django/types.py +0 -121
  75. reactpy_django-3.8.1/src/reactpy_django.egg-info/SOURCES.txt +0 -49
  76. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/LICENSE.md +0 -0
  77. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/MANIFEST.in +0 -0
  78. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/pyproject.toml +0 -0
  79. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/setup.cfg +0 -0
  80. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/apps.py +0 -0
  81. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/checks.py +0 -0
  82. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/clean.py +0 -0
  83. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/database.py +0 -0
  84. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/decorators.py +0 -0
  85. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/exceptions.py +0 -0
  86. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/http/__init__.py +0 -0
  87. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/http/urls.py +0 -0
  88. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/http/views.py +0 -0
  89. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/management/__init__.py +0 -0
  90. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/management/commands/__init__.py +0 -0
  91. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0001_initial.py +0 -0
  92. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0002_rename_created_at_componentparams_last_accessed.py +0 -0
  93. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0003_componentsession_delete_componentparams.py +0 -0
  94. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0004_config.py +0 -0
  95. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0005_alter_componentsession_last_accessed.py +0 -0
  96. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/0006_userdatamodel.py +0 -0
  97. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/migrations/__init__.py +0 -0
  98. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/models.py +0 -0
  99. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/py.typed +0 -0
  100. {reactpy_django-3.8.1/src/reactpy_django/templatetags → reactpy_django-4.0.0/src/reactpy_django/pyscript}/__init__.py +0 -0
  101. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/router/__init__.py +0 -0
  102. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/router/converters.py +0 -0
  103. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/router/resolvers.py +0 -0
  104. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/static/reactpy_django/client.js +0 -0
  105. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/templates/reactpy/component.html +0 -0
  106. {reactpy_django-3.8.1/src/reactpy_django/websocket → reactpy_django-4.0.0/src/reactpy_django/templatetags}/__init__.py +0 -0
  107. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django/websocket/paths.py +0 -0
  108. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django.egg-info/dependency_links.txt +0 -0
  109. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django.egg-info/not-zip-safe +0 -0
  110. {reactpy_django-3.8.1 → reactpy_django-4.0.0}/src/reactpy_django.egg-info/requires.txt +0 -0
  111. {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.8.1
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 Django
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/my-template.html`](https://docs.djangoproject.com/en/dev/topics/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 Django
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/my-template.html`](https://docs.djangoproject.com/en/dev/topics/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 checks, components, decorators, hooks, router, types, utils
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__ = "3.8.1"
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.utils import generate_obj_name, import_module, render_view
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
- REACTPY_DEBUG_MODE.set_current(getattr(settings, "DEBUG"))
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
- QueryOptions,
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
- *args: FuncParams.args,
111
- **kwargs: FuncParams.kwargs,
112
- ) -> Query[Inferred]: ...
113
-
114
-
115
- @overload
116
- def use_query(
117
- query: Callable[FuncParams, Awaitable[Inferred]] | Callable[FuncParams, Inferred],
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
- options: An optional `QueryOptions` object that can modify how the query is executed.
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
- Keyword Args:
133
- **kwargs: Keyword arguments to pass into `query`."""
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(*query_args, **query_kwargs)
153
+ new_data = await query(**kwargs)
157
154
  else:
158
155
  new_data = await database_sync_to_async(
159
- query,
160
- thread_sensitive=query_options.thread_sensitive,
161
- )(*query_args, **query_kwargs)
156
+ query, thread_sensitive=thread_sensitive
157
+ )(**kwargs)
162
158
 
163
159
  # Run the postprocessor
164
- if query_options.postprocessor:
165
- if asyncio.iscoroutinefunction(query_options.postprocessor):
166
- new_data = await query_options.postprocessor(
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
- query_options.postprocessor,
172
- thread_sensitive=query_options.thread_sensitive,
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(f"Failed to execute query: {generate_obj_name(query)}")
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
- # The query's user API
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=mutation_options.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
- f"Failed to execute mutation: {generate_obj_name(mutation)}"
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
- # The mutation's user API
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
- user=user,
361
- default_data=default_data,
362
- save_default_data=save_default_data,
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:
@@ -0,0 +1,3 @@
1
+ from reactpy.core.vdom import make_vdom_constructor
2
+
3
+ pyscript = make_vdom_constructor("py-script")
@@ -28,10 +28,10 @@ class Command(BaseCommand):
28
28
  parser.add_argument(
29
29
  "--sessions",
30
30
  action="store_true",
31
- help="Configure this clean to only clean session data (and other configured cleaning options).",
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="Configure this clean to only clean user data (and other configured cleaning options).",
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))