reactpy 2.0.0b4__py3-none-any.whl → 2.0.0b6__py3-none-any.whl

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 (55) hide show
  1. reactpy/__init__.py +3 -2
  2. reactpy/_console/rewrite_props.py +2 -2
  3. reactpy/_html.py +11 -9
  4. reactpy/_option.py +2 -1
  5. reactpy/config.py +2 -2
  6. reactpy/core/_life_cycle_hook.py +12 -10
  7. reactpy/core/_thread_local.py +2 -1
  8. reactpy/core/component.py +4 -38
  9. reactpy/core/events.py +61 -36
  10. reactpy/core/hooks.py +25 -35
  11. reactpy/core/layout.py +193 -201
  12. reactpy/core/serve.py +17 -22
  13. reactpy/core/vdom.py +9 -12
  14. reactpy/executors/asgi/__init__.py +9 -4
  15. reactpy/executors/asgi/middleware.py +1 -2
  16. reactpy/executors/asgi/pyscript.py +3 -7
  17. reactpy/executors/asgi/standalone.py +4 -6
  18. reactpy/executors/asgi/types.py +2 -2
  19. reactpy/pyscript/components.py +3 -3
  20. reactpy/pyscript/utils.py +49 -46
  21. reactpy/reactjs/__init__.py +353 -0
  22. reactpy/reactjs/module.py +203 -0
  23. reactpy/reactjs/types.py +7 -0
  24. reactpy/reactjs/utils.py +183 -0
  25. reactpy/static/index-h31022cd.js +5 -0
  26. reactpy/static/index-h31022cd.js.map +11 -0
  27. reactpy/static/index-sbddj6ms.js +5 -0
  28. reactpy/static/index-sbddj6ms.js.map +10 -0
  29. reactpy/static/index-y71bxs88.js +5 -0
  30. reactpy/static/index-y71bxs88.js.map +10 -0
  31. reactpy/static/index.js +2 -2
  32. reactpy/static/index.js.map +6 -10
  33. reactpy/static/react-dom.js +4 -0
  34. reactpy/static/react-dom.js.map +11 -0
  35. reactpy/static/react-jsx-runtime.js +4 -0
  36. reactpy/static/react-jsx-runtime.js.map +9 -0
  37. reactpy/static/react.js +4 -0
  38. reactpy/static/react.js.map +10 -0
  39. reactpy/testing/backend.py +6 -5
  40. reactpy/testing/common.py +3 -5
  41. reactpy/testing/display.py +2 -1
  42. reactpy/testing/logs.py +1 -1
  43. reactpy/transforms.py +2 -2
  44. reactpy/types.py +117 -58
  45. reactpy/utils.py +8 -8
  46. reactpy/web/__init__.py +0 -6
  47. reactpy/web/module.py +37 -470
  48. reactpy/web/utils.py +2 -158
  49. reactpy/widgets.py +2 -2
  50. {reactpy-2.0.0b4.dist-info → reactpy-2.0.0b6.dist-info}/METADATA +4 -7
  51. {reactpy-2.0.0b4.dist-info → reactpy-2.0.0b6.dist-info}/RECORD +54 -39
  52. reactpy/web/templates/react.js +0 -61
  53. {reactpy-2.0.0b4.dist-info → reactpy-2.0.0b6.dist-info}/WHEEL +0 -0
  54. {reactpy-2.0.0b4.dist-info → reactpy-2.0.0b6.dist-info}/entry_points.txt +0 -0
  55. {reactpy-2.0.0b4.dist-info → reactpy-2.0.0b6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,353 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ from pathlib import Path
5
+ from typing import Any, overload
6
+
7
+ from reactpy.reactjs.module import (
8
+ file_to_module,
9
+ import_reactjs,
10
+ module_to_vdom,
11
+ string_to_module,
12
+ url_to_module,
13
+ )
14
+ from reactpy.reactjs.types import (
15
+ NAME_SOURCE,
16
+ URL_SOURCE,
17
+ )
18
+ from reactpy.types import JavaScriptModule, VdomConstructor
19
+
20
+ __all__ = [
21
+ "NAME_SOURCE",
22
+ "URL_SOURCE",
23
+ "component_from_file",
24
+ "component_from_npm",
25
+ "component_from_string",
26
+ "component_from_url",
27
+ "import_reactjs",
28
+ ]
29
+
30
+ _URL_JS_MODULE_CACHE: dict[str, JavaScriptModule] = {}
31
+ _FILE_JS_MODULE_CACHE: dict[str, JavaScriptModule] = {}
32
+ _STRING_JS_MODULE_CACHE: dict[str, JavaScriptModule] = {}
33
+
34
+
35
+ @overload
36
+ def component_from_url(
37
+ url: str,
38
+ import_names: str,
39
+ resolve_imports: bool = ...,
40
+ resolve_imports_depth: int = ...,
41
+ fallback: Any | None = ...,
42
+ unmount_before_update: bool = ...,
43
+ allow_children: bool = ...,
44
+ ) -> VdomConstructor: ...
45
+
46
+
47
+ @overload
48
+ def component_from_url(
49
+ url: str,
50
+ import_names: list[str] | tuple[str, ...],
51
+ resolve_imports: bool = ...,
52
+ resolve_imports_depth: int = ...,
53
+ fallback: Any | None = ...,
54
+ unmount_before_update: bool = ...,
55
+ allow_children: bool = ...,
56
+ ) -> list[VdomConstructor]: ...
57
+
58
+
59
+ def component_from_url(
60
+ url: str,
61
+ import_names: str | list[str] | tuple[str, ...],
62
+ resolve_imports: bool = False,
63
+ resolve_imports_depth: int = 5,
64
+ fallback: Any | None = None,
65
+ unmount_before_update: bool = False,
66
+ allow_children: bool = True,
67
+ ) -> VdomConstructor | list[VdomConstructor]:
68
+ """Import a component from a URL.
69
+
70
+ Parameters:
71
+ url:
72
+ The URL to import the component from.
73
+ import_names:
74
+ One or more component names to import. If given as a string, a single component
75
+ will be returned. If a list is given, then a list of components will be
76
+ returned.
77
+ resolve_imports:
78
+ Whether to try and find all the named imports of this module.
79
+ resolve_imports_depth:
80
+ How deeply to search for those imports.
81
+ fallback:
82
+ What to temporarily display while the module is being loaded.
83
+ unmount_before_update:
84
+ Cause the component to be unmounted before each update. This option should
85
+ only be used if the imported package fails to re-render when props change.
86
+ Using this option has negative performance consequences since all DOM
87
+ elements must be changed on each render. See :issue:`461` for more info.
88
+ allow_children:
89
+ Whether or not these components can have children.
90
+ """
91
+ key = f"{url}{resolve_imports}{resolve_imports_depth}{unmount_before_update}"
92
+ if key in _URL_JS_MODULE_CACHE:
93
+ module = _URL_JS_MODULE_CACHE[key]
94
+ else:
95
+ module = url_to_module(
96
+ url,
97
+ fallback=fallback,
98
+ resolve_imports=resolve_imports,
99
+ resolve_imports_depth=resolve_imports_depth,
100
+ unmount_before_update=unmount_before_update,
101
+ )
102
+ _URL_JS_MODULE_CACHE[key] = module
103
+ return module_to_vdom(module, import_names, fallback, allow_children)
104
+
105
+
106
+ @overload
107
+ def component_from_npm(
108
+ package: str,
109
+ import_names: str,
110
+ resolve_imports: bool = ...,
111
+ resolve_imports_depth: int = ...,
112
+ version: str = "latest",
113
+ cdn: str = "https://esm.sh",
114
+ fallback: Any | None = ...,
115
+ unmount_before_update: bool = ...,
116
+ allow_children: bool = ...,
117
+ ) -> VdomConstructor: ...
118
+
119
+
120
+ @overload
121
+ def component_from_npm(
122
+ package: str,
123
+ import_names: list[str] | tuple[str, ...],
124
+ resolve_imports: bool = ...,
125
+ resolve_imports_depth: int = ...,
126
+ version: str = "latest",
127
+ cdn: str = "https://esm.sh",
128
+ fallback: Any | None = ...,
129
+ unmount_before_update: bool = ...,
130
+ allow_children: bool = ...,
131
+ ) -> list[VdomConstructor]: ...
132
+
133
+
134
+ def component_from_npm(
135
+ package: str,
136
+ import_names: str | list[str] | tuple[str, ...],
137
+ resolve_imports: bool = False,
138
+ resolve_imports_depth: int = 5,
139
+ version: str = "latest",
140
+ cdn: str = "https://esm.sh",
141
+ fallback: Any | None = None,
142
+ unmount_before_update: bool = False,
143
+ allow_children: bool = True,
144
+ ) -> VdomConstructor | list[VdomConstructor]:
145
+ """Import a component from an NPM package.
146
+
147
+ Is is mandatory to load `reactpy.reactjs.import_reactjs()` on your page before using this
148
+ function. It is recommended to put this within your HTML <head> content.
149
+
150
+ Parameters:
151
+ package:
152
+ The name of the NPM package.
153
+ import_names:
154
+ One or more component names to import. If given as a string, a single component
155
+ will be returned. If a list is given, then a list of components will be
156
+ returned.
157
+ resolve_imports:
158
+ Whether to try and find all the named imports of this module.
159
+ resolve_imports_depth:
160
+ How deeply to search for those imports.
161
+ version:
162
+ The version of the package to use. Defaults to "latest".
163
+ cdn:
164
+ The CDN to use. Defaults to "https://esm.sh".
165
+ fallback:
166
+ What to temporarily display while the module is being loaded.
167
+ unmount_before_update:
168
+ Cause the component to be unmounted before each update. This option should
169
+ only be used if the imported package fails to re-render when props change.
170
+ Using this option has negative performance consequences since all DOM
171
+ elements must be changed on each render. See :issue:`461` for more info.
172
+ allow_children:
173
+ Whether or not these components can have children.
174
+ """
175
+ url = f"{cdn}/{package}@{version}"
176
+
177
+ if "esm.sh" in cdn:
178
+ if "?" in url:
179
+ url += "&external=react,react-dom"
180
+ else:
181
+ url += "?external=react,react-dom"
182
+
183
+ return component_from_url(
184
+ url,
185
+ import_names,
186
+ fallback=fallback,
187
+ resolve_imports=resolve_imports,
188
+ resolve_imports_depth=resolve_imports_depth,
189
+ unmount_before_update=unmount_before_update,
190
+ allow_children=allow_children,
191
+ )
192
+
193
+
194
+ @overload
195
+ def component_from_file(
196
+ file: str | Path,
197
+ import_names: str,
198
+ resolve_imports: bool = ...,
199
+ resolve_imports_depth: int = ...,
200
+ name: str = "",
201
+ fallback: Any | None = ...,
202
+ unmount_before_update: bool = ...,
203
+ symlink: bool = ...,
204
+ allow_children: bool = ...,
205
+ ) -> VdomConstructor: ...
206
+
207
+
208
+ @overload
209
+ def component_from_file(
210
+ file: str | Path,
211
+ import_names: list[str] | tuple[str, ...],
212
+ resolve_imports: bool = ...,
213
+ resolve_imports_depth: int = ...,
214
+ name: str = "",
215
+ fallback: Any | None = ...,
216
+ unmount_before_update: bool = ...,
217
+ symlink: bool = ...,
218
+ allow_children: bool = ...,
219
+ ) -> list[VdomConstructor]: ...
220
+
221
+
222
+ def component_from_file(
223
+ file: str | Path,
224
+ import_names: str | list[str] | tuple[str, ...],
225
+ resolve_imports: bool = False,
226
+ resolve_imports_depth: int = 5,
227
+ name: str = "",
228
+ fallback: Any | None = None,
229
+ unmount_before_update: bool = False,
230
+ symlink: bool = False,
231
+ allow_children: bool = True,
232
+ ) -> VdomConstructor | list[VdomConstructor]:
233
+ """Import a component from a file.
234
+
235
+ Parameters:
236
+ file:
237
+ The file from which the content of the web module will be created.
238
+ import_names:
239
+ One or more component names to import. If given as a string, a single component
240
+ will be returned. If a list is given, then a list of components will be
241
+ returned.
242
+ resolve_imports:
243
+ Whether to try and find all the named imports of this module.
244
+ resolve_imports_depth:
245
+ How deeply to search for those imports.
246
+ name:
247
+ The human-readable name of the ReactJS package
248
+ fallback:
249
+ What to temporarily display while the module is being loaded.
250
+ unmount_before_update:
251
+ Cause the component to be unmounted before each update. This option should
252
+ only be used if the imported package fails to re-render when props change.
253
+ Using this option has negative performance consequences since all DOM
254
+ elements must be changed on each render. See :issue:`461` for more info.
255
+ symlink:
256
+ Whether the web module should be saved as a symlink to the given ``file``.
257
+ allow_children:
258
+ Whether or not these components can have children.
259
+ """
260
+ name = name or hashlib.sha256(str(file).encode()).hexdigest()[:10]
261
+ key = f"{name}{resolve_imports}{resolve_imports_depth}{unmount_before_update}"
262
+ if key in _FILE_JS_MODULE_CACHE:
263
+ module = _FILE_JS_MODULE_CACHE[key]
264
+ else:
265
+ module = file_to_module(
266
+ name,
267
+ file,
268
+ fallback=fallback,
269
+ resolve_imports=resolve_imports,
270
+ resolve_imports_depth=resolve_imports_depth,
271
+ unmount_before_update=unmount_before_update,
272
+ symlink=symlink,
273
+ )
274
+ _FILE_JS_MODULE_CACHE[key] = module
275
+ return module_to_vdom(module, import_names, fallback, allow_children)
276
+
277
+
278
+ @overload
279
+ def component_from_string(
280
+ content: str,
281
+ import_names: str,
282
+ resolve_imports: bool = ...,
283
+ resolve_imports_depth: int = ...,
284
+ name: str = "",
285
+ fallback: Any | None = ...,
286
+ unmount_before_update: bool = ...,
287
+ allow_children: bool = ...,
288
+ ) -> VdomConstructor: ...
289
+
290
+
291
+ @overload
292
+ def component_from_string(
293
+ content: str,
294
+ import_names: list[str] | tuple[str, ...],
295
+ resolve_imports: bool = ...,
296
+ resolve_imports_depth: int = ...,
297
+ name: str = "",
298
+ fallback: Any | None = ...,
299
+ unmount_before_update: bool = ...,
300
+ allow_children: bool = ...,
301
+ ) -> list[VdomConstructor]: ...
302
+
303
+
304
+ def component_from_string(
305
+ content: str,
306
+ import_names: str | list[str] | tuple[str, ...],
307
+ resolve_imports: bool = False,
308
+ resolve_imports_depth: int = 5,
309
+ name: str = "",
310
+ fallback: Any | None = None,
311
+ unmount_before_update: bool = False,
312
+ allow_children: bool = True,
313
+ ) -> VdomConstructor | list[VdomConstructor]:
314
+ """Import a component from a string.
315
+
316
+ Parameters:
317
+ content:
318
+ The contents of the web module
319
+ import_names:
320
+ One or more component names to import. If given as a string, a single component
321
+ will be returned. If a list is given, then a list of components will be
322
+ returned.
323
+ resolve_imports:
324
+ Whether to try and find all the named imports of this module.
325
+ resolve_imports_depth:
326
+ How deeply to search for those imports.
327
+ name:
328
+ The human-readable name of the ReactJS package
329
+ fallback:
330
+ What to temporarily display while the module is being loaded.
331
+ unmount_before_update:
332
+ Cause the component to be unmounted before each update. This option should
333
+ only be used if the imported package fails to re-render when props change.
334
+ Using this option has negative performance consequences since all DOM
335
+ elements must be changed on each render. See :issue:`461` for more info.
336
+ allow_children:
337
+ Whether or not these components can have children.
338
+ """
339
+ name = name or hashlib.sha256(content.encode()).hexdigest()[:10]
340
+ key = f"{name}{resolve_imports}{resolve_imports_depth}{unmount_before_update}"
341
+ if key in _STRING_JS_MODULE_CACHE:
342
+ module = _STRING_JS_MODULE_CACHE[key]
343
+ else:
344
+ module = string_to_module(
345
+ name,
346
+ content,
347
+ fallback=fallback,
348
+ resolve_imports=resolve_imports,
349
+ resolve_imports_depth=resolve_imports_depth,
350
+ unmount_before_update=unmount_before_update,
351
+ )
352
+ _STRING_JS_MODULE_CACHE[key] = module
353
+ return module_to_vdom(module, import_names, fallback, allow_children)
@@ -0,0 +1,203 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from reactpy import config
8
+ from reactpy.config import REACTPY_WEB_MODULES_DIR
9
+ from reactpy.core.vdom import Vdom
10
+ from reactpy.reactjs.types import NAME_SOURCE, URL_SOURCE
11
+ from reactpy.reactjs.utils import (
12
+ are_files_identical,
13
+ copy_file,
14
+ module_name_suffix,
15
+ resolve_from_module_file,
16
+ resolve_from_module_url,
17
+ )
18
+ from reactpy.types import ImportSourceDict, JavaScriptModule, VdomConstructor
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def url_to_module(
24
+ url: str,
25
+ fallback: Any | None = None,
26
+ resolve_imports: bool = True,
27
+ resolve_imports_depth: int = 5,
28
+ unmount_before_update: bool = False,
29
+ ) -> JavaScriptModule:
30
+ return JavaScriptModule(
31
+ source=url,
32
+ source_type=URL_SOURCE,
33
+ default_fallback=fallback,
34
+ file=None,
35
+ import_names=(
36
+ resolve_from_module_url(url, resolve_imports_depth)
37
+ if resolve_imports
38
+ else None
39
+ ),
40
+ unmount_before_update=unmount_before_update,
41
+ )
42
+
43
+
44
+ def file_to_module(
45
+ name: str,
46
+ file: str | Path,
47
+ fallback: Any | None = None,
48
+ resolve_imports: bool = True,
49
+ resolve_imports_depth: int = 5,
50
+ unmount_before_update: bool = False,
51
+ symlink: bool = False,
52
+ ) -> JavaScriptModule:
53
+ name += module_name_suffix(name)
54
+
55
+ source_file = Path(file).resolve()
56
+ target_file = get_module_path(name)
57
+ if not source_file.exists():
58
+ msg = f"Source file does not exist: {source_file}"
59
+ raise FileNotFoundError(msg)
60
+
61
+ if not target_file.exists():
62
+ copy_file(target_file, source_file, symlink)
63
+ elif not are_files_identical(source_file, target_file):
64
+ logger.info(
65
+ f"Existing web module {name!r} will "
66
+ f"be replaced with {target_file.resolve()}"
67
+ )
68
+ target_file.unlink()
69
+ copy_file(target_file, source_file, symlink)
70
+
71
+ return JavaScriptModule(
72
+ source=name,
73
+ source_type=NAME_SOURCE,
74
+ default_fallback=fallback,
75
+ file=target_file,
76
+ import_names=(
77
+ resolve_from_module_file(source_file, resolve_imports_depth)
78
+ if resolve_imports
79
+ else None
80
+ ),
81
+ unmount_before_update=unmount_before_update,
82
+ )
83
+
84
+
85
+ def string_to_module(
86
+ name: str,
87
+ content: str,
88
+ fallback: Any | None = None,
89
+ resolve_imports: bool = True,
90
+ resolve_imports_depth: int = 5,
91
+ unmount_before_update: bool = False,
92
+ ) -> JavaScriptModule:
93
+ name += module_name_suffix(name)
94
+
95
+ target_file = get_module_path(name)
96
+
97
+ if target_file.exists() and target_file.read_text(encoding="utf-8") != content:
98
+ logger.info(
99
+ f"Existing web module {name!r} will "
100
+ f"be replaced with {target_file.resolve()}"
101
+ )
102
+ target_file.unlink()
103
+
104
+ target_file.parent.mkdir(parents=True, exist_ok=True)
105
+ target_file.write_text(content)
106
+
107
+ return JavaScriptModule(
108
+ source=name,
109
+ source_type=NAME_SOURCE,
110
+ default_fallback=fallback,
111
+ file=target_file,
112
+ import_names=(
113
+ resolve_from_module_file(target_file, resolve_imports_depth)
114
+ if resolve_imports
115
+ else None
116
+ ),
117
+ unmount_before_update=unmount_before_update,
118
+ )
119
+
120
+
121
+ def module_to_vdom(
122
+ web_module: JavaScriptModule,
123
+ import_names: str | list[str] | tuple[str, ...],
124
+ fallback: Any | None = None,
125
+ allow_children: bool = True,
126
+ ) -> VdomConstructor | list[VdomConstructor]:
127
+ """Return one or more VDOM constructors from a :class:`JavaScriptModule`
128
+
129
+ Parameters:
130
+ import_names:
131
+ One or more names to import. If given as a string, a single component
132
+ will be returned. If a list is given, then a list of components will be
133
+ returned.
134
+ fallback:
135
+ What to temporarily display while the module is being loaded.
136
+ allow_children:
137
+ Whether or not these components can have children.
138
+ """
139
+ if isinstance(import_names, str):
140
+ if (
141
+ web_module.import_names is not None
142
+ and import_names.split(".")[0] not in web_module.import_names
143
+ ):
144
+ msg = f"{web_module.source!r} does not contain {import_names!r}"
145
+ raise ValueError(msg)
146
+ return make_module(web_module, import_names, fallback, allow_children)
147
+ else:
148
+ if web_module.import_names is not None:
149
+ missing = sorted(
150
+ {e.split(".")[0] for e in import_names}.difference(
151
+ web_module.import_names
152
+ )
153
+ )
154
+ if missing:
155
+ msg = f"{web_module.source!r} does not contain {missing!r}"
156
+ raise ValueError(msg)
157
+ return [
158
+ make_module(web_module, name, fallback, allow_children)
159
+ for name in import_names
160
+ ]
161
+
162
+
163
+ def make_module(
164
+ web_module: JavaScriptModule,
165
+ name: str,
166
+ fallback: Any | None,
167
+ allow_children: bool,
168
+ ) -> VdomConstructor:
169
+ return Vdom(
170
+ name,
171
+ allow_children=allow_children,
172
+ import_source=ImportSourceDict(
173
+ source=web_module.source,
174
+ sourceType=web_module.source_type,
175
+ fallback=(fallback or web_module.default_fallback),
176
+ unmountBeforeUpdate=web_module.unmount_before_update,
177
+ ),
178
+ )
179
+
180
+
181
+ def get_module_path(name: str) -> Path:
182
+ directory = REACTPY_WEB_MODULES_DIR.current
183
+ path = directory.joinpath(*name.split("/"))
184
+ return path.with_suffix(path.suffix)
185
+
186
+
187
+ def import_reactjs():
188
+ from reactpy import html
189
+
190
+ base_url = config.REACTPY_PATH_PREFIX.current.strip("/")
191
+ return html.script(
192
+ {"type": "importmap"},
193
+ f"""
194
+ {{
195
+ "imports": {{
196
+ "react": "/{base_url}/static/react.js",
197
+ "react-dom": "/{base_url}/static/react-dom.js",
198
+ "react-dom/client": "/{base_url}/static/react-dom.js",
199
+ "react/jsx-runtime": "/{base_url}/static/react-jsx-runtime.js"
200
+ }}
201
+ }}
202
+ """,
203
+ )
@@ -0,0 +1,7 @@
1
+ from reactpy.types import SourceType
2
+
3
+ NAME_SOURCE = SourceType("NAME")
4
+ """A named source - usually a Javascript package name"""
5
+
6
+ URL_SOURCE = SourceType("URL")
7
+ """A source loaded from a URL, usually a CDN"""