reactpy 2.0.0b5__py3-none-any.whl → 2.0.0b7__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 (52) hide show
  1. reactpy/__init__.py +4 -3
  2. reactpy/_html.py +11 -9
  3. reactpy/config.py +1 -1
  4. reactpy/core/_life_cycle_hook.py +4 -1
  5. reactpy/core/_thread_local.py +1 -1
  6. reactpy/core/hooks.py +11 -19
  7. reactpy/core/layout.py +43 -38
  8. reactpy/core/serve.py +11 -16
  9. reactpy/core/vdom.py +3 -5
  10. reactpy/executors/asgi/pyscript.py +4 -1
  11. reactpy/executors/asgi/standalone.py +1 -1
  12. reactpy/{pyscript → executors/pyscript}/component_template.py +1 -1
  13. reactpy/{pyscript → executors/pyscript}/components.py +1 -1
  14. reactpy/{pyscript → executors/pyscript}/utils.py +1 -1
  15. reactpy/executors/utils.py +32 -7
  16. reactpy/reactjs/__init__.py +351 -0
  17. reactpy/reactjs/module.py +267 -0
  18. reactpy/reactjs/types.py +7 -0
  19. reactpy/reactjs/utils.py +212 -0
  20. reactpy/static/index-64wy0fss.js +5 -0
  21. reactpy/static/index-64wy0fss.js.map +10 -0
  22. reactpy/static/index-beq660xy.js +5 -0
  23. reactpy/static/index-beq660xy.js.map +12 -0
  24. reactpy/static/index.js +2 -2
  25. reactpy/static/index.js.map +7 -10
  26. reactpy/static/preact-dom.js +4 -0
  27. reactpy/static/preact-dom.js.map +11 -0
  28. reactpy/static/preact-jsx-runtime.js +4 -0
  29. reactpy/static/preact-jsx-runtime.js.map +9 -0
  30. reactpy/static/preact.js +4 -0
  31. reactpy/static/preact.js.map +10 -0
  32. reactpy/templatetags/jinja.py +4 -1
  33. reactpy/testing/__init__.py +2 -7
  34. reactpy/testing/backend.py +24 -12
  35. reactpy/testing/common.py +1 -9
  36. reactpy/testing/display.py +65 -28
  37. reactpy/testing/logs.py +1 -1
  38. reactpy/transforms.py +2 -2
  39. reactpy/types.py +17 -11
  40. reactpy/utils.py +1 -1
  41. reactpy/web/__init__.py +0 -6
  42. reactpy/web/module.py +37 -473
  43. reactpy/web/utils.py +2 -158
  44. {reactpy-2.0.0b5.dist-info → reactpy-2.0.0b7.dist-info}/METADATA +1 -1
  45. {reactpy-2.0.0b5.dist-info → reactpy-2.0.0b7.dist-info}/RECORD +50 -38
  46. reactpy/testing/utils.py +0 -27
  47. reactpy/web/templates/react.js +0 -61
  48. /reactpy/{pyscript → executors/pyscript}/__init__.py +0 -0
  49. /reactpy/{pyscript → executors/pyscript}/layout_handler.py +0 -0
  50. {reactpy-2.0.0b5.dist-info → reactpy-2.0.0b7.dist-info}/WHEEL +0 -0
  51. {reactpy-2.0.0b5.dist-info → reactpy-2.0.0b7.dist-info}/entry_points.txt +0 -0
  52. {reactpy-2.0.0b5.dist-info → reactpy-2.0.0b7.dist-info}/licenses/LICENSE +0 -0
@@ -1,38 +1,47 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
3
4
  from contextlib import AsyncExitStack
5
+ from logging import getLogger
4
6
  from types import TracebackType
5
- from typing import Any
7
+ from typing import TYPE_CHECKING, Any
6
8
 
7
- from playwright.async_api import (
8
- Browser,
9
- BrowserContext,
10
- Page,
11
- async_playwright,
12
- )
9
+ from playwright.async_api import Browser, Page, async_playwright, expect
13
10
 
