Skip to content

Commit 3c6abe6

Browse files
authored
Improve and rename vdom_to_html -> reactpy_to_html (#1278)
- Renamed `reactpy.utils.html_to_vdom` to `reactpy.utils.string_to_reactpy`. - Renamed `reactpy.utils.vdom_to_html` to `reactpy.utils.reactpy_to_string`. - `reactpy.utils.string_to_reactpy` has been upgraded to handle more complex scenarios without causing ReactJS rendering errors. - `reactpy.utils.reactpy_to_string` will now retain the user's original casing for element `data-*` and `aria-*` attributes. - Convert `pragma: no cover` comments to `nocov`
1 parent e5e2661 commit 3c6abe6

19 files changed

+706
-239
lines changed

Diff for: docs/source/about/changelog.rst

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ Unreleased
3838
- :pull:`1113` - Renamed ``reactpy.config.REACTPY_DEBUG_MODE`` to ``reactpy.config.REACTPY_DEBUG``.
3939
- :pull:`1113` - ``@reactpy/client`` now exports ``React`` and ``ReactDOM``.
4040
- :pull:`1263` - ReactPy no longer auto-converts ``snake_case`` props to ``camelCase``. It is now the responsibility of the user to ensure that props are in the correct format.
41+
- :pull:`1278` - ``reactpy.utils.reactpy_to_string`` will now retain the user's original casing for ``data-*`` and ``aria-*`` attributes.
42+
- :pull:`1278` - ``reactpy.utils.string_to_reactpy`` has been upgraded to handle more complex scenarios without causing ReactJS rendering errors.
4143

4244
**Removed**
4345

@@ -48,6 +50,8 @@ Unreleased
4850
- :pull:`1113` - Removed ``reactpy.run``. See the documentation for the new method to run ReactPy applications.
4951
- :pull:`1113` - Removed ``reactpy.backend.*``. See the documentation for the new method to run ReactPy applications.
5052
- :pull:`1113` - Removed ``reactpy.core.types`` module. Use ``reactpy.types`` instead.
53+
- :pull:`1278` - Removed ``reactpy.utils.html_to_vdom``. Use ``reactpy.utils.string_to_reactpy`` instead.
54+
- :pull:`1278` - Removed ``reactpy.utils.vdom_to_html``. Use ``reactpy.utils.reactpy_to_string`` instead.
5155
- :pull:`1113` - All backend related installation extras (such as ``pip install reactpy[starlette]``) have been removed.
5256
- :pull:`1113` - Removed deprecated function ``module_from_template``.
5357
- :pull:`1113` - Removed support for Python 3.9.

Diff for: src/reactpy/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from reactpy.core.layout import Layout
2222
from reactpy.core.vdom import vdom
2323
from reactpy.pyscript.components import pyscript_component
24-
from reactpy.utils import Ref, html_to_vdom, vdom_to_html
24+
from reactpy.utils import Ref, reactpy_to_string, string_to_reactpy
2525

2626
__author__ = "The Reactive Python Team"
2727
__version__ = "2.0.0a1"
@@ -35,9 +35,10 @@
3535
"event",
3636
"hooks",
3737
"html",
38-
"html_to_vdom",
3938
"logging",
4039
"pyscript_component",
40+
"reactpy_to_string",
41+
"string_to_reactpy",
4142
"types",
4243
"use_async_effect",
4344
"use_callback",
@@ -52,7 +53,6 @@
5253
"use_scope",
5354
"use_state",
5455
"vdom",
55-
"vdom_to_html",
5656
"web",
5757
"widgets",
5858
]

Diff for: src/reactpy/core/_life_cycle_hook.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async def __call__(self, stop: Event) -> None: ...
2222
logger = logging.getLogger(__name__)
2323

2424

25-
class _HookStack(Singleton): # pragma: no cover
25+
class _HookStack(Singleton): # nocov
2626
"""A singleton object which manages the current component tree's hooks.
2727
Life cycle hooks can be stored in a thread local or context variable depending
2828
on the platform."""

Diff for: src/reactpy/core/_thread_local.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
_StateType = TypeVar("_StateType")
66

77

8-
class ThreadLocal(Generic[_StateType]): # pragma: no cover
8+
class ThreadLocal(Generic[_StateType]): # nocov
99
"""Utility for managing per-thread state information. This is only used in
1010
environments where ContextVars are not available, such as the `pyodide`
1111
executor."""

Diff for: src/reactpy/core/hooks.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ def strictly_equal(x: Any, y: Any) -> bool:
613613
return x == y # type: ignore
614614

615615
# Fallback to identity check
616-
return x is y # pragma: no cover
616+
return x is y # nocov
617617

618618

