@@ -29,6 +29,8 @@ def recursive_update(
2929 types : dict [type | tuple [type , ...], UPDATE_OPTIONS | tuple [UPDATE_OPTIONS , UPDATE_OPTIONS ]] | None = None ,
3030 paths : dict [str , UPDATE_OPTIONS ] | None = None ,
3131 constructor : Callable [[], Any ] | None = None ,
32+ undefined_new : UPDATE_OPTIONS = "write" ,
33+ undefined_existing : UPDATE_OPTIONS = "skip" ,
3234 type_mismatch : UPDATE_OPTIONS = "raise" ,
3335) -> dict [str , list [str ]]:
3436 """Recursively update a complex data structure using another data structure.
@@ -108,9 +110,8 @@ def recursive_update(
108110 types = {list: ("write", lambda data: (data[0] + data[1], "append"))}
109111 ```
110112
111- The default behavior for any data type not specified in this argument is
112- `("write", "skip")`, meaning that the key/attribute will be written in the source data
113- if it does not exist, and ignored if it does.
113+ The default behavior for any data type not specified in this argument
114+ is determined by the `undefined_new` and `undefined_existing` arguments.
114115 paths
115116 Update behavior for specific keys using JSONPath expressions.
116117 This is the same as the `types` argument, but targeting specific keys
@@ -122,6 +123,14 @@ def recursive_update(
122123 This is used when the addon data contains a recursive key/attribute
123124 that is not present in the source data. If not provided,
124125 the type of the addon value will be used to create a new instance.
126+ undefined_new
127+ Behavior for when a non-recursive data with no defined behavior
128+ in the `types` argument is found in the addon data
129+ but not in the source data.
130+ undefined_existing
131+ Behavior for when a non-recursive data with no defined behavior
132+ in the `types` argument is found in the addon data
133+ and in the source data.
125134 type_mismatch
126135 Behavior for when a key/attribute in the source data
127136 is not a recursive type, but the corresponding key/attribute
@@ -176,8 +185,12 @@ def raise_error(typ: Literal["duplicate", "type_mismatch"]):
176185 fn_add_items , _ , _ , _ , fn_add_construct = add_funcs
177186
178187 for key , value in fn_add_items (add ):
179- fullpath = f"{ path } .{ key } "
180- full_jpath = _jsonpath .parse (f"{ path } .'{ key } '" ) # quote to avoid JSONPath syntax errors
188+ fullpath = f"{ path } .'{ key } '"
189+ try :
190+ full_jpath = _jsonpath .parse (fullpath ) # quote to avoid JSONPath syntax errors
191+ except Exception as e :
192+ print (fullpath )
193+ raise e
181194 key_exists_in_src = fn_src_contains (src , key )
182195 source_value = fn_src_get (src , key ) if key_exists_in_src else None
183196 for jpath_str , matches in jsonpath_match .items ():
@@ -221,7 +234,7 @@ def raise_error(typ: Literal["duplicate", "type_mismatch"]):
221234 else :
222235 # addon value is of a non-recursive type that does not have any defined behavior;
223236 # Apply the default behavior for of ("write", "skip") for the key.
224- apply ("skip" if key_exists_in_src else "write" )
237+ apply (undefined_existing if key_exists_in_src else undefined_new )
225238 return
226239
227240 type_to_arg = {list : ("write" , lambda data : (data [0 ] + data [1 ], "append" ))} | (types or {})
@@ -307,6 +320,7 @@ def __init__(
307320 relative_key_key : str | None = None ,
308321 implicit_root : bool = True ,
309322 getter_function_name : str = "get" ,
323+ skip_key_func : Callable [[list [str ]], bool ] | None = None ,
310324 ):
311325 self ._marker_start_value = marker_start_value
312326 self ._marker_end_value = marker_end_value
@@ -329,6 +343,7 @@ def __init__(
329343 self ._template_keys = relative_template_keys or []
330344 self ._relative_key_key = relative_key_key
331345 self ._getter_function_name = getter_function_name
346+ self ._skip_func = skip_key_func
332347
333348 self ._pattern_value : dict [int , _RegexPattern ] = {}
334349 self ._data = None
@@ -368,7 +383,11 @@ def getter_function(path: str, default: Any = None, search: bool = False):
368383 code_lines = ["def __inline_code__():" ]
369384 code_lines .extend ([f" { line } " for line in code_str .strip ("\n " ).splitlines ()])
370385 code_str_full = "\n " .join (code_lines )
371- global_context = self ._code_context .copy () | {self ._getter_function_name : getter_function }
386+ global_context = self ._code_context .copy () | {
387+ self ._getter_function_name : getter_function ,
388+ "__current_path__" : current_path ,
389+ "__relative_path_anchor__" : relative_path_anchor
390+ }
372391 for name , partial_func_data in self ._code_context_partial .items ():
373392 if isinstance (partial_func_data , tuple ):
374393 func , arg_name = partial_func_data
@@ -428,6 +447,7 @@ def get_address_value(match: _re.Match | str, return_all_matches: bool = False,
428447 return output , True
429448 return output
430449 path_expr = self ._concat_json_paths (root_path_expr , path_expr )
450+ # print("IN GET ADD VAL", path_expr)
431451 cached_result = self ._visited_paths .get (path_expr )
432452 if cached_result :
433453 value , matched = cached_result
@@ -535,6 +555,11 @@ def raise_error(path_invalid: str, description_template: str, exception: Excepti
535555 template_end = self ._marker_end_value ,
536556 ) from exception
537557
558+ # print("IN MAIN", self._extract_fields(current_path))
559+
560+ if self ._skip_func and self ._skip_func (self ._extract_fields (current_path )):
561+ return templ
562+
538563 if current_path in self ._visited_paths :
539564 return self ._visited_paths [current_path ][0 ]
540565
0 commit comments