14
- from reactpy.config import REACTPY_TESTS_DEFAULT_TIMEOUT
11
+ from reactpy.config import REACTPY_TESTS_DEFAULT_TIMEOUT as DEFAULT_TIMEOUT
15
12
  from reactpy.testing.backend import BackendFixture
16
13
  from reactpy.types import RootComponentConstructor
17
14
 
15
+ if TYPE_CHECKING:
16
+ import pytest
17
+
18
+ _logger = getLogger(__name__)
19
+
18
20
 
19
21
  class DisplayFixture:
20
22
  """A fixture for running web-based tests using ``playwright``"""
21
23
 
22
- _exit_stack: AsyncExitStack
24
+ page: Page
25
+ browser_is_external: bool = False
26
+ backend_is_external: bool = False
23
27
 
24
28
  def __init__(
25
29
  self,
26
30
  backend: BackendFixture | None = None,
27
- driver: Browser | BrowserContext | Page | None = None,
31
+ browser: Browser | None = None,
32
+ headless: bool = False,
33
+ timeout: float | None = None,
28
34
  ) -> None:
29
- if backend is not None:
35
+ if backend:
36
+ self.backend_is_external = True
30
37
  self.backend = backend
31
- if driver is not None:
32
- if isinstance(driver, Page):
33
- self.page = driver
34
- else:
35
- self._browser = driver
38
+
39
+ if browser:
40
+ self.browser_is_external = True
41
+ self.browser = browser
42
+
43
+ self.timeout = DEFAULT_TIMEOUT.current if timeout is None else timeout
44
+ self.headless = headless
36
45
 
