Skip to content

Commit 3711803

Browse files
committed
Grouo pyscript related code into module
1 parent 8ec20b7 commit 3711803

File tree

5 files changed

+130
-112
lines changed

5 files changed

+130
-112
lines changed

src/reactpy_django/components.py

+1-31
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import os
77
from typing import TYPE_CHECKING, Any, Callable, Union, cast
88
from urllib.parse import urlencode
9-
from uuid import uuid4
109

1110
from django.contrib.staticfiles.finders import find
1211
from django.core.cache import caches
@@ -17,13 +16,11 @@
1716

1817
from reactpy_django.exceptions import ViewNotRegisteredError
1918
from reactpy_django.forms.components import _django_form
20-
from reactpy_django.html import pyscript
19+
from reactpy_django.pyscript.components import _pyscript_component
2120
from reactpy_django.utils import (
2221
generate_obj_name,
2322
import_module,
24-
render_pyscript_template,
2523
render_view,
26-
vdom_or_component_to_string,
2724
)
2825

2926
if TYPE_CHECKING:
@@ -316,30 +313,3 @@ def _cached_static_contents(static_path: str) -> str:
316313
caches[REACTPY_CACHE].set(cache_key, file_contents, timeout=None, version=int(last_modified_time))
317314

318315
return file_contents
319-
320-
321-
@component
322-
def _pyscript_component(
323-
*file_paths: str,
324-
initial: str | VdomDict | ComponentType = "",
325-
root: str = "root",
326-
):
327-
rendered, set_rendered = hooks.use_state(False)
328-
uuid_ref = hooks.use_ref(uuid4().hex.replace("-", ""))
329-
uuid = uuid_ref.current
330-
initial = vdom_or_component_to_string(initial, uuid=uuid)
331-
executor = render_pyscript_template(file_paths, uuid, root)
332-
333-
if not rendered:
334-
# FIXME: This is needed to properly re-render PyScript during a WebSocket
335-
# disconnection / reconnection. There may be a better way to do this in the future.
336-
set_rendered(True)
337-
return None
338-
339-
return html._(
340-
html.div(
341-
{"id": f"pyscript-{uuid}", "className": "pyscript", "data-uuid": uuid},
342-
initial,
343-
),
344-
pyscript({"async": ""}, executor),
345-
)
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
from uuid import uuid4
4+
5+
from reactpy import component, hooks, html
6+
from reactpy.types import ComponentType, VdomDict
7+
8+
from reactpy_django.html import pyscript
9+
from reactpy_django.pyscript.utils import render_pyscript_template
10+
from reactpy_django.utils import vdom_or_component_to_string
11+
12+
13+
@component
14+
def _pyscript_component(
15+
*file_paths: str,
16+
initial: str | VdomDict | ComponentType = "",
17+
root: str = "root",
18+
):
19+
rendered, set_rendered = hooks.use_state(False)
20+
uuid_ref = hooks.use_ref(uuid4().hex.replace("-", ""))
21+
uuid = uuid_ref.current
22+
initial = vdom_or_component_to_string(initial, uuid=uuid)
23+
executor = render_pyscript_template(file_paths, uuid, root)
24+
25+
if not rendered:
26+
# FIXME: This is needed to properly re-render PyScript during a WebSocket
27+
# disconnection / reconnection. There may be a better way to do this in the future.
28+
set_rendered(True)
29+
return None
30+
31+
return html._(
32+
html.div(
33+
{"id": f"pyscript-{uuid}", "className": "pyscript", "data-uuid": uuid},
34+
initial,
35+
),
36+
pyscript({"async": ""}, executor),
37+
)

