Skip to content

Commit b278ab4

Browse files
committed
docs: correct what PyScript actually strips, and what dict == costs
Two claims stated more confidently than the code supports. pyscript_component / ReactPyCsr: both pages said comments and docstrings are stripped before inlining. minify_python (executors/pyscript/utils.py) removes '#' comments and docstrings, but its docstring regex requires a newline before the opening quotes and fetch_cached_python_file .strip()s the file first -- so a module docstring is at byte 0 with no leading newline and survives. Measured on the shipped wheel: module docstring reached the page, function docstring and both comment styles did not. The security warning still holds (code and string literals ship); it is now stated accurately rather than by overstating it. choosing-the-state-structure: the Deep Dive said a rebuilt tree means comparing 'everything you just rebuilt'. dict.__eq__ compares lengths for free and stops at the first differing entry, so cost tracks where the change sorts, not object size. Measured on 100k-key dicts: changed key first ~0.00 ms, last ~1.3 ms, identical content ~3 ms. Reworded so the advice (flatten for update-code reasons, measure when it matters) survives the correction.
1 parent 5430bc9 commit b278ab4

3 files changed

Lines changed: 7 additions & 3 deletions

File tree

‎docs/src/learn/choosing-the-state-structure.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,11 @@ A useful test for whether nesting has gone too far: **count the objects an updat
283283

284284
!!! info "Deep Dive"
285285

286-
Flattening is what databases do, and ReactPy's `==` comparison gives you a concrete second reason. Because a state update is detected by comparing values, an update that rebuilds a large object makes ReactPy compare that whole object for equality — `#!python dict.__eq__` walks every key, and nested dicts compare recursively. A flat dict with one changed entry is a small comparison; a rebuilt tree is a comparison of everything you just rebuilt. For the shapes that matter, measure rather than guess: ReactPy ships no render profiler, so count renders with a `#!python use_ref` counter in the component body, as the examples on this page do.
286+
Flattening is what databases do, and ReactPy's `==` comparison gives you a concrete second reason — though it rewards less than you would expect. A state update is detected by handing the new value to `#!python ==` (`core/hooks.py`, `strictly_equal`, falls through to `==` for a dict). `#!python dict.__eq__` first compares lengths, which is free, then walks entries in insertion order and **stops at the first difference**. So the cost depends on where the changed entry sits, not on how big the object is.
287+
288+
Measured on `#!python dict`s of 100,000 keys, comparing a copy against its original after changing one entry: about `#!python 0.00 ms` when the changed key sorts first, about `#!python 1.3 ms` when it sorts last, and about `#!python 3 ms` when the two dicts hold identical content. Nested dicts recurse, so a rebuilt branch is compared down to its leaves while untouched sibling branches are still compared entry by entry too — sharing the sub-dict objects does not make the comparison skip them.
289+
290+
The honest conclusion: comparison cost is real but small and easy to over-engineer around, so flatten for the update-code reasons above rather than for this one. Where the shapes matter, measure instead of guessing — ReactPy ships no render profiler, so count renders with a `#!python use_ref` counter in the component body, as the examples on this page do.
287291

288292
## Recap
289293

‎docs/src/reference/jinja.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ Emits a `#!html <div>` plus a `#!html <script type='py'>` that runs the named fi
108108
Because the files are read and inlined, the browser never fetches them and your app serves no route for them.
109109

110110
!!! danger "The file's source ships to the client"
111-
The whole file — code, comments, docstrings, every string literal — is embedded in the HTML. Never read a secret into one of these files at render time, and do not use this tag for code you would not publish. This is unrelated to debug mode: turning `#!python reactpy.config.REACTPY_DEBUG` off hides nothing here.
111+
Nearly the whole file is embedded in the HTML: `executors/pyscript/utils.py`, `minify_python` deletes `#!python #` comments and inner docstrings before inlining, but the code and every string literal survive. One docstring does escape the filter — the **module** docstring, because the regex that removes docstrings needs a newline before the opening quotes and that docstring sits at the very start of the file (measured: a module docstring reached the page, a function docstring and both comment styles did not). Treat all of it as published, including any secret read into a string at render time. This is unrelated to debug mode: turning `#!python reactpy.config.REACTPY_DEBUG` off hides nothing here.
112112

113113
| Mistake | Error |
114114
| --- | --- |

‎docs/src/reference/reactpy-csr.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ from reactpy.executors.asgi import ReactPyCsr
2626

2727
- **`*file_paths`**: One or more paths to Python files containing components. Their **source is inlined into the served HTML**. Accepts `#!python str` or `#!python pathlib.Path`.
2828

29-
Read once, then cached for the life of the process: `executors/pyscript/utils.py`, `fetch_cached_python_file` is decorated with `#!python functools.cache`, keyed on the path. Editing the file does not change the page on reload — restart the server. The same cache strips comments and docstrings before inlining (see [Jinja](./jinja.md#pyscript-component-tag)), so what reaches the browser is not quite what you wrote.
29+
Read once, then cached for the life of the process: `executors/pyscript/utils.py`, `fetch_cached_python_file` is decorated with `#!python functools.cache`, keyed on the path. Editing the file does not change the page on reload — restart the server. The same call minifies the source before inlining — `#!python #` comments and inner docstrings are stripped, the module docstring is not — so what reaches the browser is not quite what you wrote (see [Jinja](./jinja.md#pyscript-component-tag)).
3030

3131
- **`root_name`** *(optional)*: The name of the root component inside your Python file. Defaults to `#!python "root"`. The file may define any number of components; this one is mounted.
3232

0 commit comments

Comments
 (0)