1
1
from __future__ import annotations
2
2
3
- import filecmp
3
+ import logging
4
4
import shutil
5
- from dataclasses import dataclass , replace
5
+ from dataclasses import dataclass
6
6
from functools import partial
7
7
from pathlib import Path
8
8
from string import Template
9
- from tempfile import NamedTemporaryFile
10
9
from typing import Any , List , NewType , Optional , Set , Tuple , Union , overload
11
10
from urllib .parse import urlparse
12
11
28
27
)
29
28
30
29
30
+ logger = logging .getLogger (__name__ )
31
+
31
32
SourceType = NewType ("SourceType" , str )
32
33
33
34
NAME_SOURCE = SourceType ("NAME" )
@@ -88,7 +89,6 @@ def module_from_template(
88
89
resolve_exports : bool = IDOM_DEBUG_MODE .current ,
89
90
resolve_exports_depth : int = 5 ,
90
91
unmount_before_update : bool = False ,
91
- replace_existing : bool = False ,
92
92
) -> WebModule :
93
93
"""Create a :class:`WebModule` from a framework template
94
94
@@ -127,9 +127,6 @@ def module_from_template(
127
127
only be used if the imported package failes to re-render when props change.
128
128
Using this option has negative performance consequences since all DOM
129
129
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.
133
130
"""
134
131
# We do this since the package may be any valid URL path. Thus we may need to strip
135
132
# object parameters or query information so we save the resulting template under the
@@ -152,27 +149,14 @@ def module_from_template(
152
149
variables = {"PACKAGE" : package , "CDN" : cdn }
153
150
content = Template (template_file .read_text ()).substitute (variables )
154
151
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
+ )
176
160
177
161
178
162
def module_from_file (
@@ -181,20 +165,16 @@ def module_from_file(
181
165
fallback : Optional [Any ] = None ,
182
166
resolve_exports : bool = IDOM_DEBUG_MODE .current ,
183
167
resolve_exports_depth : int = 5 ,
184
- symlink : bool = False ,
185
168
unmount_before_update : bool = False ,
186
- replace_existing : bool = False ,
169
+ symlink : bool = False ,
187
170
) -> WebModule :
188
- """Load a :class:`WebModule` from a :data:`URL_SOURCE` using a known framework
171
+ """Load a :class:`WebModule` from a given ``file``
189
172
190
173
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.
198
178
fallback:
199
179
What to temporarilly display while the module is being loaded.
200
180
resolve_imports:
@@ -206,9 +186,8 @@ def module_from_file(
206
186
only be used if the imported package failes to re-render when props change.
207
187
Using this option has negative performance consequences since all DOM
208
188
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``.
212
191
"""
213
192
source_file = Path (file )
214
193
target_file = _web_module_path (name )
@@ -222,15 +201,12 @@ def module_from_file(
222
201
and target_file .is_symlink ()
223
202
and target_file .resolve () == source_file .resolve ()
224
203
):
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 )
234
210
235
211
return WebModule (
236
212
source = name + module_name_suffix (name ),
@@ -254,6 +230,59 @@ def _copy_file(target: Path, source: Path, symlink: bool) -> None:
254
230
shutil .copy (source , target )
255
231
256
232
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
+
257
286
class _VdomDictConstructor (Protocol ):
258
287
def __call__ (
259
288
self ,
0 commit comments