PySerials 0.0.0.dev48__tar.gz → 0.0.0.dev50__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/PKG-INFO +3 -3
  2. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/pyproject.toml +3 -3
  3. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/PySerials.egg-info/PKG-INFO +3 -3
  4. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/PySerials.egg-info/requires.txt +2 -2
  5. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/exception/update.py +4 -3
  6. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/update.py +48 -27
  7. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/write.py +19 -2
  8. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/README.md +0 -0
  9. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/setup.cfg +0 -0
  10. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/PySerials.egg-info/SOURCES.txt +0 -0
  11. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/PySerials.egg-info/dependency_links.txt +0 -0
  12. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/PySerials.egg-info/not-zip-safe +0 -0
  13. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/PySerials.egg-info/top_level.txt +0 -0
  14. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/__init__.py +0 -0
  15. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/compare.py +0 -0
  16. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/exception/__init__.py +0 -0
  17. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/exception/_base.py +0 -0
  18. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/exception/read.py +0 -0
  19. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/exception/validate.py +0 -0
  20. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/format.py +0 -0
  21. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/nested_dict.py +0 -0
  22. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/property_dict.py +0 -0
  23. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/read.py +0 -0
  24. {pyserials-0.0.0.dev48 → pyserials-0.0.0.dev50}/src/pyserials/validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PySerials
3
- Version: 0.0.0.dev48
3
+ Version: 0.0.0.dev50
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: jsonschema<5,>=4.21.0
6
6
  Requires-Dist: referencing>=0.35.1
@@ -8,6 +8,6 @@ Requires-Dist: jsonpath-ng<2,>=1.6.1
8
8
  Requires-Dist: ruamel.yaml<0.18,>=0.17.32
9
9
  Requires-Dist: ruamel.yaml.string<1,>=0.1.1
10
10
  Requires-Dist: tomlkit<0.12,>=0.11.8
11
- Requires-Dist: MDit==0.0.0.dev45
12
- Requires-Dist: ExceptionMan==0.0.0.dev45
11
+ Requires-Dist: MDit==0.0.0.dev47
12
+ Requires-Dist: ExceptionMan==0.0.0.dev47
13
13
  Requires-Dist: ProtocolMan==0.0.0.dev2
@@ -17,7 +17,7 @@ namespaces = true
17
17
  # ----------------------------------------- Project Metadata -------------------------------------
18
18
  #
19
19
  [project]
20
- version = "0.0.0.dev48"
20
+ version = "0.0.0.dev50"
21
21
  name = "PySerials"
22
22
  dependencies = [
23
23
  "jsonschema >= 4.21.0, < 5",
@@ -26,8 +26,8 @@ dependencies = [
26
26
  "ruamel.yaml >= 0.17.32, < 0.18", # https://yaml.readthedocs.io/en/stable/
27
27
  "ruamel.yaml.string >= 0.1.1, < 1",
28
28
  "tomlkit >= 0.11.8, < 0.12", # https://tomlkit.readthedocs.io/en/stable/,
29
- "MDit == 0.0.0.dev45",
30
- "ExceptionMan == 0.0.0.dev45",
29
+ "MDit == 0.0.0.dev47",
30
+ "ExceptionMan == 0.0.0.dev47",
31
31
  "ProtocolMan == 0.0.0.dev2",
32
32
  ]
33
33
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PySerials
3
- Version: 0.0.0.dev48
3
+ Version: 0.0.0.dev50
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: jsonschema<5,>=4.21.0
6
6
  Requires-Dist: referencing>=0.35.1
@@ -8,6 +8,6 @@ Requires-Dist: jsonpath-ng<2,>=1.6.1
8
8
  Requires-Dist: ruamel.yaml<0.18,>=0.17.32
9
9
  Requires-Dist: ruamel.yaml.string<1,>=0.1.1
10
10
  Requires-Dist: tomlkit<0.12,>=0.11.8
11
- Requires-Dist: MDit==0.0.0.dev45
12
- Requires-Dist: ExceptionMan==0.0.0.dev45
11
+ Requires-Dist: MDit==0.0.0.dev47
12
+ Requires-Dist: ExceptionMan==0.0.0.dev47
13
13
  Requires-Dist: ProtocolMan==0.0.0.dev2
@@ -4,6 +4,6 @@ jsonpath-ng<2,>=1.6.1
4
4
  ruamel.yaml<0.18,>=0.17.32
5
5
  ruamel.yaml.string<1,>=0.1.1
6
6
  tomlkit<0.12,>=0.11.8
7
- MDit==0.0.0.dev45
8
- ExceptionMan==0.0.0.dev45
7
+ MDit==0.0.0.dev47
8
+ ExceptionMan==0.0.0.dev47
9
9
  ProtocolMan==0.0.0.dev2
@@ -123,14 +123,15 @@ class PySerialsUpdateTemplatedDataError(PySerialsUpdateException):
123
123
  template_start: str,
124
124
  template_end: str,
125
125
  ):