619619
def run_effect_cleanup(cleanup_func: Ref[_EffectCleanFunc | None]) -> None:

Diff for: src/reactpy/executors/asgi/middleware.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ async def __call__(
166166
msg: dict[str, str] = orjson.loads(event["text"])
167167
if msg.get("type") == "layout-event":
168168
await ws.rendering_queue.put(msg)
169-
else: # pragma: no cover
169+
else: # nocov
170170
await asyncio.to_thread(
171171
_logger.warning, f"Unknown message type: {msg.get('type')}"
172172
)
@@ -205,7 +205,7 @@ async def run_dispatcher(self) -> None:
205205
# Determine component to serve by analyzing the URL and/or class parameters.
206206
if self.parent.multiple_root_components:
207207
url_match = re.match(self.parent.dispatcher_pattern, self.scope["path"])
208-
if not url_match: # pragma: no cover
208+
if not url_match: # nocov
209209
raise RuntimeError("Could not find component in URL path.")
210210
dotted_path = url_match["dotted_path"]
211211
if dotted_path not in self.parent.root_components:
@@ -215,7 +215,7 @@ async def run_dispatcher(self) -> None:
215215
component = self.parent.root_components[dotted_path]
216216
elif self.parent.root_component:
217217
component = self.parent.root_component
218-
else: # pragma: no cover
218+
else: # nocov
219219
raise RuntimeError("No root component provided.")
220220

221221
# Create a connection object by analyzing the websocket's query string.

Diff for: src/reactpy/executors/asgi/pyscript.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,7 @@ def __init__(
7979
self.html_head = html_head or html.head()
8080
self.html_lang = html_lang
8181

82-
def match_dispatch_path(
83-
self, scope: AsgiWebsocketScope
84-
) -> bool: # pragma: no cover
82+
def match_dispatch_path(self, scope: AsgiWebsocketScope) -> bool: # nocov
8583
"""We do not use a WebSocket dispatcher for Client-Side Rendering (CSR)."""
8684
return False
8785

Diff for: src/reactpy/executors/asgi/standalone.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
RootComponentConstructor,
3232
VdomDict,
3333
)
34-
from reactpy.utils import html_to_vdom, import_dotted_path
34+
from reactpy.utils import import_dotted_path, string_to_reactpy
3535

3636
_logger = getLogger(__name__)
3737

@@ -74,7 +74,7 @@ def __init__(
7474
extra_py = pyscript_options.get("extra_py", [])
7575
extra_js = pyscript_options.get("extra_js", {})
7676
config = pyscript_options.get("config", {})
77-
pyscript_head_vdom = html_to_vdom(
77+
pyscript_head_vdom = string_to_reactpy(
7878
pyscript_setup_html(extra_py, extra_js, config)
7979
)
8080
pyscript_head_vdom["tagName"] = ""
@@ -182,7 +182,7 @@ class ReactPyApp:
182182
async def __call__(
183183
self, scope: AsgiScope, receive: AsgiReceive, send: AsgiSend
184184
) -> None:
185-
if scope["type"] != "http": # pragma: no cover
185+
if scope["type"] != "http": # nocov
186186
if scope["type"] != "lifespan":
187187
msg = (
188188
"ReactPy app received unsupported request of type '%s' at path '%s'",

Diff for: src/reactpy/executors/utils.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
REACTPY_RECONNECT_MAX_RETRIES,
1414
)
1515
from reactpy.types import ReactPyConfig, VdomDict
16-
from reactpy.utils import import_dotted_path, vdom_to_html
16+
from reactpy.utils import import_dotted_path, reactpy_to_string
1717

1818
logger = logging.getLogger(__name__)
1919

@@ -25,7 +25,7 @@ def import_components(dotted_paths: Iterable[str]) -> dict[str, Any]:
2525
}
2626

2727

28-
def check_path(url_path: str) -> str: # pragma: no cover
28+
def check_path(url_path: str) -> str: # nocov
2929
"""Check that a path is valid URL path."""
3030
if not url_path:
3131
return "URL path must not be empty."
@@ -41,7 +41,7 @@ def check_path(url_path: str) -> str: # pragma: no cover
4141

4242
def vdom_head_to_html(head: VdomDict) -> str:
4343
if isinstance(head, dict) and head.get("tagName") == "head":
44-
return vdom_to_html(head)
44+
return reactpy_to_string(head)
4545

4646
raise ValueError(
4747
"Invalid head element! Element must be either `html.head` or a string."

Diff for: src/reactpy/pyscript/components.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from reactpy import component, hooks
77
from reactpy.pyscript.utils import pyscript_component_html
88
from reactpy.types import ComponentType, Key
9-
from reactpy.utils import html_to_vdom
9+
from reactpy.utils import string_to_reactpy
1010

1111
if TYPE_CHECKING:
1212
from reactpy.types import VdomDict
@@ -22,15 +22,15 @@ def _pyscript_component(
2222
raise ValueError("At least one file path must be provided.")
2323

2424
rendered, set_rendered = hooks.use_state(False)
25-
initial = html_to_vdom(initial) if isinstance(initial, str) else initial
25+
initial = string_to_reactpy(initial) if isinstance(initial, str) else initial
2626

2727
if not rendered:
2828
# FIXME: This is needed to properly re-render PyScript during a WebSocket
2929
# disconnection / reconnection. There may be a better way to do this in the future.
3030
set_rendered(True)
3131
return None
3232

33-
component_vdom = html_to_vdom(
33+
component_vdom = string_to_reactpy(
3434
pyscript_component_html(tuple(str(fp) for fp in file_paths), initial, root)
3535
)
3636
component_vdom["tagName"] = ""

Diff for: src/reactpy/pyscript/utils.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import reactpy
1919
from reactpy.config import REACTPY_DEBUG, REACTPY_PATH_PREFIX, REACTPY_WEB_MODULES_DIR
2020
from reactpy.types import VdomDict
21-
from reactpy.utils import vdom_to_html
21+
from reactpy.utils import reactpy_to_string
2222

2323
if TYPE_CHECKING:
2424
from collections.abc import Sequence
@@ -77,7 +77,7 @@ def pyscript_component_html(
7777
file_paths: Sequence[str], initial: str | VdomDict, root: str
7878
) -> str:
7979
"""Renders a PyScript component with the user's code."""
80-
_initial = initial if isinstance(initial, str) else vdom_to_html(initial)
80+
_initial = initial if isinstance(initial, str) else reactpy_to_string(initial)
8181
uuid = uuid4().hex
8282
executor_code = pyscript_executor_html(file_paths=file_paths, uuid=uuid, root=root)
8383

@@ -144,7 +144,7 @@ def extend_pyscript_config(
144144
return orjson.dumps(pyscript_config).decode("utf-8")
145145

146146

147-
def reactpy_version_string() -> str: # pragma: no cover
147+
def reactpy_version_string() -> str: # nocov
148148
from reactpy.testing.common import GITHUB_ACTIONS
149149

150150
local_version = reactpy.__version__

Diff for: src/reactpy/templatetags/jinja.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def render(self, *args: str, **kwargs: str) -> str:
2222
return pyscript_setup(*args, **kwargs)
2323

2424
# This should never happen, but we validate it for safety.
25-
raise ValueError(f"Unknown tag: {self.tag_name}") # pragma: no cover
25+
raise ValueError(f"Unknown tag: {self.tag_name}") # nocov
2626

2727

2828
def component(dotted_path: str, **kwargs: str) -> str:

Diff for: src/reactpy/testing/common.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from reactpy.config import REACTPY_TESTS_DEFAULT_TIMEOUT, REACTPY_WEB_MODULES_DIR
1717
from reactpy.core._life_cycle_hook import HOOK_STACK, LifeCycleHook
1818
from reactpy.core.events import EventHandler, to_event_handler_function
19+
from reactpy.utils import str_to_bool
1920

2021

2122
def clear_reactpy_web_modules_dir() -> None:
@@ -29,14 +30,7 @@ def clear_reactpy_web_modules_dir() -> None:
2930

3031

3132
_DEFAULT_POLL_DELAY = 0.1
32-
GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS", "False") in {
33-
"y",
34-
"yes",
35-
"t",
36-
"true",
37-
"on",
38-
"1",
39-
}
33+
GITHUB_ACTIONS = str_to_bool(os.getenv("GITHUB_ACTIONS", ""))
4034

4135

4236
class poll(Generic[_R]): # noqa: N801

Diff for: src/reactpy/testing/display.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ async def __aenter__(self) -> DisplayFixture:
5858

5959
self.page.set_default_timeout(REACTPY_TESTS_DEFAULT_TIMEOUT.current * 1000)
6060

61-
if not hasattr(self, "backend"): # pragma: no cover
61+
if not hasattr(self, "backend"): # nocov
6262
self.backend = BackendFixture()
6363
await es.enter_async_context(self.backend)
6464

Diff for: src/reactpy/testing/utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
def find_available_port(
99
host: str, port_min: int = 8000, port_max: int = 9000
10-
) -> int: # pragma: no cover
10+
) -> int: # nocov
1111
"""Get a port that's available for the given host and port range"""
1212
for port in range(port_min, port_max):
1313
with closing(socket.socket()) as sock:

0 commit comments

Comments
 (0)