Skip to content

Commit 6c62af7

Browse files
committed
log module replacements instead of error
1 parent 3bc38e7 commit 6c62af7

File tree

4 files changed

+101
-59
lines changed

4 files changed

+101
-59
lines changed

docs/source/escape-hatches/_examples/super_simple_chart/app.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
file = Path(__file__).parent / "super-simple-chart.js"
7-
ssc = web.module_from_file("super-simple-chart", file, fallback="⌛", symlink=True)
7+
ssc = web.module_from_file("super-simple-chart", file, fallback="⌛")
88
SuperSimpleChart = web.export(ssc, "SuperSimpleChart")
99

1010

src/idom/web/__init__.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
from .module import export, module_from_file, module_from_template, module_from_url
1+
from .module import (
2+
export,
3+
module_from_file,
4+
module_from_string,
5+
module_from_template,
6+
module_from_url,
7+
)
28

39

410
__all__ = [
511
"module_from_file",
12+
"module_from_string",
613
"module_from_template",
714
"module_from_url",
815
"export",

src/idom/web/module.py

+79-50
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
from __future__ import annotations
22

3-
import filecmp
3+
import logging
44
import shutil
5-
from dataclasses import dataclass, replace
5+
from dataclasses import dataclass
66
from functools import partial
77
from pathlib import Path
88
from string import Template
9-
from tempfile import NamedTemporaryFile
109
from typing import Any, List, NewType, Optional, Set, Tuple, Union, overload
1110
from urllib.parse import urlparse
1211

@@ -28,6 +27,8 @@
2827
)
2928

3029

30+
logger = logging.getLogger(__name__)
31+
3132
SourceType = NewType("SourceType", str)
3233