126
- self.path_invalid = path_invalid.replace("'", "")
126
+ self.path_invalid = path_invalid
127
127
  self.data_source = data_source
128
128
  self.template_start = template_start
129
129
  self.template_end = template_end
130
130
  parts = description_template.split("{path_invalid}")
131
- parts.insert(1, _mdit.element.code_span(self.path_invalid))
131
+ if len(parts) > 1:
132
+ parts.insert(1, _mdit.element.code_span(str(self.path_invalid)))
132
133
  super().__init__(
133
- path=path.replace("'", ""),
134
+ path=str(path),
134
135
  data=data,
135
136
  data_full=data_full,
136
137
  problem=_mdit.inline_container(*parts),
@@ -152,6 +152,7 @@ class TemplateFiller:
152
152
 
153
153
  self._pattern_value: dict[int, _RegexPattern] = {}
154
154
  self._data = None
155
+ self._visited_paths = {}
155
156
  return
156
157
 
157
158
  def fill(
@@ -161,16 +162,17 @@ class TemplateFiller:
161
162
  current_path: str = "",
162
163
  ):
163
164
  self._data = data
164
- path = (f"$.{current_path}" if self._add_prefix else current_path) if current_path else "$"
165
+ self._visited_paths = {}
166
+ path = _jsonpath.parse((f"$.{current_path}" if self._add_prefix else current_path) if current_path else "$")
165
167
  return self._recursive_subst(
166
168
  templ=template or data,
167
169
  current_path=path,
168
170
  relative_path_anchor=path,
169
171
  level=0,
170
- current_chain=[path],
172
+ current_chain=(path,),
171
173
  )
172
174
 
173
- def _recursive_subst(self, templ, current_path: str, relative_path_anchor: str, level: int, current_chain: list[str]):
175
+ def _recursive_subst(self, templ, current_path: str, relative_path_anchor: str, level: int, current_chain: tuple[str, ...]):
174
176
 
175
177
  def get_code_value(match: _re.Match | str):
176
178
 
@@ -198,13 +200,15 @@ class TemplateFiller:
198
200
  local_context = {}
199
201
  try:
200
202
  exec(code_str_full, global_context, local_context)
201
- return local_context["__inline_code__"]()
203
+ output = local_context["__inline_code__"]()
202
204
  except Exception as e:
203
205
  raise_error(
204
206
  description_template=f"Code at {{path_invalid}} raised an exception: {e}\n{code_str_full}",
205
207
  path_invalid=current_path,
206
208
  exception=e,
207
209
  )
210
+ self._visited_paths[current_path] = (output, True)
211
+ return output
208
212
 
209
213
  def get_address_value(match: _re.Match | str, return_all_matches: bool = False, from_code: bool = False):
210
214
  raw_path = match if isinstance(match, str) else str(match.group(1))
@@ -215,21 +219,21 @@ class TemplateFiller:
215
219
  path_expr = _jsonpath.parse(path)
216
220
  except _jsonpath_exceptions.JSONPathError:
217
221
  raise_error(
218
- path_invalid=path,
222
+ path_invalid=path_expr,
219
223
  description_template="JSONPath expression {path_invalid} is invalid.",
220
224
  )
221
225
  if num_periods:
222
226
  if relative_path_anchor != current_path:
223
- path_fields = self._extract_fields(_jsonpath.parse(current_path))
227
+ path_fields = self._extract_fields(current_path)
224
228
  has_template_key = any(field in self._template_keys for field in path_fields)
225
229
  anchor_path = relative_path_anchor if has_template_key else current_path
226
230
  else:
227
231
  anchor_path = current_path
228
- root_path_expr = _jsonpath.parse(anchor_path)
232
+ root_path_expr = anchor_path
229
233
  for period in range(num_periods):
230
234
  if isinstance(root_path_expr, _jsonpath.Root):
231
235
  raise_error(
232
- path_invalid=path,
236
+ path_invalid=path_expr,
233
237
  description_template=(
234
238
  "Relative path {path_invalid} is invalid; "
235
239
  f"reached root but still {num_periods - period} levels remaining."
@@ -237,7 +241,8 @@ class TemplateFiller:
237
241
  )
238
242
  root_path_expr = root_path_expr.left
239
243
  path_expr = self._concat_json_paths(root_path_expr, path_expr)
240
- value, matched = get_value(path_expr, return_all_matches, from_code)
244
+ value, matched = self._visited_paths.get(path_expr) or get_value(path_expr, return_all_matches, from_code)
245
+ self._visited_paths[path_expr] = (value, matched)
241
246
  if from_code:
242
247
  return value, matched
243
248
  if matched:
@@ -255,7 +260,7 @@ class TemplateFiller:
255
260
  return [], True
256
261
  if self._raise_no_match:
257
262
  raise_error(
258
- path_invalid=str(jsonpath),
263
+ path_invalid=jsonpath,
259
264
  description_template="JSONPath expression {path_invalid} did not match any data.",
260
265
  )
261
266
  return None, False
@@ -269,10 +274,10 @@ class TemplateFiller:
269
274
  _rel_path_anchor = relative_path_anchor
270
275
  return self._recursive_subst(
271
276
  output,
272
- current_path=str(jsonpath),
277
+ current_path=jsonpath,
273
278
  relative_path_anchor=_rel_path_anchor,
274
279
  level=0,
275
- current_chain=current_chain + [str(jsonpath)],
280
+ current_chain=current_chain + (jsonpath,),
276
281
  ), True
277
282
 
278
283
  def _rec_match(expr) -> list:
@@ -286,10 +291,10 @@ class TemplateFiller:
286
291
  for left_match in left_matches:
287
292
  left_match_filled = self._recursive_subst(
288
293
  templ=left_match.value,
289
- current_path=str(expr.left),
290
- relative_path_anchor=str(expr.left),
294
+ current_path=expr.left,
295
+ relative_path_anchor=expr.left,
291
296
  level=0,
292
- current_chain=current_chain + [str(expr.left)],
297
+ current_chain=current_chain + (expr.left,),
293
298
  ) if isinstance(left_match.value, str) else left_match.value
294
299
  right_matches = expr.right.find(left_match_filled)
295
300
  whole_matches.extend(right_matches)
@@ -336,6 +341,9 @@ class TemplateFiller:
336
341
  template_end=self._marker_end_value,
337
342
  ) from exception
338
343
 
344
+ if current_path in self._visited_paths:
345
+ return self._visited_paths[current_path][0]
346
+
339
347
  self._check_endless_loop(templ, current_chain)
340
348
 
341
349
  if isinstance(templ, str):
@@ -384,18 +392,25 @@ class TemplateFiller:
384
392
  if isinstance(templ, list):
385
393
  out = []
386
394
  for idx, elem in enumerate(templ):
387
- new_path = f"{current_path}[{idx}]"
395
+ new_path = _jsonpath.Child(current_path, _jsonpath.Index(idx))
388
396
  elem_filled = self._recursive_subst(
389
397
  templ=elem,
390
398
  current_path=new_path,
391
399
  relative_path_anchor=get_relative_path(new_path),
392
400
  level=0,
393
- current_chain=current_chain + [new_path],
401
+ current_chain=current_chain + (new_path,),
394
402
  )
395
403
  if isinstance(elem, str) and self._pattern_unpack.fullmatch(elem):
396
- out.extend(elem_filled)
404
+ try:
405
+ out.extend(elem_filled)
406
+ except TypeError as e:
407
+ raise_error(
408
+ path_invalid=current_path,
409
+ description_template=str(e)
410
+ )
397
411
  else:
398
412
  out.append(elem_filled)
413
+ self._visited_paths[current_path] = (out, True)
399
414
  return out
400
415
 
401
416
  if isinstance(templ, dict):
@@ -414,28 +429,30 @@ class TemplateFiller:
414
429
  if key_filled in self._template_keys:
415
430
  new_dict[key_filled] = val
416
431
  continue
417
- new_path = f"{current_path}.'{key_filled}'"
432
+ new_path = _jsonpath.Child(current_path, _jsonpath.Fields(key_filled))
418
433
  new_dict[key_filled] = self._recursive_subst(
419
434
  templ=val,
420
435
  current_path=new_path,
421
436
  relative_path_anchor=get_relative_path(new_path),
422
437
  level=0,
423
- current_chain=current_chain + [new_path],
438
+ current_chain=current_chain + (new_path,),
424
439
  )
440
+ self._visited_paths[current_path] = (new_dict, True)
425
441
  return new_dict
426
442
  return templ
427
443
 
428
- def _check_endless_loop(self,templ, chain: list[str]):
429
- last_idx = len(chain) -1
444
+ def _check_endless_loop(self,templ, chain: tuple[str, ...]):
445
+ last_idx = len(chain) - 1
430
446
  first_idx = chain.index(chain[-1])
431
447
  if first_idx == last_idx:
432
448
  return
433
- loop = chain[first_idx - 1: -1]
434
- loop_str = "\n".join([f"- {path.replace("'", "")}" for path in loop])
449
+ loop = [chain[-2], *chain[first_idx: -2]]
450
+ loop_str = "\n".join([f"- {path}" for path in loop])
451
+ history_str = "\n".join([f"- {path}" for path in chain])
435
452
  raise _exception.update.PySerialsUpdateTemplatedDataError(
436
- description_template=f"Path {{path_invalid}} starts a loop:\n{loop_str}",
437
- path_invalid=loop[0],
438
- path=chain[-1],
453
+ description_template=f"Path {{path_invalid}} starts a loop:\n{loop_str}\nHistory:\n{history_str}",
454
+ path_invalid=chain[-2],
455
+ path=chain[0],
439
456
  data=templ,
440
457
  data_full=self._data,
441
458
  data_source=self._data,
@@ -480,6 +497,10 @@ class TemplateFiller:
480
497
  _recursive_extract(jsonpath)
481
498
  return fields
482
499
 
500
+ @staticmethod
501
+ def _normalize_path(path: str) -> str:
502
+ return path.replace("'", "")
503
+
483
504
  def _concat_json_paths(self, path1, path2):
484
505
  if not isinstance(path2, _jsonpath.Child):
485
506
  return _jsonpath.Child(path1, path2)
@@ -2,6 +2,7 @@ from typing import Literal as _Literal
2
2
  from pathlib import Path as _Path
3
3
  import json as _json
4
4
  import ruamel.yaml as _yaml
5
+ from ruamel.yaml import scalarstring as _yaml_scalar_string
5
6
  import tomlkit as _tomlkit
6
7
 
7
8
 
@@ -22,8 +23,16 @@ def to_string(
22
23
  def to_yaml_string(
23
24
  data: dict | list | str | int | float | bool | _yaml.CommentedMap | _yaml.CommentedSeq,
24
25
  end_of_file_newline: bool = True,
26
+ indent_mapping: int = 2,
27
+ indent_sequence: int = 4,
28
+ indent_sequence_offset: int = 2,
29
+ multiline_string_to_block: bool = True,
25
30
  ) -> str:
26
- yaml_syntax = _yaml.YAML(typ=["rt", "string"]).dumps(data, add_final_eol=False).removesuffix("\n...")
31
+ yaml = _yaml.YAML(typ=["rt", "string"])
32
+ yaml.indent(mapping=indent_mapping, sequence=indent_sequence, offset=indent_sequence_offset)
33
+ if multiline_string_to_block:
34
+ _yaml_scalar_string.walk_tree(data)
35
+ yaml_syntax = yaml.dumps(data, add_final_eol=False).removesuffix("\n...")
27
36
  return f"{yaml_syntax}\n" if end_of_file_newline else yaml_syntax
28
37
 
29
38
 
@@ -46,9 +55,17 @@ def to_yaml_file(
46
55
  data: dict | list | str | int | float | bool | _yaml.CommentedMap | _yaml.CommentedSeq,
47
56
  path: str | _Path,
48
57
  make_dirs: bool = True,
58
+ indent_mapping: int = 2,
59
+ indent_sequence: int = 4,
60
+ indent_sequence_offset: int = 2,
61
+ multiline_string_to_block: bool = True,
49
62
  ):
50
63
  path = _Path(path).resolve()
51
64
  if make_dirs:
52
65
  path.parent.mkdir(parents=True, exist_ok=True)
53
- _yaml.YAML().dump(data, path)
66
+ yaml = _yaml.YAML()
67
+ yaml.indent(mapping=indent_mapping, sequence=indent_sequence, offset=indent_sequence_offset)
68
+ if multiline_string_to_block:
69
+ _yaml_scalar_string.walk_tree(data)
70
+ yaml.dump(data, path)
54
71
  return