src/reactpy_django/pyscript/utils.py

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from __future__ import annotations
2+
3+
import json
4+
import os
5+
import textwrap
6+
from copy import deepcopy
7+
from pathlib import Path
8+
from typing import TYPE_CHECKING, Any
9+
10+
import jsonpointer
11+
import orjson
12+
import reactpy
13+
from django.templatetags.static import static
14+
15+
from reactpy_django.utils import create_cache_key
16+
17+
if TYPE_CHECKING:
18+
from collections.abc import Sequence
19+
20+
21+
PYSCRIPT_COMPONENT_TEMPLATE = (Path(__file__).parent / "component_template.py").read_text(encoding="utf-8")
22+
PYSCRIPT_LAYOUT_HANDLER = (Path(__file__).parent / "layout_handler.py").read_text(encoding="utf-8")
23+
PYSCRIPT_DEFAULT_CONFIG: dict[str, Any] = {}
24+
25+
26+
def render_pyscript_template(file_paths: Sequence[str], uuid: str, root: str):
27+
"""Inserts the user's code into the PyScript template using pattern matching."""
28+
from django.core.cache import caches
29+
30+
from reactpy_django.config import REACTPY_CACHE
31+
32+
# Create a valid PyScript executor by replacing the template values
33+
executor = PYSCRIPT_COMPONENT_TEMPLATE.replace("UUID", uuid)
34+
executor = executor.replace("return root()", f"return {root}()")
35+
36+
# Fetch the user's PyScript code
37+
all_file_contents: list[str] = []
38+
for file_path in file_paths:
39+
# Try to get user code from cache
40+
cache_key = create_cache_key("pyscript", file_path)
41+
last_modified_time = os.stat(file_path).st_mtime
42+
file_contents: str = caches[REACTPY_CACHE].get(cache_key, version=int(last_modified_time))
43+
if file_contents:
44+
all_file_contents.append(file_contents)
45+
46+
# If not cached, read from file system
47+
else:
48+
file_contents = Path(file_path).read_text(encoding="utf-8").strip()
49+
all_file_contents.append(file_contents)
50+
caches[REACTPY_CACHE].set(cache_key, file_contents, version=int(last_modified_time))
51+
52+
# Prepare the PyScript code block
53+
user_code = "\n".join(all_file_contents) # Combine all user code
54+
user_code = user_code.replace("\t", " ") # Normalize the text
55+
user_code = textwrap.indent(user_code, " ") # Add indentation to match template
56+
57+
# Insert the user code into the PyScript template
58+
return executor.replace(" def root(): ...", user_code)
59+
60+
61+
def extend_pyscript_config(extra_py: Sequence, extra_js: dict | str, config: dict | str) -> str:
62+
"""Extends ReactPy's default PyScript config with user provided values."""
63+
# Lazily set up the initial config in to wait for Django's static file system
64+
if not PYSCRIPT_DEFAULT_CONFIG:
65+
PYSCRIPT_DEFAULT_CONFIG.update({
66+
"packages": [
67+
f"reactpy=={reactpy.__version__}",
68+
f"jsonpointer=={jsonpointer.__version__}",
69+
"ssl",
70+
],
71+
"js_modules": {"main": {static("reactpy_django/morphdom/morphdom-esm.js"): "morphdom"}},
72+
})
73+
74+
# Extend the Python dependency list
75+
pyscript_config = deepcopy(PYSCRIPT_DEFAULT_CONFIG)
76+
pyscript_config["packages"].extend(extra_py)
77+
78+
# Extend the JavaScript dependency list
79+
if extra_js and isinstance(extra_js, str):
80+
pyscript_config["js_modules"]["main"].update(json.loads(extra_js))
81+
elif extra_js and isinstance(extra_js, dict):
82+
pyscript_config["js_modules"]["main"].update(extra_py)
83+
84+
# Update the config
85+
if config and isinstance(config, str):
86+
pyscript_config.update(json.loads(config))
87+
elif config and isinstance(config, dict):
88+
pyscript_config.update(config)
89+
return orjson.dumps(pyscript_config).decode("utf-8")

src/reactpy_django/templatetags/reactpy.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
InvalidHostError,
1616
OfflineComponentMissingError,
1717
)
18+
from reactpy_django.pyscript.utils import PYSCRIPT_LAYOUT_HANDLER, extend_pyscript_config, render_pyscript_template
1819
from reactpy_django.utils import (
19-
PYSCRIPT_LAYOUT_HANDLER,
20-
extend_pyscript_config,
2120
prerender_component,
22-
render_pyscript_template,
2321
save_component_params,
2422
strtobool,
2523
validate_component_args,

src/reactpy_django/utils.py

+2-78
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,25 @@
22

33
import contextlib
44
import inspect
5-
import json
65
import logging
76
import os
87
import re
9-
import textwrap
108
from asyncio import iscoroutinefunction
119
from concurrent.futures import ThreadPoolExecutor
12-
from copy import deepcopy
1310
from fnmatch import fnmatch
1411
from functools import wraps
1512
from importlib import import_module
16-
from pathlib import Path
17-
from typing import TYPE_CHECKING, Any, Callable
13+
from typing import TYPE_CHECKING, Any, Awaitable, Callable
1814
from uuid import UUID, uuid4
1915

2016
import dill
21-
import jsonpointer
22-
import orjson
23-
import reactpy
2417
from asgiref.sync import async_to_sync
2518
from channels.db import database_sync_to_async
2619
from django.db.models import ManyToManyField, ManyToOneRel, prefetch_related_objects
2720
from django.db.models.base import Model
2821
from django.db.models.query import QuerySet
2922
from django.http import HttpRequest, HttpResponse
3023
from django.template import engines
31-
from django.templatetags.static import static
3224
from django.utils.encoding import smart_str
3325
from reactpy import vdom_to_html
3426
from reactpy.backend.types import Connection, Location
@@ -41,6 +33,7 @@
4133
InvalidHostError,
4234
ViewDoesNotExistError,
4335
)
36+
from reactpy_django.types import FuncParams, Inferred
4437

