1515 Optional ,
1616 Any ,
1717 Iterator ,
18+ Iterable ,
1819 TypeVar ,
1920 Tuple ,
2021 Callable ,
2122 TypedDict ,
2223)
2324
24- from fastjsonschema import compile as compile_schema
25+ from jsonschema import validate as validate_schema
2526
2627import idom
27- from .utils import split_package_name_and_version
2828
2929
3030_Self = TypeVar ("_Self" )
3131_Method = TypeVar ("_Method" , bound = Callable [..., Any ])
3232
33- _ConfigItem = Dict [str , Any ]
33+ BuildConfigItem = Dict [str , Any ]
3434
3535
3636def _requires_open_transaction (method : _Method ) -> _Method :
@@ -43,16 +43,26 @@ def wrapper(self: BuildConfig, *args: Any, **kwargs: Any) -> Any:
4343 return wrapper
4444
4545
46+ def _modified_by_transaction (method : _Method ) -> _Method :
47+ @wraps (method )
48+ def wrapper (self : BuildConfig , * args : Any , ** kwargs : Any ) -> Any :
49+ if self ._transaction_open :
50+ raise RuntimeError ("Wait for transaction to end before using this method." )
51+ return method (self , * args , ** kwargs )
52+
53+ return wrapper
54+
55+
4656class BuildConfig :
4757
48- __slots__ = "config" , "_path" , "_transaction_open"
58+ __slots__ = "config" , "_path" , "_transaction_open" , "_derived_properties"
4959 _filename = "idom-build-config.json"
5060 _default_config = {"version" : idom .__version__ , "by_source" : {}}
5161
5262 def __init__ (self , path : Path ) -> None :
5363 self ._path = path / self ._filename
5464 self .config = self ._load ()
55- self ._derived_properties = _derive_config_properties (self .config )
65+ self ._derived_properties = derive_config_properties (self .config )
5666 self ._transaction_open = False
5767
5868 @contextmanager
@@ -67,13 +77,21 @@ def transaction(self: _Self) -> Iterator[_Self]:
6777 raise
6878 else :
6979 self ._save ()
80+ self ._derived_properties = derive_config_properties (self .config )
7081 finally :
7182 self ._transaction_open = False
7283
84+ @_requires_open_transaction
85+ def update_config_items (self , config_items : Iterable [BuildConfigItem ]) -> None :
86+ for conf in map (validate_config_item , config_items ):
87+ self .config ["by_source" ][conf ["source_name" ]] = conf
88+
89+ @_modified_by_transaction
7390 def get_js_dependency_alias (self , source_name : str , dependency_name : str ) -> str :
7491 aliases_by_src = self ._derived_properties ["js_dependency_aliases_by_source" ]
7592 return aliases_by_src [source_name ][dependency_name ]
7693
94+ @_modified_by_transaction
7795 def all_aliased_js_dependencies (self ) -> List [str ]:
7896 return [
7997 dep
@@ -83,27 +101,43 @@ def all_aliased_js_dependencies(self) -> List[str]:
83101 for dep in aliased_deps
84102 ]
85103
104+ @_modified_by_transaction
105+ def all_js_dependency_aliases (self ) -> List [str ]:
106+ return [
107+ als
108+ for aliases in self ._derived_properties [
109+ "js_dependency_aliases_by_source"
110+ ].values ()
111+ for als in aliases
112+ ]
113+
86114 def _load (self ) -> Dict [str , Any ]:
87- with self ._path .open () as f :
88- return validate_config (
89- json .loads (f .read () or "null" ) or self ._default_config
90- )
115+ if not self ._path .exists ():
116+ return deepcopy (self ._default_config )
117+ else :
118+ with self ._path .open () as f :
119+ return validate_config (
120+ json .loads (f .read () or "null" ) or self ._default_config
121+ )
91122
92123 def _save (self ) -> None :
93124 with self ._path .open ("w" ) as f :
94125 json .dump (validate_config (self .config ), f )
95126
127+ def __repr__ (self ) -> str :
128+ return f"{ type (self ).__name__ } ({ self .config } )"
129+
96130
97131def find_build_config_item_in_python_file (
98132 module_name : str , path : Path
99- ) -> Optional [_ConfigItem ]:
133+ ) -> Optional [BuildConfigItem ]:
100134 with path .open () as f :
101135 return find_build_config_item_in_python_source (module_name , f .read ())
102136
103137
104138def find_python_packages_build_config_items (
105139 paths : Optional [List [str ]] = None ,
106- ) -> Tuple [List [_ConfigItem ], List [Exception ]]:
140+ ) -> Tuple [List [BuildConfigItem ], List [Exception ]]:
107141 """Find javascript dependencies declared by Python modules
108142
109143 Parameters:
@@ -115,7 +149,7 @@ def find_python_packages_build_config_items(
115149 Mapping of module names to their corresponding list of discovered dependencies.
116150 """
117151 failures : List [Tuple [str , Exception ]] = []
118- build_configs : List [_ConfigItem ] = []
152+ build_configs : List [BuildConfigItem ] = []
119153 for module_info in iter_modules (paths ):
120154 module_name = module_info .name
121155 module_loader = module_info .module_finder .find_module (module_name )
@@ -137,7 +171,7 @@ def find_python_packages_build_config_items(
137171
138172def find_build_config_item_in_python_source (
139173 module_name : str , module_src : str
140- ) -> Optional [_ConfigItem ]:
174+ ) -> Optional [BuildConfigItem ]:
141175 for node in ast .parse (module_src ).body :
142176 if isinstance (node , ast .Assign ) and (
143177 len (node .targets ) == 1
@@ -153,12 +187,26 @@ def find_build_config_item_in_python_source(
153187 return None
154188
155189
190+ def split_package_name_and_version (pkg : str ) -> Tuple [str , str ]:
191+ at_count = pkg .count ("@" )
192+ if pkg .startswith ("@" ):
193+ if at_count == 1 :
194+ return pkg , ""
195+ else :
196+ name , version = pkg [1 :].split ("@" , 1 )
197+ return ("@" + name ), version
198+ elif at_count :
199+ return tuple (pkg .split ("@" , 1 ))
200+ else :
201+ return pkg , ""
202+
203+
156204class _DerivedConfigProperties (TypedDict ):
157205 js_dependency_aliases_by_source : Dict [str , Dict [str , str ]]
158206 aliased_js_dependencies_by_source : Dict [str , List [str ]]
159207
160208
161- def _derive_config_properties (config : Dict [str , Any ]) -> _DerivedConfigProperties :
209+ def derive_config_properties (config : Dict [str , Any ]) -> _DerivedConfigProperties :
162210 js_dependency_aliases_by_source = {}
163211 aliased_js_dependencies_by_source = {}
164212 for src , cfg in config ["by_source" ].items ():
@@ -178,7 +226,7 @@ def _config_item_js_dependencies(
178226 alias_suffix = f"{ config_item ['source_name' ]} -{ config_hash } "
179227 aliases : Dict [str , str ] = {}
180228 aliased_js_deps : List [str ] = []
181- for dep in config_item [ "js_dependencies" ] :
229+ for dep in config_item . get ( "js_dependencies" , []) :
182230 dep_name = split_package_name_and_version (dep )[0 ]
183231 dep_alias = f"{ dep_name } -{ alias_suffix } "
184232 aliases [dep_name ] = dep_alias
@@ -209,16 +257,29 @@ def _hash_config_item(config_item: Dict[str, Any]) -> str:
209257 "ConfigItem" : {
210258 "type" : "object" ,
211259 "properties" : {
212- "source_name" : {"type" : "string" },
260+ "source_name" : {
261+ "type" : "string" ,
262+ "pattern" : r"^[\w\d\-]+$" ,
263+ },
213264 "js_dependencies" : {
214265 "type" : "array" ,
215266 "items" : {"type" : "string" },
216267 },
217268 },
269+ "requiredProperties" : ["source_name" ],
218270 }
219271 },
220272}
221273
222274
223- validate_config = compile_schema (_CONFIG_SCHEMA )
224- validate_config_item = compile_schema (_CONFIG_SCHEMA ["definitions" ]["ConfigItem" ])
275+ _V = TypeVar ("_V" )
276+
277+
278+ def validate_config (value : _V ) -> _V :
279+ validate_schema (value , _CONFIG_SCHEMA )
280+ return value
281+
282+
283+ def validate_config_item (value : _V ) -> _V :
284+ validate_schema (value , _CONFIG_SCHEMA ["definitions" ]["ConfigItem" ])
285+ return value
0 commit comments