33import json
44import ast
55from copy import deepcopy
6- from functools import wraps
7- from contextlib import contextmanager
86from hashlib import sha256
97from pathlib import Path
108from importlib .machinery import SourceFileLoader
1412 Dict ,
1513 Optional ,
1614 Any ,
17- Iterator ,
1815 Iterable ,
1916 TypeVar ,
2017 Tuple ,
21- Callable ,
22- TypedDict ,
18+ NamedTuple ,
2319)
2420
2521from jsonschema import validate as validate_schema
2622
2723import idom
2824
2925
30- _Self = TypeVar ("_Self" )
31- _Method = TypeVar ("_Method" , bound = Callable [..., Any ])
32-
33- BuildConfigItem = Dict [str , Any ]
34-
35-
36- def _requires_open_transaction (method : _Method ) -> _Method :
37- @wraps (method )
38- def wrapper (self : BuildConfig , * args : Any , ** kwargs : Any ) -> Any :
39- if not self ._transaction_open :
40- raise RuntimeError (
41- f"BuildConfig method { method .__name__ !r} must be used in a transaction."
42- )
43- return method (self , * args , ** kwargs )
44-
45- return wrapper
46-
47-
48- def _modified_by_transaction (method : _Method ) -> _Method :
49- @wraps (method )
50- def wrapper (self : BuildConfig , * args : Any , ** kwargs : Any ) -> Any :
51- if self ._transaction_open :
52- raise RuntimeError (
53- f"BuildConfig method { method .__name__ !r} cannot be used in a transaction."
54- )
55- return method (self , * args , ** kwargs )
56-
57- return wrapper
26+ ConfigItem = Dict [str , Any ]
5827
5928
6029class BuildConfig :
6130
62- __slots__ = "config " , "_path" , "_transaction_open" , "_derived_properties "
31+ __slots__ = "data " , "_path" , "_item_info "
6332 _filename = "idom-build-config.json"
64- _default_config = {"version" : idom .__version__ , "by_source " : {}}
33+ _default_config = {"version" : idom .__version__ , "items " : {}}
6534
6635 def __init__ (self , path : Path ) -> None :
6736 self ._path = path / self ._filename
68- self .config = self ._load ()
69- self ._derived_properties = derive_config_properties (self .config )
70- self ._transaction_open = False
71-
72- @contextmanager
73- def transaction (self : _Self ) -> Iterator [_Self ]:
74- """Open a transaction to modify the config file state"""
75- self ._transaction_open = True
76- old_config = deepcopy (self .config )
77- try :
78- yield self
79- except Exception :
80- self .config = old_config
81- raise
82- else :
83- self ._save ()
84- self ._derived_properties = derive_config_properties (self .config )
85- finally :
86- self ._transaction_open = False
37+ self .data = self ._load ()
38+ self ._item_info : Dict [str , ConfigItemInfo ] = {
39+ name : derive_config_item_info (item )
40+ for name , item in self .data ["items" ].items ()
41+ }
8742
88- @_requires_open_transaction
89- def update_config_items (self , config_items : Iterable [BuildConfigItem ]) -> None :
43+ def update (self , config_items : Iterable [ConfigItem ]) -> None :
9044 for conf in map (validate_config_item , config_items ):
91- self .config ["by_source" ][conf ["source_name" ]] = conf
45+ src_name = conf ["source_name" ]
46+ self .data ["items" ][src_name ] = conf
47+ self ._item_info [src_name ] = derive_config_item_info (conf )
48+
49+ def has_config_item (self , source_name : str ) -> bool :
50+ return source_name in self .data ["items" ]
9251
93- @_modified_by_transaction
9452 def get_js_dependency_alias (
9553 self ,
9654 source_name : str ,
9755 dependency_name : str ,
9856 ) -> Optional [str ]:
99- aliases_by_src = self ._derived_properties ["js_dependency_aliases_by_source" ]
10057 try :
101- return aliases_by_src [source_name ][dependency_name ]
58+ return self . _item_info [source_name ]. js_dependency_aliases [dependency_name ]
10259 except KeyError :
10360 return None
10461
105- @_modified_by_transaction
10662 def all_aliased_js_dependencies (self ) -> List [str ]:
10763 return [
10864 dep
109- for aliased_deps in self ._derived_properties [
110- "aliased_js_dependencies_by_source"
111- ].values ()
112- for dep in aliased_deps
65+ for info in self ._item_info .values ()
66+ for dep in info .aliased_js_dependencies
11367 ]
11468
115- @_modified_by_transaction
11669 def all_js_dependency_aliases (self ) -> List [str ]:
11770 return [
118- als
119- for aliases in self ._derived_properties [
120- "js_dependency_aliases_by_source"
121- ].values ()
122- for als in aliases .values ()
71+ alias
72+ for info in self ._item_info .values ()
73+ for alias in info .js_dependency_aliases .values ()
12374 ]
12475
76+ def save (self ) -> None :
77+ with self ._path .open ("w" ) as f :
78+ json .dump (validate_config (self .data ), f )
79+
12580 def _load (self ) -> Dict [str , Any ]:
12681 if not self ._path .exists ():
12782 return deepcopy (self ._default_config )
@@ -131,17 +86,13 @@ def _load(self) -> Dict[str, Any]:
13186 json .loads (f .read () or "null" ) or self ._default_config
13287 )
13388
134- def _save (self ) -> None :
135- with self ._path .open ("w" ) as f :
136- json .dump (validate_config (self .config ), f )
137-
13889 def __repr__ (self ) -> str :
139- return f"{ type (self ).__name__ } ({ self .config } )"
90+ return f"{ type (self ).__name__ } ({ self .data } )"
14091
14192
14293def find_python_packages_build_config_items (
14394 paths : Optional [List [str ]] = None ,
144- ) -> Tuple [List [BuildConfigItem ], List [Exception ]]:
95+ ) -> Tuple [List [ConfigItem ], List [Exception ]]:
14596 """Find javascript dependencies declared by Python modules
14697
14798 Parameters:
@@ -153,7 +104,7 @@ def find_python_packages_build_config_items(
153104 Mapping of module names to their corresponding list of discovered dependencies.
154105 """
155106 failures : List [Tuple [str , Exception ]] = []
156- build_configs : List [BuildConfigItem ] = []
107+ build_configs : List [ConfigItem ] = []
157108 for module_info in iter_modules (paths ):
158109 module_name = module_info .name
159110 module_loader = module_info .module_finder .find_module (module_name )
@@ -175,14 +126,14 @@ def find_python_packages_build_config_items(
175126
176127def find_build_config_item_in_python_file (
177128 module_name : str , path : Path
178- ) -> Optional [BuildConfigItem ]:
129+ ) -> Optional [ConfigItem ]:
179130 with path .open () as f :
180131 return find_build_config_item_in_python_source (module_name , f .read ())
181132
182133
183134def find_build_config_item_in_python_source (
184135 module_name : str , module_src : str
185- ) -> Optional [BuildConfigItem ]:
136+ ) -> Optional [ConfigItem ]:
186137 for node in ast .parse (module_src ).body :
187138 if isinstance (node , ast .Assign ) and (
188139 len (node .targets ) == 1
@@ -212,28 +163,13 @@ def split_package_name_and_version(pkg: str) -> Tuple[str, str]:
212163 return pkg , ""
213164
214165
215- class _DerivedConfigProperties (TypedDict ):
216- js_dependency_aliases_by_source : Dict [str , Dict [str , str ]]
217- aliased_js_dependencies_by_source : Dict [str , List [str ]]
218-
166+ class ConfigItemInfo (NamedTuple ):
167+ js_dependency_aliases : Dict [str , str ]
168+ aliased_js_dependencies : List [str ]
219169
220- def derive_config_properties (config : Dict [str , Any ]) -> _DerivedConfigProperties :
221- js_dependency_aliases_by_source = {}
222- aliased_js_dependencies_by_source = {}
223- for src , cfg in config ["by_source" ].items ():
224- cfg_hash = _hash_config_item (cfg )
225- aliases , aliased_js_deps = _config_item_js_dependencies (cfg , cfg_hash )
226- js_dependency_aliases_by_source [src ] = aliases
227- aliased_js_dependencies_by_source [src ] = aliased_js_deps
228- return {
229- "js_dependency_aliases_by_source" : js_dependency_aliases_by_source ,
230- "aliased_js_dependencies_by_source" : aliased_js_dependencies_by_source ,
231- }
232170
233-
234- def _config_item_js_dependencies (
235- config_item : Dict [str , Any ], config_hash : str
236- ) -> Tuple [Dict [str , str ], List [str ]]:
171+ def derive_config_item_info (config_item : Dict [str , Any ]) -> ConfigItemInfo :
172+ config_hash = _hash_config_item (config_item )
237173 alias_suffix = f"{ config_item ['source_name' ]} -{ config_hash } "
238174 aliases : Dict [str , str ] = {}
239175 aliased_js_deps : List [str ] = []
@@ -242,7 +178,10 @@ def _config_item_js_dependencies(
242178 dep_alias = f"{ dep_name } -{ alias_suffix } "
243179 aliases [dep_name ] = dep_alias
244180 aliased_js_deps .append (f"{ dep_alias } @npm:{ dep } " )
245- return aliases , aliased_js_deps
181+ return ConfigItemInfo (
182+ js_dependency_aliases = aliases ,
183+ aliased_js_dependencies = aliased_js_deps ,
184+ )
246185
247186
248187def _hash_config_item (config_item : Dict [str , Any ]) -> str :
@@ -259,7 +198,7 @@ def _hash_config_item(config_item: Dict[str, Any]) -> str:
259198 "type" : "object" ,
260199 "properties" : {
261200 "version" : {"type" : "string" },
262- "by_source " : {
201+ "items " : {
263202 "type" : "object" ,
264203 "patternProperties" : {".*" : {"$ref" : "#/definitions/ConfigItem" }},
265204 },
@@ -278,6 +217,7 @@ def _hash_config_item(config_item: Dict[str, Any]) -> str:
278217 },
279218 },
280219 "requiredProperties" : ["source_name" ],
220+ "additionalProperties" : False ,
281221 }
282222 },
283223}
0 commit comments