4538
if TYPE_CHECKING:
4639
from collections.abc import Awaitable, Mapping, Sequence
@@ -65,9 +58,6 @@
6558
+ rf"({_OFFLINE_KWARG_PATTERN}|{_GENERIC_KWARG_PATTERN})*?"
6659
+ r"\s*%}"
6760
)
68-
PYSCRIPT_COMPONENT_TEMPLATE = (Path(__file__).parent / "pyscript" / "component_template.py").read_text(encoding="utf-8")
69-
PYSCRIPT_LAYOUT_HANDLER = (Path(__file__).parent / "pyscript" / "layout_handler.py").read_text(encoding="utf-8")
70-
PYSCRIPT_DEFAULT_CONFIG: dict[str, Any] = {}
7161
FILE_ASYNC_ITERATOR_THREAD = ThreadPoolExecutor(max_workers=1, thread_name_prefix="ReactPy-Django-FileAsyncIterator")
7262

7363

@@ -441,72 +431,6 @@ def vdom_or_component_to_string(
441431
raise ValueError(msg)
442432

443433

444-
def render_pyscript_template(file_paths: Sequence[str], uuid: str, root: str):
445-
"""Inserts the user's code into the PyScript template using pattern matching."""
446-
from django.core.cache import caches
447-
448-
from reactpy_django.config import REACTPY_CACHE
449-
450-
# Create a valid PyScript executor by replacing the template values
451-
executor = PYSCRIPT_COMPONENT_TEMPLATE.replace("UUID", uuid)
452-
executor = executor.replace("return root()", f"return {root}()")
453-
454-
# Fetch the user's PyScript code
455-
all_file_contents: list[str] = []
456-
for file_path in file_paths:
457-
# Try to get user code from cache
458-
cache_key = create_cache_key("pyscript", file_path)
459-
last_modified_time = os.stat(file_path).st_mtime
460-
file_contents: str = caches[REACTPY_CACHE].get(cache_key, version=int(last_modified_time))
461-
if file_contents:
462-
all_file_contents.append(file_contents)
463-
464-
# If not cached, read from file system
465-
else:
466-
file_contents = Path(file_path).read_text(encoding="utf-8").strip()
467-
all_file_contents.append(file_contents)
468-
caches[REACTPY_CACHE].set(cache_key, file_contents, version=int(last_modified_time))
469-
470-
# Prepare the PyScript code block
471-
user_code = "\n".join(all_file_contents) # Combine all user code
472-
user_code = user_code.replace("\t", " ") # Normalize the text
473-
user_code = textwrap.indent(user_code, " ") # Add indentation to match template
474-
475-
# Insert the user code into the PyScript template
476-
return executor.replace(" def root(): ...", user_code)
477-
478-
479-
def extend_pyscript_config(extra_py: Sequence, extra_js: dict | str, config: dict | str) -> str:
480-
"""Extends ReactPy's default PyScript config with user provided values."""
481-
# Lazily set up the initial config in to wait for Django's static file system
482-
if not PYSCRIPT_DEFAULT_CONFIG:
483-
PYSCRIPT_DEFAULT_CONFIG.update({
484-
"packages": [
485-
f"reactpy=={reactpy.__version__}",
486-
f"jsonpointer=={jsonpointer.__version__}",
487-
"ssl",
488-
],
489-
"js_modules": {"main": {static("reactpy_django/morphdom/morphdom-esm.js"): "morphdom"}},
490-
})
491-
492-
# Extend the Python dependency list
493-
pyscript_config = deepcopy(PYSCRIPT_DEFAULT_CONFIG)
494-
pyscript_config["packages"].extend(extra_py)
495-
496-
# Extend the JavaScript dependency list
497-
if extra_js and isinstance(extra_js, str):
498-
pyscript_config["js_modules"]["main"].update(json.loads(extra_js))
499-
elif extra_js and isinstance(extra_js, dict):
500-
pyscript_config["js_modules"]["main"].update(extra_py)
501-
502-
# Update the config
503-
if config and isinstance(config, str):
504-
pyscript_config.update(json.loads(config))
505-
elif config and isinstance(config, dict):
506-
pyscript_config.update(config)
507-
return orjson.dumps(pyscript_config).decode("utf-8")
508-
509-
510434
def save_component_params(args, kwargs, uuid) -> None:
511435
"""Saves the component parameters to the database.
512436
This is used within our template tag in order to propogate

0 commit comments

Comments
 (0)