Skip to content

Commit 8b5b67f

Browse files
committed
misc work
straighten out the client protocol and add a few tests for new functions also update examples scripts
1 parent a3b4ef1 commit 8b5b67f

File tree

11 files changed

+122
-71
lines changed

11 files changed

+122
-71
lines changed

idom/client/build_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def _hash_config_item(config_item: Dict[str, Any]) -> str:
270270
"properties": {
271271
"source_name": {
272272
"type": "string",
273-
"pattern": r"^[\w\d\-]+$",
273+
"pattern": r"^[\w\d\-\.]+$",
274274
},
275275
"js_dependencies": {
276276
"type": "array",

idom/client/manage.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
import subprocess
33
from pathlib import Path
44
from tempfile import TemporaryDirectory
5-
from typing import Optional, Iterable, Sequence, List
5+
from typing import Optional, Iterable, Sequence, List, Tuple
66

77
from .build_config import (
88
BuildConfig,
99
BuildConfigItem,
1010
find_python_packages_build_config_items,
1111
)
12-
from .utils import open_modifiable_json, find_js_module_exports
12+
from .utils import open_modifiable_json, find_js_module_exports_in_source
1313

1414
from idom.cli import console
1515

@@ -19,6 +19,10 @@
1919
_BUILD_CONFIG: Optional[BuildConfig] = None
2020

2121

22+
class WebModuleError(Exception):
23+
"""Related to the use of javascript web modules"""
24+
25+
2226
def build_config() -> BuildConfig:
2327
global _BUILD_CONFIG
2428
if _BUILD_CONFIG is None:
@@ -27,23 +31,24 @@ def build_config() -> BuildConfig:
2731

2832

2933
def web_module_exports(source_name: str, package_name: str) -> List[str]:
30-
dep_alias = build_config().get_js_dependency_alias(source_name, package_name)
31-
if dep_alias is None:
32-
return []
33-
module_file = find_client_build_path(f"web_modules/{dep_alias}.js")
34-
if module_file is None:
35-
return []
36-
return find_js_module_exports(module_file)
34+
_, module_file = _web_module_alias_and_file_path(source_name, package_name)
35+
with module_file.open() as f:
36+
return find_js_module_exports_in_source(f.read())
3737

3838

3939
def web_module_url(source_name: str, package_name: str) -> Optional[str]:
40-
dep_alias = build_config().get_js_dependency_alias(source_name, package_name)
41-
if dep_alias is None:
42-
return None
43-
if find_client_build_path(f"web_modules/{dep_alias}.js") is None:
44-
return None
45-
# need to go back a level since the JS that import this is in `core_components`
46-
return f"../web_modules/{dep_alias}.js"
40+
alias, _ = _web_module_alias_and_file_path(source_name, package_name)
41+
# need to go back a level since the JS that imports this is in `core_components`
42+
return f"../web_modules/{alias}.js"
43+
44+
45+
def web_module_exists(source_name: str, package_name: str) -> bool:
46+
try:
47+
_web_module_alias_and_file_path(source_name, package_name)
48+
except WebModuleError:
49+
return False
50+
else:
51+
return True
4752

4853

4954
def find_client_build_path(rel_path: str) -> Optional[Path]:
@@ -108,6 +113,22 @@ def restore() -> None:
108113
_run_subprocess(["npm", "run", "build"], APP_DIR)
109114

110115

116+
def _web_module_alias_and_file_path(
117+
source_name: str, package_name: str
118+
) -> Tuple[str, Path]:
119+
alias = build_config().get_js_dependency_alias(source_name, package_name)
120+
if alias is None:
121+
raise WebModuleError(
122+
f"Package {package_name!r} is not declared as a dependency of {source_name!r}"
123+
)
124+
module_file = find_client_build_path(f"web_modules/{alias}.js")
125+
if module_file is None:
126+
raise WebModuleError(
127+
f"Dependency {package_name!r} of {source_name!r} was not installed"
128+
)
129+
return alias, module_file
130+
131+
111132
def _npm_install(packages: Sequence[str], cwd: Path) -> None:
112133
_run_subprocess(["npm", "install"] + packages, cwd)
113134

idom/client/module.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,7 @@ def __init__(
5252
if source_name is None:
5353
module_name: str = inspect.currentframe().f_back.f_globals["__name__"]
5454
source_name = module_name.split(".", 1)[0]
55-
url = client.current.web_module_url(source_name, url_or_name)
56-
if url is None:
57-
raise ValueError(
58-
f"{url_or_name!r} is not installed for {source_name!r}"
59-
)
60-
self.url = url
55+
self.url = client.current.web_module_url(source_name, url_or_name)
6156
self.installed = True
6257
if check_exports:
6358
self.exports = client.current.web_module_exports(

idom/client/protocol.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import cast, Optional
1+
from typing import cast, List
22

33
from typing_extensions import Protocol
44

@@ -10,9 +10,15 @@
1010
class ClientImplementation(Protocol):
1111
"""A minimal set of functions required to use :class:`idom.widget.module.Module`"""
1212

13-
def web_module_url(self, source_name: str, package_name: str) -> Optional[str]:
13+
def web_module_url(self, source_name: str, package_name: str) -> str:
1414
"""Return the URL to import the module with the given name."""
1515

16+
def web_module_exports(self, source_name: str, package_name: str) -> List[str]:
17+
"""Return a list of names exported by a Javascript module."""
18+
19+
def web_module_exists(self, source_name: str, package_name: str) -> bool:
20+
"""Whether or not the given web module exists or is installed"""
21+
1622

1723
client_implementation: Ref[ClientImplementation] = Ref(
1824
cast(ClientImplementation, manage)

idom/client/utils.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ def open_modifiable_json(path: Path) -> Iterator[Any]:
1616
json.dump(data, f)
1717

1818

19-
_JS_MODULE_EXPORT_PATTERN = re.compile(r";export{(.*?)};")
20-
_JS_MODULE_EXPORT_NAME_PATTERN = re.compile(r";export (.*?) .*?;")
19+
_JS_VARIABLE_NAME = r"[a-zA-Z_$][0-9a-zA-Z_$]*"
20+
_JS_MODULE_EXPORT_PATTERN = re.compile(f";export{({_JS_VARIABLE_NAME})};")
21+
_JS_MODULE_EXPORT_NAME_PATTERN = re.compile(
22+
f";export ({_JS_VARIABLE_NAME}) {_JS_VARIABLE_NAME};"
23+
)
2124

2225

23-
def find_js_module_exports(path: Path) -> List[str]:
26+
def find_js_module_exports_in_source(content: str) -> List[str]:
2427
names: List[str] = []
25-
if path.suffix != ".js":
26-
# we only know how to do this for javascript modules
27-
return []
28-
with path.open() as f:
29-
content = f.read()
30-
for match in _JS_MODULE_EXPORT_PATTERN.findall(content):
31-
for export in match.split(","):
32-
names.append(export.split(" as ", 1)[1].strip())
33-
names.extend(_JS_MODULE_EXPORT_NAME_PATTERN.findall(content))
28+
for match in _JS_MODULE_EXPORT_PATTERN.findall(content):
29+
for export in match.split(","):
30+
export_parts = export.split(" as ", 1)
31+
print(export_parts)
32+
names.append(export_parts[1].strip())
33+
names.extend(_JS_MODULE_EXPORT_NAME_PATTERN.findall(content))
3434
return names

idom/server/sanic.py

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import json
33
import uuid
44

5-
from typing import Tuple, Any, Dict, Union, Optional, cast
5+
from typing import Tuple, Any, Dict, Union, cast
66

77
from sanic import Blueprint, Sanic, request, response
88
from sanic_cors import CORS
@@ -16,14 +16,14 @@
1616
RecvCoroutine,
1717
)
1818
from idom.core.layout import LayoutEvent
19-
from idom.client.manage import find_client_build_path
19+
from idom.client.manage import BUILD_DIR
2020

2121
from .base import AbstractRenderServer
2222

2323

2424
class Config(TypedDict, total=False):
2525
cors: Union[bool, Dict[str, Any]]
26-
url_prefix: Optional[str]
26+
url_prefix: str
2727
server_static_files: bool
2828
redirect_root_to_index: bool
2929

@@ -37,7 +37,7 @@ def _stop(self) -> None:
3737
def _init_config(self) -> Config:
3838
return Config(
3939
cors=False,
40-
url_prefix=None,
40+
url_prefix="",
4141
server_static_files=True,
4242
redirect_root_to_index=True,
4343
)
@@ -81,30 +81,13 @@ def handler_name(function: Any) -> str:
8181
return f"{blueprint.name}.{function.__name__}"
8282

8383
if config["server_static_files"]:
84+
blueprint.static("/client", str(BUILD_DIR))
8485

85-
@blueprint.route("/client/<path:path>") # type: ignore
86-
async def client_files(
87-
request: request.Request, path: str
88-
) -> response.HTTPResponse:
89-
file_extensions = [".html", ".js", ".json"]
90-
abs_path = find_client_build_path(path)
91-
return (
92-
await response.file_stream(str(abs_path))
93-
if (
94-
abs_path is not None
95-
and abs_path.suffix in file_extensions
96-
and not abs_path.stem.startswith(".")
97-
)
98-
else response.text(f"Could not find: {path!r}", status=404)
99-
)
100-
101-
if config["redirect_root_to_index"]:
86+
if config["server_static_files"] and config["redirect_root_to_index"]:
10287

10388
@blueprint.route("/") # type: ignore
10489
def redirect_to_index(request: request.Request) -> response.HTTPResponse:
105-
return response.redirect(
106-
request.app.url_for(handler_name(client_files), path="index.html")
107-
)
90+
return response.redirect(f"{blueprint.url_prefix}/client/index.html")
10891

10992
def _run_application(
11093
self, app: Sanic, config: Config, args: Tuple[Any, ...], kwargs: Dict[str, Any]

scripts/all_examples.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
from scripts.install_doc_js_modules import install_doc_js_modules
99

1010

11-
install_doc_js_modules()
12-
1311
here = Path(__file__).parent
1412
examples_dir = here.parent / "docs" / "source" / "examples"
1513
sys.path.insert(0, str(examples_dir))
@@ -20,6 +18,8 @@
2018

2119

2220
def main():
21+
install_doc_js_modules()
22+
2323
views = []
2424

2525
for example_file in examples_dir.glob("*.py"):

scripts/install_doc_js_modules.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1-
def install_doc_js_modules(show_spinner=True):
2-
...
1+
from pathlib import Path
2+
3+
from idom.client.build_config import find_build_config_item_in_python_file
4+
from idom.client.manage import build, build_config
5+
6+
7+
DOCS_PATH = Path(__file__).parent.parent / "docs"
8+
9+
10+
def install_doc_js_modules():
11+
config_item = find_build_config_item_in_python_file(
12+
"__main__", DOCS_PATH / "main.py"
13+
)
14+
if (
15+
config_item is not None
16+
and config_item["source_name"] not in build_config().config["by_source"]
17+
):
18+
build([config_item])
319

420

521
if __name__ == "__main__":

scripts/one_example.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
from scripts.install_doc_js_modules import install_doc_js_modules
77

88

9-
install_doc_js_modules()
10-
119
here = Path(__file__).parent
1210
examples_dir = here.parent / "docs" / "source" / "examples"
1311
sys.path.insert(0, str(examples_dir))
@@ -18,6 +16,8 @@
1816

1917

2018
def main():
19+
install_doc_js_modules()
20+
2121
try:
2222
ex_name = sys.argv[1]
2323
except IndexError:

tests/test_client/test_manage.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import pytest
22

3-
from idom.client.manage import web_module_url, web_module_exports, build, restore
3+
from idom.client.manage import (
4+
web_module_url,
5+
web_module_exports,
6+
web_module_exists,
7+
build,
8+
restore,
9+
)
410

511
from tests.general_utils import assert_same_items
612

@@ -15,14 +21,17 @@ def _setup_build_for_tests():
1521

1622

1723
def test_web_module_url():
18-
assert web_module_url("tests", "does/not/exist") is None
1924
assert (
2025
web_module_url("tests", "victory") == "../web_modules/victory-tests-24fa38b.js"
2126
)
2227

2328

29+
def test_web_module_exists():
30+
assert not web_module_exists("tests", "does/not/exist")
31+
assert web_module_exists("tests", "victory")
32+
33+
2434
def test_web_module_exports():
25-
assert web_module_exports("tests", "does/not/exist") == []
2635
assert_same_items(
2736
web_module_exports("tests", "victory"),
2837
[

0 commit comments

Comments
 (0)