diff --git a/pyproject.toml b/pyproject.toml index b888d65..1484c6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ namespaces = true # ----------------------------------------- Project Metadata ------------------------------------- # [project] -version = "0.0.0.dev49" +version = "0.0.0.dev50" name = "PySerials" dependencies = [ "jsonschema >= 4.21.0, < 5", @@ -26,8 +26,8 @@ dependencies = [ "ruamel.yaml >= 0.17.32, < 0.18", # https://yaml.readthedocs.io/en/stable/ "ruamel.yaml.string >= 0.1.1, < 1", "tomlkit >= 0.11.8, < 0.12", # https://tomlkit.readthedocs.io/en/stable/, - "MDit == 0.0.0.dev46", - "ExceptionMan == 0.0.0.dev46", + "MDit == 0.0.0.dev47", + "ExceptionMan == 0.0.0.dev47", "ProtocolMan == 0.0.0.dev2", ] requires-python = ">=3.10" diff --git a/requirements.txt b/requirements.txt index 758e3e4..676cb6f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,6 @@ jsonpath-ng >= 1.6.1, < 2 ruamel.yaml >= 0.17.32, < 0.18 ruamel.yaml.string >= 0.1.1, < 1 tomlkit >= 0.11.8, < 0.12 -MDit == 0.0.0.dev46 -ExceptionMan == 0.0.0.dev46 +MDit == 0.0.0.dev47 +ExceptionMan == 0.0.0.dev47 ProtocolMan == 0.0.0.dev2 \ No newline at end of file diff --git a/src/pyserials/exception/update.py b/src/pyserials/exception/update.py index a8b8b03..ee3a639 100644 --- a/src/pyserials/exception/update.py +++ b/src/pyserials/exception/update.py @@ -123,15 +123,15 @@ def __init__( template_start: str, template_end: str, ): - self.path_invalid = path_invalid.replace("'", "") + self.path_invalid = path_invalid self.data_source = data_source self.template_start = template_start self.template_end = template_end parts = description_template.split("{path_invalid}") if len(parts) > 1: - parts.insert(1, _mdit.element.code_span(self.path_invalid)) + parts.insert(1, _mdit.element.code_span(str(self.path_invalid))) super().__init__( - path=path.replace("'", ""), + path=str(path), data=data, data_full=data_full, problem=_mdit.inline_container(*parts), diff --git a/src/pyserials/update.py b/src/pyserials/update.py index 678685a..90e51a5 100644 --- a/src/pyserials/update.py +++ b/src/pyserials/update.py @@ -152,7 +152,7 @@ def __init__( self._pattern_value: dict[int, _RegexPattern] = {} self._data = None - self._visited_paths = set() + self._visited_paths = {} return def fill( @@ -162,17 +162,17 @@ def fill( current_path: str = "", ): self._data = data - self._visited_paths = set() - path = (f"$.{current_path}" if self._add_prefix else current_path) if current_path else "$" + self._visited_paths = {} + path = _jsonpath.parse((f"$.{current_path}" if self._add_prefix else current_path) if current_path else "$") return self._recursive_subst( templ=template or data, current_path=path, relative_path_anchor=path, level=0, - current_chain=[path], + current_chain=(path,), ) - def _recursive_subst(self, templ, current_path: str, relative_path_anchor: str, level: int, current_chain: list[str]): + def _recursive_subst(self, templ, current_path: str, relative_path_anchor: str, level: int, current_chain: tuple[str, ...]): def get_code_value(match: _re.Match | str): @@ -207,7 +207,7 @@ def getter_function(path: str, default: Any = None, search: bool = False): path_invalid=current_path, exception=e, ) - self._visited_paths.add(self._normalize_path(current_path)) + self._visited_paths[current_path] = (output, True) return output def get_address_value(match: _re.Match | str, return_all_matches: bool = False, from_code: bool = False): @@ -219,21 +219,21 @@ def get_address_value(match: _re.Match | str, return_all_matches: bool = False, path_expr = _jsonpath.parse(path) except _jsonpath_exceptions.JSONPathError: raise_error( - path_invalid=path, + path_invalid=path_expr, description_template="JSONPath expression {path_invalid} is invalid.", ) if num_periods: if relative_path_anchor != current_path: - path_fields = self._extract_fields(_jsonpath.parse(current_path)) + path_fields = self._extract_fields(current_path) has_template_key = any(field in self._template_keys for field in path_fields) anchor_path = relative_path_anchor if has_template_key else current_path else: anchor_path = current_path - root_path_expr = _jsonpath.parse(anchor_path) + root_path_expr = anchor_path for period in range(num_periods): if isinstance(root_path_expr, _jsonpath.Root): raise_error( - path_invalid=path, + path_invalid=path_expr, description_template=( "Relative path {path_invalid} is invalid; " f"reached root but still {num_periods - period} levels remaining." @@ -241,8 +241,8 @@ def get_address_value(match: _re.Match | str, return_all_matches: bool = False, ) root_path_expr = root_path_expr.left path_expr = self._concat_json_paths(root_path_expr, path_expr) - value, matched = get_value(path_expr, return_all_matches, from_code) - self._visited_paths.add(self._normalize_path(current_path)) + value, matched = self._visited_paths.get(path_expr) or get_value(path_expr, return_all_matches, from_code) + self._visited_paths[path_expr] = (value, matched) if from_code: return value, matched if matched: @@ -260,7 +260,7 @@ def get_value(jsonpath, return_all_matches: bool, from_code: bool) -> tuple[Any, return [], True if self._raise_no_match: raise_error( - path_invalid=str(jsonpath), + path_invalid=jsonpath, description_template="JSONPath expression {path_invalid} did not match any data.", ) return None, False @@ -274,10 +274,10 @@ def get_value(jsonpath, return_all_matches: bool, from_code: bool) -> tuple[Any, _rel_path_anchor = relative_path_anchor return self._recursive_subst( output, - current_path=str(jsonpath), + current_path=jsonpath, relative_path_anchor=_rel_path_anchor, level=0, - current_chain=current_chain + [str(jsonpath)], + current_chain=current_chain + (jsonpath,), ), True def _rec_match(expr) -> list: @@ -291,10 +291,10 @@ def _rec_match(expr) -> list: for left_match in left_matches: left_match_filled = self._recursive_subst( templ=left_match.value, - current_path=str(expr.left), - relative_path_anchor=str(expr.left), + current_path=expr.left, + relative_path_anchor=expr.left, level=0, - current_chain=current_chain + [str(expr.left)], + current_chain=current_chain + (expr.left,), ) if isinstance(left_match.value, str) else left_match.value right_matches = expr.right.find(left_match_filled) whole_matches.extend(right_matches) @@ -341,9 +341,10 @@ def raise_error(path_invalid: str, description_template: str, exception: Excepti template_end=self._marker_end_value, ) from exception + if current_path in self._visited_paths: + return self._visited_paths[current_path][0] + self._check_endless_loop(templ, current_chain) - # if self._normalize_path(current_path) in self._visited_paths: - # return templ if isinstance(templ, str): # Handle value blocks @@ -391,13 +392,13 @@ def raise_error(path_invalid: str, description_template: str, exception: Excepti if isinstance(templ, list): out = [] for idx, elem in enumerate(templ): - new_path = f"{current_path}[{idx}]" + new_path = _jsonpath.Child(current_path, _jsonpath.Index(idx)) elem_filled = self._recursive_subst( templ=elem, current_path=new_path, relative_path_anchor=get_relative_path(new_path), level=0, - current_chain=current_chain + [new_path], + current_chain=current_chain + (new_path,), ) if isinstance(elem, str) and self._pattern_unpack.fullmatch(elem): try: @@ -409,7 +410,7 @@ def raise_error(path_invalid: str, description_template: str, exception: Excepti ) else: out.append(elem_filled) - self._visited_paths.add(self._normalize_path(current_path)) + self._visited_paths[current_path] = (out, True) return out if isinstance(templ, dict): @@ -428,29 +429,30 @@ def raise_error(path_invalid: str, description_template: str, exception: Excepti if key_filled in self._template_keys: new_dict[key_filled] = val continue - new_path = f"{current_path}.'{key_filled}'" + new_path = _jsonpath.Child(current_path, _jsonpath.Fields(key_filled)) new_dict[key_filled] = self._recursive_subst( templ=val, current_path=new_path, relative_path_anchor=get_relative_path(new_path), level=0, - current_chain=current_chain + [new_path], + current_chain=current_chain + (new_path,), ) - self._visited_paths.add(self._normalize_path(current_path)) + self._visited_paths[current_path] = (new_dict, True) return new_dict return templ - def _check_endless_loop(self,templ, chain: list[str]): - last_idx = len(chain) -1 + def _check_endless_loop(self,templ, chain: tuple[str, ...]): + last_idx = len(chain) - 1 first_idx = chain.index(chain[-1]) if first_idx == last_idx: return - loop = chain[first_idx - 1: -1] - loop_str = "\n".join([f"- {path.replace("'", "")}" for path in loop]) + loop = [chain[-2], *chain[first_idx: -2]] + loop_str = "\n".join([f"- {path}" for path in loop]) + history_str = "\n".join([f"- {path}" for path in chain]) raise _exception.update.PySerialsUpdateTemplatedDataError( - description_template=f"Path {{path_invalid}} starts a loop:\n{loop_str}", - path_invalid=loop[0], - path=chain[-1], + description_template=f"Path {{path_invalid}} starts a loop:\n{loop_str}\nHistory:\n{history_str}", + path_invalid=chain[-2], + path=chain[0], data=templ, data_full=self._data, data_source=self._data,