Skip to content

Commit 469379a

Browse files
committed
various cleanup and docs to allow no install in Module
1 parent a4a50cc commit 469379a

File tree

9 files changed

+91
-46
lines changed

9 files changed

+91
-46
lines changed

Diff for: idom/__main__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ def run(args: argparse.Namespace) -> None:
5656
)
5757

5858
if args.command == "install":
59-
install(args.dependencies or [], args.exports or [])
59+
install(args.dependencies or [], args.exports or [], show_spinner=True)
6060
elif args.command == "uninstall":
6161
delete_web_modules(args.dependencies)
6262
elif args.command == "restore":
63-
restore()
63+
restore(show_spinner=True)
6464
else:
6565
print("Installed:")
6666
for name in installed():

Diff for: idom/client/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
delete_web_modules,
55
register_web_module,
66
web_module_exists,
7-
web_module_path,
87
install,
98
installed,
109
restore,

Diff for: idom/client/manage.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ def find_path(url_path: str) -> Optional[Path]:
2727
return STATIC_SHIMS.get(url_path)
2828

2929

30-
def web_module_path(name: str) -> Optional[Path]:
31-
return find_path(f"web_modules/{name}.js")
32-
33-
3430
def web_module_url(name: str) -> str:
3531
path = f"../{WEB_MODULES_DIR.name}/{name}.js"
3632
if not web_module_exists(name):
@@ -94,7 +90,9 @@ def installed() -> List[str]:
9490
return list(sorted(names))
9591

9692

97-
def install(packages: Sequence[str], exports: Sequence[str] = ()) -> None:
93+
def install(
94+
packages: Sequence[str], exports: Sequence[str] = (), show_spinner: bool = False
95+
) -> None:
9896
with TemporaryDirectory() as tempdir:
9997
tempdir_path = Path(tempdir)
10098
temp_static_dir = tempdir_path / "static"
@@ -115,7 +113,7 @@ def install(packages: Sequence[str], exports: Sequence[str] = ()) -> None:
115113
with (temp_static_dir / "package.json").open("w+") as f:
116114
json.dump(package_json, f)
117115

118-
with Spinner(f"Installing: {', '.join(packages)}"):
116+
with Spinner(f"Installing: {', '.join(packages)}", show=show_spinner):
119117
_run_subprocess(["npm", "install"], temp_static_dir)
120118
_run_subprocess(["npm", "install"] + cache.package_list, temp_static_dir)
121119
_run_subprocess(["npm", "run", "build"], temp_static_dir)
@@ -128,8 +126,8 @@ def install(packages: Sequence[str], exports: Sequence[str] = ()) -> None:
128126
shutil.copytree(temp_build_dir, BUILD_DIR, symlinks=True)
129127

130128

131-
def restore() -> None:
132-
with Spinner("Restoring"):
129+
def restore(show_spinner: bool = False) -> None:
130+
with Spinner("Restoring", show=show_spinner):
133131
_delete_os_paths(BUILD_DIR)
134132
_run_subprocess(["npm", "install"], APP_DIR)
135133
_run_subprocess(["npm", "run", "build"], APP_DIR)