3334
NAME_SOURCE = SourceType("NAME")
@@ -88,7 +89,6 @@ def module_from_template(
8889
resolve_exports: bool = IDOM_DEBUG_MODE.current,
8990
resolve_exports_depth: int = 5,
9091
unmount_before_update: bool = False,
91-
replace_existing: bool = False,
9292
) -> WebModule:
9393
"""Create a :class:`WebModule` from a framework template
9494
@@ -127,9 +127,6 @@ def module_from_template(
127127
only be used if the imported package failes to re-render when props change.
128128
Using this option has negative performance consequences since all DOM
129129
elements must be changed on each render. See :issue:`461` for more info.
130-
replace_existing:
131-
Whether to replace the source for a module with the same name if it already
132-
exists and has different content. Otherwise raise an error.
133130
"""
134131
# We do this since the package may be any valid URL path. Thus we may need to strip
135132
# object parameters or query information so we save the resulting template under the
@@ -152,27 +149,14 @@ def module_from_template(
152149
variables = {"PACKAGE": package, "CDN": cdn}
153150
content = Template(template_file.read_text()).substitute(variables)
154151

155-
with NamedTemporaryFile(mode="r+") as file:
156-
file.write(content)
157-
file.seek(0) # set the cursor back to begining of file
158-
159-
module = module_from_file(
160-
(
161-
_FROM_TEMPLATE_DIR
162-
+ "/"
163-
+ package_name
164-
+ module_name_suffix(package_name)
165-
),
166-
file.name,
167-
fallback,
168-
resolve_exports,
169-
resolve_exports_depth,
170-
symlink=False,
171-
unmount_before_update=unmount_before_update,
172-
replace_existing=replace_existing,
173-
)
174-
175-
return replace(module, file=None)
152+
return module_from_string(
153+
_FROM_TEMPLATE_DIR + "/" + package_name + module_name_suffix(package_name),
154+
content,
155+
fallback,
156+
resolve_exports,
157+
resolve_exports_depth,
158+
unmount_before_update=unmount_before_update,
159+
)
176160

177161

178162
def module_from_file(
@@ -181,20 +165,16 @@ def module_from_file(
181165
fallback: Optional[Any] = None,
182166
resolve_exports: bool = IDOM_DEBUG_MODE.current,
183167
resolve_exports_depth: int = 5,
184-
symlink: bool = False,
185168
unmount_before_update: bool = False,
186-
replace_existing: bool = False,
169+
symlink: bool = False,
187170
) -> WebModule:
188-
"""Load a :class:`WebModule` from a :data:`URL_SOURCE` using a known framework
171+
"""Load a :class:`WebModule` from a given ``file``
189172
190173
Parameters:
191-
template:
192-
The name of the template to use with the given ``package``
193-
package:
194-
The name of a package to load. May include a file extension (defaults to
195-
``.js`` if not given)
196-
cdn:
197-
Where the package should be loaded from. The CDN must distribute ESM modules
174+
name:
175+
The name of the package
176+
file:
177+
The file from which the content of the web module will be created.
198178
fallback:
199179
What to temporarilly display while the module is being loaded.
200180
resolve_imports:
@@ -206,9 +186,8 @@ def module_from_file(
206186
only be used if the imported package failes to re-render when props change.
207187
Using this option has negative performance consequences since all DOM
208188
elements must be changed on each render. See :issue:`461` for more info.
209-
replace_existing:
210-
Whether to replace the source for a module with the same name if it already
211-
exists and has different content. Otherwise raise an error.
189+
symlink:
190+
Whether the web module should be saved as a symlink to the given ``file``.
212191
"""
213192
source_file = Path(file)
214193
target_file = _web_module_path(name)
@@ -222,15 +201,12 @@ def module_from_file(
222201
and target_file.is_symlink()
223202
and target_file.resolve() == source_file.resolve()
224203
):
225-
if replace_existing:
226-
target_file.unlink()
227-
_copy_file(target_file, source_file, symlink)
228-
elif not filecmp.cmp(
229-
str(source_file.resolve()),
230-
str(target_file.resolve()),
231-
shallow=False,
232-
):
233-
raise FileExistsError(f"{name!r} already exists as {target_file.resolve()}")
204+
logger.info(
205+
f"Existing web module {name!r} will "
206+
f"be replaced with {target_file.resolve()}"
207+
)
208+
target_file.unlink()
209+
_copy_file(target_file, source_file, symlink)
234210

235211
return WebModule(
236212
source=name + module_name_suffix(name),
@@ -254,6 +230,59 @@ def _copy_file(target: Path, source: Path, symlink: bool) -> None:
254230
shutil.copy(source, target)
255231

256232

233+
def module_from_string(
234+
name: str,
235+
content: str,
236+
fallback: Optional[Any] = None,
237+
resolve_exports: bool = IDOM_DEBUG_MODE.current,
238+
resolve_exports_depth: int = 5,
239+
unmount_before_update: bool = False,
240+
):
241+
"""Load a :class:`WebModule` whose ``content`` comes from a string.
242+
243+
Parameters:
244+
name:
245+
The name of the package
246+
content:
247+
The contents of the web module
248+
fallback:
249+
What to temporarilly display while the module is being loaded.
250+
resolve_imports:
251+
Whether to try and find all the named exports of this module.
252+
resolve_exports_depth:
253+
How deeply to search for those exports.
254+
unmount_before_update:
255+
Cause the component to be unmounted before each update. This option should
256+
only be used if the imported package failes to re-render when props change.
257+
Using this option has negative performance consequences since all DOM
258+
elements must be changed on each render. See :issue:`461` for more info.
259+
"""
260+
target_file = _web_module_path(name)
261+
262+
if target_file.exists():
263+
logger.info(
264+
f"Existing web module {name!r} will "
265+
f"be replaced with {target_file.resolve()}"
266+
)
267+
target_file.unlink()
268+
269+
target_file.parent.mkdir(parents=True, exist_ok=True)
270+
target_file.write_text(content)
271+
272+
return WebModule(
273+
source=name + module_name_suffix(name),
274+
source_type=NAME_SOURCE,
275+
default_fallback=fallback,
276+
file=target_file,
277+
export_names=(
278+
resolve_module_exports_from_file(target_file, resolve_exports_depth)
279+
if resolve_exports
280+
else None
281+
),
282+
unmount_before_update=unmount_before_update,
283+
)
284+
285+
257286
class _VdomDictConstructor(Protocol):
258287
def __call__(
259288
self,

tests/test_web/test_module.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import idom
1010
from idom.server.sanic import PerClientStateServer
11-
from idom.testing import ServerMountPoint
11+
from idom.testing import ServerMountPoint, assert_idom_did_not_log, assert_idom_logged
1212
from idom.web.module import NAME_SOURCE, WebModule
1313

1414

@@ -132,7 +132,7 @@ def test_module_from_file_source_conflict(tmp_path):
132132
third_file = tmp_path / "third.js"
133133
third_file.write_text("something-different")
134134

135-
with pytest.raises(FileExistsError, match="already exists"):
135+
with assert_idom_logged(r"Existing web module .* will be replaced with"):
136136
idom.web.module_from_file("temp", third_file)
137137

138138

@@ -154,12 +154,14 @@ def test_web_module_from_file_symlink_twice(tmp_path):
154154
file_1.touch()
155155

156156
idom.web.module_from_file("temp", file_1, symlink=True)
157-
idom.web.module_from_file("temp", file_1, symlink=True)
157+
158+
with assert_idom_did_not_log(r"Existing web module .* will be replaced with"):
159+
idom.web.module_from_file("temp", file_1, symlink=True)
158160

159161
file_2 = tmp_path / "temp_2.js"
160162
file_2.write_text("something")
161163

162-
with pytest.raises(FileExistsError, match="already exists"):
164+
with assert_idom_logged(r"Existing web module .* will be replaced with"):
163165
idom.web.module_from_file("temp", file_2, symlink=True)
164166

165167

@@ -172,11 +174,9 @@ def test_web_module_from_file_replace_existing(tmp_path):
172174
file2 = tmp_path / "temp2.js"
173175
file2.write_text("something")
174176

175-
with pytest.raises(FileExistsError, match="already exists"):
177+
with assert_idom_logged(r"Existing web module .* will be replaced with"):
176178
idom.web.module_from_file("temp", file2)
177179

178-
idom.web.module_from_file("temp", file2, replace_existing=True)
179-
180180

181181
def test_module_missing_exports():
182182
module = WebModule("test", NAME_SOURCE, None, {"a", "b", "c"}, None, False)
@@ -226,3 +226,9 @@ def test_imported_components_can_render_children(driver, display):
226226

227227
for index, child in enumerate(children):
228228
assert child.get_attribute("id") == f"child-{index + 1}"
229+
230+
231+
def test_module_from_string():
232+
idom.web.module_from_string("temp", "")
233+
with assert_idom_logged(r"Existing web module .* will be replaced with"):
234+
idom.web.module_from_string("temp", "")

0 commit comments

Comments
 (0)