37
46
  async def show(
38
47
  self,
@@ -42,28 +51,47 @@ class DisplayFixture:
42
51
  await self.goto("/")
43
52
 
44
53
  async def goto(self, path: str, query: Any | None = None) -> None:
54
+ await self.configure_page()
45
55
  await self.page.goto(self.backend.url(path, query))
46
56
 
47
57
  async def __aenter__(self) -> DisplayFixture:
48
- es = self._exit_stack = AsyncExitStack()
58
+ self.exit_stack = AsyncExitStack()
49
59
 
50
- browser: Browser | BrowserContext
51
- if not hasattr(self, "page"):
52
- if not hasattr(self, "_browser"):
53
- pw = await es.enter_async_context(async_playwright())
54
- browser = await pw.chromium.launch()
55
- else:
56
- browser = self._browser
57
- self.page = await browser.new_page()
60
+ if not hasattr(self, "browser"):
61
+ pw = await self.exit_stack.enter_async_context(async_playwright())
62
+ self.browser = await self.exit_stack.enter_async_context(
63
+ await pw.chromium.launch(headless=not _playwright_visible())
64
+ )
58
65
 
59
- self.page.set_default_timeout(REACTPY_TESTS_DEFAULT_TIMEOUT.current * 1000)
66
+ expect.set_options(timeout=self.timeout * 1000)
67
+ await self.configure_page()
60
68
 
61
69
  if not hasattr(self, "backend"): # nocov
62
70
  self.backend = BackendFixture()
63
- await es.enter_async_context(self.backend)
71
+ await self.exit_stack.enter_async_context(self.backend)
64
72
 
65
73
  return self
66
74
 
75
+ async def configure_page(self) -> None:
76
+ if getattr(self, "page", None) is None:
77
+ self.page = await self.browser.new_page()
78
+ self.page = await self.exit_stack.enter_async_context(self.page)
79
+ self.page.set_default_navigation_timeout(self.timeout * 1000)
80
+ self.page.set_default_timeout(self.timeout * 1000)
81
+ self.page.on(
82
+ "requestfailed",
83
+ lambda x: _logger.error(f"BROWSER LOAD ERROR: {x.url}\n{x.failure}"),
84
+ )
85
+ self.page.on(
86
+ "console", lambda x: _logger.info(f"BROWSER CONSOLE: {x.text}")
87
+ )
88
+ self.page.on(
89
+ "pageerror",
90
+ lambda x: _logger.error(
91
+ f"BROWSER ERROR: {x.name} - {x.message}\n{x.stack}"
92
+ ),
93
+ )
94
+
67
95
  async def __aexit__(
68
96
  self,
69
97
  exc_type: type[BaseException] | None,
@@ -71,4 +99,13 @@ class DisplayFixture:
71
99
  traceback: TracebackType | None,
72
100
  ) -> None:
73
101
  self.backend.mount(None)
74
- await self._exit_stack.aclose()
102
+ await self.exit_stack.aclose()
103
+
104
+
105
+ def _playwright_visible(pytestconfig: pytest.Config | None = None) -> bool:
106
+ if (pytestconfig and pytestconfig.getoption("visible")) or os.environ.get(
107
+ "PLAYWRIGHT_VISIBLE"
108
+ ) == "1":
109
+ os.environ.setdefault("PLAYWRIGHT_VISIBLE", "1")
110
+ return True
111
+ return False
reactpy/testing/logs.py CHANGED
@@ -175,4 +175,4 @@ def _raise_log_message_error(
175
175
  conditions.append(f"exception type {error_type}")
176
176
  if match_error:
177
177
  conditions.append(f"error message pattern {match_error!r}")
178
- raise LogAssertionError(prefix + " " + " and ".join(conditions))
178
+ raise LogAssertionError(f"{prefix} " + " and ".join(conditions))
reactpy/transforms.py CHANGED
@@ -113,8 +113,8 @@ class RequiredTransforms:
113
113
  if key is None and vdom["tagName"] in {"input", "select", "textarea"}:
114
114
  key = attributes.get("name")
115
115
 
116
- if key:
117
- vdom["key"] = key
116
+ if key and "key" not in attributes:
117
+ attributes["key"] = key
118
118
 
119
119
  def intercept_link_clicks(self, vdom: VdomDict) -> None:
120
120
  """Intercepts anchor link clicks and prevents the default behavior.
reactpy/types.py CHANGED
@@ -10,6 +10,7 @@ from typing import (
10
10
  Generic,
11
11
  Literal,
12
12
  NamedTuple,
13
+ NewType,
13
14
  NotRequired,
14
15
  Protocol,
15
16
  TypeAlias,
@@ -533,7 +534,7 @@ class CssStyleTypeDict(TypedDict, total=False):
533
534
  zIndex: str | int
534
535
 
535
536
 
536
- # TODO: Enable `extra_items` on `CssStyleDict` when PEP 728 is merged, likely in Python 3.14. Ref: https://peps.python.org/pep-0728/
537
+ # TODO: Enable `extra_items` on `CssStyleDict` when PEP 728 is merged, likely in Python 3.15. Ref: https://peps.python.org/pep-0728/
537
538
  CssStyleDict = CssStyleTypeDict | dict[str, Any]
538
539
 
539
540
  EventFunc = Callable[[dict[str, Any]], Awaitable[None] | None]
@@ -543,8 +544,8 @@ class DangerouslySetInnerHTML(TypedDict):
543
544
  __html: str
544
545
 
545
546
 
546
- # TODO: It's probably better to break this one attributes dict down into what each specific
547
- # HTML node's attributes can be, and make sure those types are resolved correctly within `HtmlConstructor`
547
+ # TODO: It's probably better to break this down into what each HTML node's attributes can be,
548
+ # and make sure those types are resolved correctly within `HtmlConstructor`
548
549
  # TODO: This could be generated by parsing from `@types/react` in the future
549
550
  # https://www.npmjs.com/package/@types/react?activeTab=code
550
551
  VdomAttributesTypeDict = TypedDict(
@@ -797,7 +798,6 @@ VdomAttributes = VdomAttributesTypeDict | dict[str, Any]
797
798
 
798
799
  VdomDictKeys = Literal[
799
800
  "tagName",
800
- "key",
801
801
  "children",
802
802
  "attributes",
803
803
  "eventHandlers",
@@ -806,7 +806,6 @@ VdomDictKeys = Literal[
806
806
  ]
807
807
  ALLOWED_VDOM_KEYS = {
808
808
  "tagName",
809
- "key",
810
809
  "children",
811
810
  "attributes",
812
811
  "eventHandlers",
@@ -819,7 +818,6 @@ class VdomTypeDict(TypedDict):
819
818
  """TypedDict representation of what the `VdomDict` should look like."""
820
819
 
821
820
  tagName: str
822
- key: NotRequired[Key | None]
823
821
  children: NotRequired[Sequence[Component | VdomChild]]
824
822
  attributes: NotRequired[VdomAttributes]
825
823
  eventHandlers: NotRequired[EventHandlerDict]
@@ -844,8 +842,6 @@ class VdomDict(dict):
844
842
  @overload
845
843
  def __getitem__(self, key: Literal["tagName"]) -> str: ...
846
844
  @overload
847
- def __getitem__(self, key: Literal["key"]) -> Key | None: ...
848
- @overload
849
845
  def __getitem__(
850
846
  self, key: Literal["children"]
851
847
  ) -> Sequence[Component | VdomChild]: ...
@@ -863,8 +859,6 @@ class VdomDict(dict):
863
859
  @overload
864
860
  def __setitem__(self, key: Literal["tagName"], value: str) -> None: ...
865
861
  @overload
866
- def __setitem__(self, key: Literal["key"], value: Key | None) -> None: ...
867
- @overload
868
862
  def __setitem__(
869
863
  self, key: Literal["children"], value: Sequence[Component | VdomChild]
870
864
  ) -> None: ...
@@ -1116,7 +1110,6 @@ class CustomVdomConstructor(Protocol):
1116
1110
  self,
1117
1111
  attributes: VdomAttributes,
1118
1112
  children: Sequence[VdomChildren],
1119
- key: Key | None,
1120
1113
  event_handlers: EventHandlerDict,
1121
1114
  ) -> VdomDict: ...
1122
1115
 
@@ -1140,3 +1133,16 @@ class Event(dict):
1140
1133
 
1141
1134
  def stopPropagation(self) -> None:
1142
1135
  """Stop the event from propagating."""
1136
+
1137
+
1138
+ SourceType = NewType("SourceType", str)
1139
+
1140
+
1141
+ @dataclass(frozen=True)
1142
+ class JavaScriptModule:
1143
+ source: str
1144
+ source_type: SourceType
1145
+ default_fallback: Any | None
1146
+ import_names: set[str] | None
1147
+ file: Path | None
1148
+ unmount_before_update: bool
reactpy/utils.py CHANGED
@@ -242,7 +242,7 @@ def component_to_vdom(component: Component) -> VdomDict:
242
242
  return component_to_vdom(cast(Component, result))
243
243
  elif isinstance(result, str):
244
244
  return html.div(result)
245
- return html.fragment()
245
+ return html()
246
246
 
247
247
 
248
248
  def _react_attribute_to_html(key: str, value: Any) -> tuple[str, str]:
reactpy/web/__init__.py CHANGED
@@ -3,9 +3,6 @@ from reactpy.web.module import (
3
3
  module_from_file,
4
4
  module_from_string,
5
5
  module_from_url,
6
- reactjs_component_from_file,
7
- reactjs_component_from_string,
8
- reactjs_component_from_url,
9
6
  )
10
7
 
11
8
  __all__ = [
@@ -13,7 +10,4 @@ __all__ = [
13
10
  "module_from_file",
14
11
  "module_from_string",
15
12
  "module_from_url",
16
- "reactjs_component_from_file",
17
- "reactjs_component_from_string",
18
- "reactjs_component_from_url",
19
13
  ]