11from __future__ import annotations
22
3- import filecmp
3+ import logging
44import shutil
5- from dataclasses import dataclass , replace
5+ from dataclasses import dataclass
66from functools import partial
77from pathlib import Path
88from string import Template
9- from tempfile import NamedTemporaryFile
109from typing import Any , List , NewType , Optional , Set , Tuple , Union , overload
1110from urllib .parse import urlparse
1211
2827)
2928
3029
30+ logger = logging .getLogger (__name__ )
31+
3132SourceType = NewType ("SourceType" , str )
3233
3334NAME_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
178162def 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+
257286class _VdomDictConstructor (Protocol ):
258287 def __call__ (
259288 self ,
0 commit comments