Diff for: idom/client/utils.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ def __init__(
2525
failure_frame: str = "✖",
2626
success_frame: str = "✔",
2727
rate: float = 0.1,
28-
display: Optional[Callable[[Optional[str], str], None]] = None,
28+
show: bool = True,
29+
display_function: Optional[Callable[[Optional[str], str], None]] = None,
2930
) -> None:
3031
self.text = text
3132
self.frames = frames
@@ -34,8 +35,11 @@ def __init__(
3435
self.rate = rate
3536
self._stop = Event()
3637
self._stopped = Event()
37-
if display is not None:
38-
self.display = display
38+
if not show:
39+
# do nothing on display
40+
self.display = lambda *a, **kw: None
41+
elif display_function is not None:
42+
self.display = display_function
3943
else:
4044
self.display = (
4145
self._display_notebook

Diff for: idom/widgets/module.py

+55-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pathlib import Path
22
from typing import Any, Optional, Union
3+
from urllib.parse import urlparse
34

45
from idom import client
56
from idom.core.vdom import VdomDict, ImportSourceDict, make_vdom_constructor
@@ -9,49 +10,72 @@ class Module:
910
"""A Javascript module
1011
1112
Parameters:
12-
name:
13-
If the module is installed, or ``source`` is not None, then this is the name
14-
the the module to import from (omit the ``.js`` file extension). Otherwise
15-
this is the URl (relative or absolute) to import from.
16-
source:
17-
Create a module of the given name using the given source code.
18-
replace:
19-
Overwrite a module defined from ``source`` if one of the same ``name``
20-
already exists, otherwise raise a ``ValueError`` complaining of name
21-
conflict.
22-
23-
Returns:
24-
An :class:`Import` element for the newly defined module.
13+
path:
14+
The URL to an ECMAScript module which exports React components
15+
(*with* a ``.js`` file extension) or name of a module installed in the
16+
built-in client application (*without* a ``.js`` file extension).
17+
source_file:
18+
Only applicable if running on the built-in client app. Dynamically install
19+
the code in the give file as a single-file module. The built-in client will
20+
inject this module adjacent to other installed modules which means they can
21+
be imported via a relative path (e.g. ``./some-other-installed-module.js``).
22+
23+
.. warning::
24+
25+
Do not use the ``source_file`` parameter if not running with the client app
26+
distributed with ``idom``.
27+
28+
Examples:
29+
.. testcode::
30+
31+
import idom
32+
2533
"""
2634

2735
__slots__ = "_module", "_installed"
2836

2937
def __init__(
3038
self,
31-
name: str,
32-
source: Optional[Union[str, Path]] = None,
33-
replace: bool = False,
39+
path: str,
40+
source_file: Optional[Union[str, Path]] = None,
3441
) -> None:
3542
self._installed = False
36-
if source is not None:
37-
if replace:
38-
client.delete_web_modules([name], skip_missing=True)
39-
self._module = client.register_web_module(name, source)
43+
if source_file is not None:
44+
self._module = client.register_web_module(path, source_file)
4045
self._installed = True
41-
elif client.web_module_exists(name):
42-
self._module = client.web_module_url(name)
46+
elif client.web_module_exists(path):
47+
self._module = client.web_module_url(path)
48+
self._installed = True
49+
elif not _is_url(path):
50+
raise ValueError(
51+
f"{path!r} is not installed - "
52+
"only installed modules can omit a file extension."
53+
)
4354
else:
44-
self._module = name
55+
self._module = path
4556

4657
@property
4758
def installed(self) -> bool:
59+
"""Whether or not this module has been installed into the built-in client app."""
4860
return self._installed
4961

5062
@property
5163
def url(self) -> str:
64+
"""The path this module will be imported from"""
5265
return self._module
5366

5467
def Import(self, name: str, *args: Any, **kwargs: Any) -> "Import":
68+
"""Return an :class:`Import` for the given :class:`Module` and ``name``
69+
70+
This roughly translates to the javascript statement
71+
72+
.. code-block:: javascript
73+
74+
import { name } from "module"
75+
76+
Where ``name`` is the given name, and ``module`` is the :attr:`Module.url` of
77+
this :class:`Module` instance.
78+
"""
5579
return Import(self._module, name, *args, **kwargs)
5680

5781
def __repr__(self) -> str: # pragma: no cover
@@ -95,3 +119,11 @@ def __call__(
95119
def __repr__(self) -> str: # pragma: no cover
96120
items = ", ".join(f"{k}={v!r}" for k, v in self._import_source.items())
97121
return f"{type(self).__name__}({items})"
122+
123+
124+
def _is_url(string: str) -> bool:
125+
if string.startswith("/") or string.startswith("./") or string.startswith("../"):
126+
return True
127+
else:
128+
parsed = urlparse(string)
129+
return parsed.scheme and parsed.netloc

Diff for: tests/test_client/test_manage.py

+5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ def test_install_namespace_package():
3333
assert client.web_module_url("@material-ui/core") == expected
3434

3535

36+
def test_error_on_delete_not_exists():
37+
with pytest.raises(ValueError, match=r"Module .*? does not exist"):
38+
client.delete_web_modules("module/that/does/not/exist")
39+
40+
3641
def test_raise_on_missing_import_path():
3742
with pytest.raises(ValueError, match="does not exist"):
3843
client.web_module_url("module/that/does/not/exist")

Diff for: tests/test_client/test_utils.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def save_display(frame, text):
1919
success_frame="success",
2020
failure_frame="failure",
2121
rate=0, # go as fast as we can
22-
display=save_display,
22+
display_function=save_display,
2323
)
2424

2525
seen_frames = set()
@@ -50,6 +50,7 @@ def save_display(frame, text):
5050
if frame in seen_frames:
5151
raise ValueError("we raised this on purpose")
5252
seen_frames.add(frame)
53+
5354
# grab the failure frame
5455
while not displayed_frames.empty():
5556
seen_frames.add(displayed_frames.get())
File renamed without changes.

Diff for: tests/test_widgets/test_module.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,25 @@
99
HERE = Path(__file__).parent
1010

1111

12-
def test_module_cannot_have_source_and_install():
13-
with pytest.raises(ValueError, match=r"Both .* were given."):
14-
idom.Module("something", install="something", source=HERE / "something.js")
15-
16-
1712
@pytest.fixture
1813
def victory():
1914
if "victory" not in client.installed():
2015
client.install(["victory"], [])
2116
return Module("victory")
2217

2318

19+
def test_non_url_must_be_installed():
20+
with pytest.raises(ValueError, match="not installed"):
21+
Module("module/not/installed")
22+
23+
24+
def test_any_relative_or_abolute_url_allowed():
25+
Module("/absolute/url/module")
26+
Module("./relative/url/module")
27+
Module("../relative/url/module")
28+
Module("http://someurl.com/module")
29+
30+
2431
@pytest.mark.slow
2532
def test_installed_module(driver, display, victory):
2633
assert victory.installed
@@ -35,7 +42,7 @@ def test_reference_pre_installed_module(victory):
3542

3643
@pytest.mark.slow
3744
def test_custom_module(driver, display, victory):
38-
my_chart = Module("my/chart", source=HERE / "my_chart.js")
45+
my_chart = Module("my/chart", source_file=HERE / "my-chart.js")
3946

4047
assert client.web_module_exists("my/chart")
4148
assert client.web_module_url("my/chart") == "../web_modules/my/chart.js"
@@ -44,8 +51,7 @@ def test_custom_module(driver, display, victory):
4451

4552
driver.find_element_by_class_name("VictoryContainer")
4653

47-
my_chart.delete()
48-
54+
client.delete_web_modules("my/chart")
4955
assert not client.web_module_exists("my/chart")
5056

5157

0 commit comments

Comments
 (0)