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
reactpy/core/vdom.py CHANGED
@@ -3,10 +3,9 @@ from __future__ import annotations
3
3
 
4
4
  import json
5
5
  import re
6
- from collections.abc import Mapping, Sequence
6
+ from collections.abc import Callable, Mapping, Sequence
7
7
  from typing import (
8
8
  Any,
9
- Callable,
10
9
  cast,
11
10
  overload,
12
11
  )
@@ -18,11 +17,11 @@ from reactpy.config import REACTPY_CHECK_JSON_ATTRS, REACTPY_DEBUG
18
17
  from reactpy.core._f_back import f_module_name
19
18
  from reactpy.core.events import EventHandler, to_event_handler_function
20
19
  from reactpy.types import (
21
- ComponentType,
20
+ BaseEventHandler,
21
+ Component,
22
22
  CustomVdomConstructor,
23
23
  EllipsisRepr,
24
24
  EventHandlerDict,
25
- EventHandlerType,
26
25
  ImportSourceDict,
27
26
  InlineJavaScript,
28
27
  InlineJavaScriptDict,
@@ -42,7 +41,6 @@ VDOM_JSON_SCHEMA = {
42
41
  "type": "object",
43
42
  "properties": {
44
43
  "tagName": {"type": "string"},
45
- "key": {"type": ["string", "number", "null"]},
46
44
  "error": {"type": "string"},
47
45
  "children": {"$ref": "#/definitions/elementChildren"},
48
46
  "attributes": {"type": "object"},
@@ -171,7 +169,6 @@ class Vdom:
171
169
  ) -> VdomDict:
172
170
  """The entry point for the VDOM API, for example reactpy.html(<WE_ARE_HERE>)."""
173
171
  attributes, children = separate_attributes_and_children(attributes_and_children)
174
- key = attributes.get("key", None)
175
172
  attributes, event_handlers, inline_javascript = (
176
173
  separate_attributes_handlers_and_inline_javascript(attributes)
177
174
  )
@@ -181,7 +178,6 @@ class Vdom:
181
178
  # Run custom constructor, if defined
182
179
  if self.custom_constructor:
183
180
  result = self.custom_constructor(
184
- key=key,
185
181
  children=children,
186
182
  attributes=attributes,
187
183
  event_handlers=event_handlers,
@@ -190,7 +186,6 @@ class Vdom:
190
186
  # Otherwise, use the default constructor
191
187
  else:
192
188
  result = {
193
- **({"key": key} if key is not None else {}),
194
189
  **({"children": children} if children else {}),
195
190
  **({"attributes": attributes} if attributes else {}),
196
191
  **({"eventHandlers": event_handlers} if event_handlers else {}),
@@ -232,13 +227,13 @@ def separate_attributes_handlers_and_inline_javascript(
232
227
  attributes: Mapping[str, Any],
233
228
  ) -> tuple[VdomAttributes, EventHandlerDict, InlineJavaScriptDict]:
234
229
  _attributes: VdomAttributes = {}
235
- _event_handlers: dict[str, EventHandlerType] = {}
230
+ _event_handlers: dict[str, BaseEventHandler] = {}
236
231
  _inline_javascript: dict[str, InlineJavaScript] = {}
237
232
 
238
233
  for k, v in attributes.items():
239
234
  if callable(v):
240
235
  _event_handlers[k] = EventHandler(to_event_handler_function(v))
241
- elif isinstance(v, EventHandler):
236
+ elif isinstance(v, BaseEventHandler):
242
237
  _event_handlers[k] = v
243
238
  elif EVENT_ATTRIBUTE_PATTERN.match(k) and isinstance(v, str):
244
239
  _inline_javascript[k] = InlineJavaScript(v)
@@ -276,9 +271,11 @@ def _validate_child_key_integrity(value: Any) -> None:
276
271
  )
277
272
  else:
278
273
  for child in value:
279
- if isinstance(child, ComponentType) and child.key is None:
274
+ if isinstance(child, Component) and child.key is None:
280
275
  warn(f"Key not specified for child in list {child}", UserWarning)
281
- elif isinstance(child, Mapping) and "key" not in child:
276
+ elif isinstance(child, Mapping) and "key" not in child.get(
277
+ "attributes", {}
278
+ ):
282
279
  # remove 'children' to reduce log spam
283
280
  child_copy = {**child, "children": EllipsisRepr()}
284
281
  warn(f"Key not specified for child in list {child_copy}", UserWarning)
@@ -1,5 +1,10 @@
1
- from reactpy.executors.asgi.middleware import ReactPyMiddleware
2
- from reactpy.executors.asgi.pyscript import ReactPyCsr
3
- from reactpy.executors.asgi.standalone import ReactPy
1
+ try:
2
+ from reactpy.executors.asgi.middleware import ReactPyMiddleware
3
+ from reactpy.executors.asgi.pyscript import ReactPyCsr
4
+ from reactpy.executors.asgi.standalone import ReactPy
4
5
 
5
- __all__ = ["ReactPy", "ReactPyCsr", "ReactPyMiddleware"]
6
+ __all__ = ["ReactPy", "ReactPyCsr", "ReactPyMiddleware"]
7
+ except ModuleNotFoundError as e:
8
+ raise ModuleNotFoundError(
9
+ "ASGI executors require the 'reactpy[asgi]' extra to be installed."
10
+ ) from e
@@ -8,13 +8,12 @@ import urllib.parse
8
8
  from collections.abc import Iterable
9
9
  from dataclasses import dataclass
10
10
  from pathlib import Path
11
- from typing import Any
11
+ from typing import Any, Unpack
12
12
 
13
13
  import orjson
14
14
  from asgi_tools import ResponseText, ResponseWebSocket
15
15
  from asgiref.compatibility import guarantee_single_callable
16
16
  from servestatic import ServeStaticASGI
17
- from typing_extensions import Unpack
18
17
 
19
18
  from reactpy import config
20
19
  from reactpy.core.hooks import ConnectionContext
@@ -4,12 +4,10 @@ import hashlib
4
4
  import re
5
5
  from collections.abc import Sequence
6
6
  from dataclasses import dataclass
7
- from datetime import datetime, timezone
7
+ from datetime import UTC, datetime
8
8
  from email.utils import formatdate
9
9
  from pathlib import Path
10
- from typing import Any
11
-
12
- from typing_extensions import Unpack
10
+ from typing import Any, Unpack
13
11
 
14
12
  from reactpy import html
15
13
  from reactpy.executors.asgi.middleware import ReactPyMiddleware
@@ -118,6 +116,4 @@ class ReactPyPyscriptApp(ReactPyApp):
118
116
  "</html>"
119
117
  )
120
118
  self._etag = f'"{hashlib.md5(self._index_html.encode(), usedforsecurity=False).hexdigest()}"'
121
- self._last_modified = formatdate(
122
- datetime.now(tz=timezone.utc).timestamp(), usegmt=True
123
- )
119
+ self._last_modified = formatdate(datetime.now(tz=UTC).timestamp(), usegmt=True)
@@ -2,14 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  import hashlib
4
4
  import re
5
+ from collections.abc import Callable
5
6
  from dataclasses import dataclass
6
- from datetime import datetime, timezone
7
+ from datetime import UTC, datetime
7
8
  from email.utils import formatdate
8
9
  from logging import getLogger
9
- from typing import Callable, Literal, cast, overload
10
+ from typing import Literal, Unpack, cast, overload
10
11
 
11
12
  from asgi_tools import ResponseHTML
12
- from typing_extensions import Unpack
13
13
 
14
14
  from reactpy import html
15
15
  from reactpy.executors.asgi.middleware import ReactPyMiddleware
@@ -239,6 +239,4 @@ class ReactPyApp:
239
239
  "</html>"
240
240
  )
241
241
  self._etag = f'"{hashlib.md5(self._index_html.encode(), usedforsecurity=False).hexdigest()}"'
242
- self._last_modified = formatdate(
243
- datetime.now(tz=timezone.utc).timestamp(), usegmt=True
244
- )
242
+ self._last_modified = formatdate(datetime.now(tz=UTC).timestamp(), usegmt=True)
@@ -2,8 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Awaitable, MutableMapping
6
- from typing import Any, Callable, Protocol
5
+ from collections.abc import Awaitable, Callable, MutableMapping
6
+ from typing import Any, Protocol
7
7
 
8
8
  from asgiref import typing as asgi_types
9
9
 
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
5
5
 
6
6
  from reactpy import component, hooks
7
7
  from reactpy.pyscript.utils import pyscript_component_html
8
- from reactpy.types import ComponentType, Key
8
+ from reactpy.types import Component, Key
9
9
  from reactpy.utils import string_to_reactpy
10
10
 
11
11
  if TYPE_CHECKING:
@@ -39,10 +39,10 @@ def _pyscript_component(
39
39
 
40
40
  def pyscript_component(
41
41
  *file_paths: str | Path,
42
- initial: str | VdomDict | ComponentType = "",
42
+ initial: str | VdomDict | Component = "",
43
43
  root: str = "root",
44
44
  key: Key | None = None,
45
- ) -> ComponentType:
45
+ ) -> Component:
46
46
  """
47
47
  Args:
48
48
  file_paths: File path to your client-side ReactPy component. If multiple paths are \
reactpy/pyscript/utils.py CHANGED
@@ -1,4 +1,4 @@
1
- # ruff: noqa: S603, S607
1
+ # ruff: noqa: S607
2
2
  from __future__ import annotations
3
3
 
4
4
  import functools
@@ -11,6 +11,7 @@ from glob import glob
11
11
  from logging import getLogger
12
12
  from pathlib import Path
13
13
  from typing import TYPE_CHECKING, Any
14
+ from urllib import request
14
15
  from uuid import uuid4
15
16
 
16
17
  import reactpy
@@ -110,8 +111,6 @@ def extend_pyscript_config(
110
111
  extra_js: dict[str, str] | str,
111
112
  config: dict[str, Any] | str,
112
113
  ) -> str:
113
- import orjson
114
-
115
114
  # Extends ReactPy's default PyScript config with user provided values.
116
115
  pyscript_config: dict[str, Any] = {
117
116
  "packages": [reactpy_version_string(), "jsonpointer==3.*", "ssl"],
@@ -120,10 +119,13 @@ def extend_pyscript_config(
120
119
  f"{REACTPY_PATH_PREFIX.current}static/morphdom/morphdom-esm.js": "morphdom"
121
120
  }
122
121
  },
123
- "packages_cache": "never",
124
122
  }
125
123
  pyscript_config["packages"].extend(extra_py)
126
124
 
125
+ # FIXME: https://github.com/pyscript/pyscript/issues/2282
126
+ if any(pkg.endswith(".whl") for pkg in pyscript_config["packages"]): # nocov
127
+ pyscript_config["packages_cache"] = "never"
128
+
127
129
  # Extend the JavaScript dependency list
128
130
  if extra_js and isinstance(extra_js, str):
129
131
  pyscript_config["js_modules"]["main"].update(json.loads(extra_js))
@@ -135,7 +137,7 @@ def extend_pyscript_config(
135
137
  pyscript_config.update(json.loads(config))
136
138
  elif config and isinstance(config, dict):
137
139
  pyscript_config.update(config)
138
- return orjson.dumps(pyscript_config).decode("utf-8")
140
+ return json.dumps(pyscript_config)
139
141
 
140
142
 
141
143
  def reactpy_version_string() -> str: # nocov
@@ -144,10 +146,10 @@ def reactpy_version_string() -> str: # nocov
144
146
  local_version = reactpy.__version__
145
147
 
146
148
  # Get a list of all versions via `pip index versions`
147
- result = cached_pip_index_versions("reactpy")
149
+ result = get_reactpy_versions()
148
150
 
149
151
  # Check if the command failed
150
- if result.returncode != 0:
152
+ if not result:
151
153
  _logger.warning(
152
154
  "Failed to verify what versions of ReactPy exist on PyPi. "
153
155
  "PyScript functionality may not work as expected.",
@@ -155,16 +157,8 @@ def reactpy_version_string() -> str: # nocov
155
157
  return f"reactpy=={local_version}"
156
158
 
157
159
  # Have `pip` tell us what versions are available
158
- available_version_symbol = "Available versions: "
159
- latest_version_symbol = "LATEST: "
160
- known_versions: list[str] = []
161
- latest_version: str = ""
162
- for line in result.stdout.splitlines():
163
- if line.startswith(available_version_symbol):
164
- known_versions.extend(line[len(available_version_symbol) :].split(", "))
165
- elif latest_version_symbol in line:
166
- symbol_postion = line.index(latest_version_symbol)
167
- latest_version = line[symbol_postion + len(latest_version_symbol) :].strip()
160
+ known_versions: list[str] = result.get("versions", [])
161
+ latest_version: str = result.get("latest", "")
168
162
 
169
163
  # Return early if the version is available on PyPi and we're not in a CI environment
170
164
  if local_version in known_versions and not GITHUB_ACTIONS:
@@ -173,8 +167,8 @@ def reactpy_version_string() -> str: # nocov
173
167
  # We are now determining an alternative method of installing ReactPy for PyScript
174
168
  if not GITHUB_ACTIONS:
175
169
  _logger.warning(
176
- "Your current version of ReactPy isn't available on PyPi. Since a packaged version "
177
- "of ReactPy is required for PyScript, we are attempting to find an alternative method..."
170
+ "Your ReactPy version isn't available on PyPi. "
171
+ "Attempting to find an alternative installation method for PyScript...",
178
172
  )
179
173
 
180
174
  # Build a local wheel for ReactPy, if needed
@@ -189,42 +183,51 @@ def reactpy_version_string() -> str: # nocov
189
183
  check=False,
190
184
  cwd=Path(reactpy.__file__).parent.parent.parent,
191
185
  )
192
- wheel_glob = glob(str(dist_dir / f"reactpy-{local_version}-*.whl"))
186
+ wheel_glob = glob(str(dist_dir / f"reactpy-{local_version}-*.whl"))
193
187
 
194
- # Building a local wheel failed, try our best to give the user any possible version.
195
- if not wheel_glob:
196
- if latest_version:
188
+ # Move the local wheel to the web modules directory, if it exists
189
+ if wheel_glob:
190
+ wheel_file = Path(wheel_glob[0])
191
+ new_path = REACTPY_WEB_MODULES_DIR.current / wheel_file.name
192
+ if not new_path.exists():
197
193
  _logger.warning(
198
- "Failed to build a local wheel for ReactPy, likely due to missing build dependencies. "
199
- "PyScript will default to using the latest ReactPy version on PyPi."
194
+ "PyScript will utilize local wheel '%s'.",
195
+ wheel_file.name,
200
196
  )
201
- return f"reactpy=={latest_version}"
202
- _logger.error(
203
- "Failed to build a local wheel for ReactPy, and could not determine the latest version on PyPi. "
204
- "PyScript functionality may not work as expected.",
205
- )
206
- return f"reactpy=={local_version}"
197
+ shutil.copy(wheel_file, new_path)
198
+ return f"{REACTPY_PATH_PREFIX.current}modules/{wheel_file.name}"
207
199
 
208
- # Move the local wheel file to the web modules directory, if needed
209
- wheel_file = Path(wheel_glob[0])
210
- new_path = REACTPY_WEB_MODULES_DIR.current / wheel_file.name
211
- if not new_path.exists():
200
+ # Building a local wheel failed, try our best to give the user any version.
201
+ if latest_version:
212
202
  _logger.warning(
213
- "PyScript will utilize local wheel '%s'.",
214
- wheel_file.name,
203
+ "Failed to build a local wheel for ReactPy, likely due to missing build dependencies. "
204
+ "PyScript will default to using the latest ReactPy version on PyPi."
215
205
  )
216
- shutil.copy(wheel_file, new_path)
217
- return f"{REACTPY_PATH_PREFIX.current}modules/{wheel_file.name}"
206
+ return f"reactpy=={latest_version}"
207
+ _logger.error(
208
+ "Failed to build a local wheel for ReactPy, and could not determine the latest version on PyPi. "
209
+ "PyScript functionality may not work as expected.",
210
+ )
211
+ return f"reactpy=={local_version}"
218
212
 
219
213
 
220
214
  @functools.cache
221
- def cached_pip_index_versions(package_name: str) -> subprocess.CompletedProcess[str]:
222
- return subprocess.run(
223
- ["pip", "index", "versions", package_name],
224
- capture_output=True,
225
- text=True,
226
- check=False,
227
- )
215
+ def get_reactpy_versions() -> dict[Any, Any]:
216
+ """Fetches the available versions of a package from PyPI."""
217
+ try:
218
+ try:
219
+ response = request.urlopen("https://pypi.org/pypi/reactpy/json", timeout=5)
220
+ except Exception:
221
+ response = request.urlopen("http://pypi.org/pypi/reactpy/json", timeout=5)
222
+ if response.status == 200: # noqa: PLR2004
223
+ data = json.load(response)
224
+ versions = list(data.get("releases", {}).keys())
225
+ latest = data.get("info", {}).get("version", "")
226
+ if versions and latest:
227
+ return {"versions": versions, "latest": latest}
228
+ except Exception:
229
+ _logger.exception("Error fetching ReactPy package versions from PyPI!")
230
+ return {}
228
231
 
229
232
 
230
233
  @functools.cache