PySerials 0.0.0.dev30__tar.gz → 0.0.0.dev32__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.
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/PKG-INFO +3 -3
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/pyproject.toml +3 -3
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/PySerials.egg-info/PKG-INFO +3 -3
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/PySerials.egg-info/requires.txt +2 -2
- pyserials-0.0.0.dev32/src/pyserials/nested_dict.py +193 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/update.py +251 -86
- pyserials-0.0.0.dev30/src/pyserials/nested_dict.py +0 -142
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/README.md +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/setup.cfg +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/PySerials.egg-info/SOURCES.txt +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/PySerials.egg-info/dependency_links.txt +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/PySerials.egg-info/not-zip-safe +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/PySerials.egg-info/top_level.txt +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/__init__.py +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/compare.py +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/exception/__init__.py +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/exception/_base.py +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/exception/read.py +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/exception/update.py +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/exception/validate.py +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/format.py +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/read.py +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/validate.py +0 -0
- {pyserials-0.0.0.dev30 → pyserials-0.0.0.dev32}/src/pyserials/write.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PySerials
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev32
|
|
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.
|
|
12
|
-
Requires-Dist: ExceptionMan==0.0.0.
|
|
11
|
+
Requires-Dist: MDit==0.0.0.dev29
|
|
12
|
+
Requires-Dist: ExceptionMan==0.0.0.dev29
|
|
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.
|
|
20
|
+
version = "0.0.0.dev32"
|
|
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.
|
|
30
|
-
"ExceptionMan == 0.0.0.
|
|
29
|
+
"MDit == 0.0.0.dev29",
|
|
30
|
+
"ExceptionMan == 0.0.0.dev29",
|
|
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.
|
|
3
|
+
Version: 0.0.0.dev32
|
|
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.
|
|
12
|
-
Requires-Dist: ExceptionMan==0.0.0.
|
|
11
|
+
Requires-Dist: MDit==0.0.0.dev29
|
|
12
|
+
Requires-Dist: ExceptionMan==0.0.0.dev29
|
|
13
13
|
Requires-Dist: ProtocolMan==0.0.0.dev2
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING as _TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
import pyserials as _ps
|
|
6
|
+
|
|
7
|
+
if _TYPE_CHECKING:
|
|
8
|
+
from typing import Callable, Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NestedDict:
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
data: dict | None = None,
|
|
16
|
+
marker_start_value: str = "$",
|
|
17
|
+
marker_end_value: str = "$",
|
|
18
|
+
repeater_start_value: str = "{",
|
|
19
|
+
repeater_end_value: str = "}",
|
|
20
|
+
repeater_count_value: int = 2,
|
|
21
|
+
start_list: str = "$[[",
|
|
22
|
+
start_unpack: str = "*{{",
|
|
23
|
+
start_code: str = "#{{",
|
|
24
|
+
end_list: str = "]]$",
|
|
25
|
+
end_unpack: str = "}}*",
|
|
26
|
+
end_code: str = "}}#",
|
|
27
|
+
recursive: bool = True,
|
|
28
|
+
raise_no_match: bool = True,
|
|
29
|
+
leave_no_match: bool = False,
|
|
30
|
+
no_match_value: Any = None,
|
|
31
|
+
code_context: dict[str, Any] | None = None,
|
|
32
|
+
stringer: Callable[[str], str] = str,
|
|
33
|
+
unpack_string_joiner: str = ", ",
|
|
34
|
+
relative_template_keys: list[str] | None = None,
|
|
35
|
+
implicit_root: bool = True,
|
|
36
|
+
):
|
|
37
|
+
self._data = data or {}
|
|
38
|
+
self._templater = _ps.update.TemplateFiller(
|
|
39
|
+
marker_start_value=marker_start_value,
|
|
40
|
+
marker_end_value=marker_end_value,
|
|
41
|
+
repeater_start_value=repeater_start_value,
|
|
42
|
+
repeater_end_value=repeater_end_value,
|
|
43
|
+
repeater_count_value=repeater_count_value,
|
|
44
|
+
start_list=start_list,
|
|
45
|
+
start_unpack=start_unpack,
|
|
46
|
+
start_code=start_code,
|
|
47
|
+
end_list=end_list,
|
|
48
|
+
end_unpack=end_unpack,
|
|
49
|
+
end_code=end_code,
|
|
50
|
+
)
|
|
51
|
+
self._recursive = recursive
|
|
52
|
+
self._raise_no_match = raise_no_match
|
|
53
|
+
self._leave_no_match = leave_no_match
|
|
54
|
+
self._no_match_value = no_match_value
|
|
55
|
+
self._code_context = code_context or {}
|
|
56
|
+
self._stringer = stringer
|
|
57
|
+
self._unpack_string_joiner = unpack_string_joiner
|
|
58
|
+
self._relative_template_keys = relative_template_keys or []
|
|
59
|
+
self._implicit_root = implicit_root
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
def fill(
|
|
63
|
+
self,
|
|
64
|
+
path: str = "",
|
|
65
|
+
recursive: bool | None = None,
|
|
66
|
+
raise_no_match: bool | None = None,
|
|
67
|
+
leave_no_match: bool | None = None,
|
|
68
|
+
code_context: dict[str, Any] | None = None,
|
|
69
|
+
stringer: Callable[[str], str] | None = None,
|
|
70
|
+
unpack_string_joiner: str | None = None,
|
|
71
|
+
level: int = 0,
|
|
72
|
+
):
|
|
73
|
+
if not path:
|
|
74
|
+
value = self._data
|
|
75
|
+
else:
|
|
76
|
+
value = self.__getitem__(path)
|
|
77
|
+
if not value:
|
|
78
|
+
return
|
|
79
|
+
filled_value = self.fill_data(
|
|
80
|
+
data=value,
|
|
81
|
+
current_path=path,
|
|
82
|
+
recursive=recursive,
|
|
83
|
+
raise_no_match=raise_no_match,
|
|
84
|
+
leave_no_match=leave_no_match,
|
|
85
|
+
code_context=code_context,
|
|
86
|
+
stringer=stringer,
|
|
87
|
+
unpack_string_joiner=unpack_string_joiner,
|
|
88
|
+
level=level,
|
|
89
|
+
)
|
|
90
|
+
if not path:
|
|
91
|
+
self._data = filled_value
|
|
92
|
+
else:
|
|
93
|
+
self.__setitem__(path, filled_value)
|
|
94
|
+
return filled_value
|
|
95
|
+
|
|
96
|
+
def fill_data(
|
|
97
|
+
self,
|
|
98
|
+
data,
|
|
99
|
+
current_path: str = "",
|
|
100
|
+
recursive: bool | None = None,
|
|
101
|
+
raise_no_match: bool | None = None,
|
|
102
|
+
leave_no_match: bool | None = None,
|
|
103
|
+
stringer: Callable[[str], str] | None = None,
|
|
104
|
+
code_context: dict[str, Any] | None = None,
|
|
105
|
+
unpack_string_joiner: str | None = None,
|
|
106
|
+
level: int = 0,
|
|
107
|
+
):
|
|
108
|
+
return self._templater.fill(
|
|
109
|
+
templated_data=data,
|
|
110
|
+
source_data=self._data,
|
|
111
|
+
current_path=current_path,
|
|
112
|
+
recursive=recursive if recursive is not None else self._recursive,
|
|
113
|
+
raise_no_match=raise_no_match if raise_no_match is not None else self._raise_no_match,
|
|
114
|
+
leave_no_match=leave_no_match if leave_no_match is not None else self._leave_no_match,
|
|
115
|
+
no_match_value=self._no_match_value,
|
|
116
|
+
code_context=code_context if code_context is not None else self._code_context,
|
|
117
|
+
stringer=stringer if stringer is not None else self._stringer,
|
|
118
|
+
unpack_string_joiner=unpack_string_joiner if unpack_string_joiner is not None else self._unpack_string_joiner,
|
|
119
|
+
relative_template_keys=self._relative_template_keys,
|
|
120
|
+
implicit_root=self._implicit_root,
|
|
121
|
+
level=level,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def __call__(self):
|
|
125
|
+
return self._data
|
|
126
|
+
|
|
127
|
+
def __getitem__(self, item: str):
|
|
128
|
+
keys = item.split(".")
|
|
129
|
+
data = self._data
|
|
130
|
+
for key in keys:
|
|
131
|
+
if not isinstance(data, dict):
|
|
132
|
+
raise KeyError(f"Key '{key}' not found in '{data}'.")
|
|
133
|
+
if key not in data:
|
|
134
|
+
return
|
|
135
|
+
data = data[key]
|
|
136
|
+
# if isinstance(data, dict):
|
|
137
|
+
# return NestedDict(data)
|
|
138
|
+
# if isinstance(data, list) and all(isinstance(item, dict) for item in data):
|
|
139
|
+
# return [NestedDict(item) for item in data]
|
|
140
|
+
return data
|
|
141
|
+
|
|
142
|
+
def __setitem__(self, key, value):
|
|
143
|
+
key = key.split(".")
|
|
144
|
+
data = self._data
|
|
145
|
+
for k in key[:-1]:
|
|
146
|
+
if k not in data:
|
|
147
|
+
data[k] = {}
|
|
148
|
+
data = data[k]
|
|
149
|
+
data[key[-1]] = value
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
def __contains__(self, item):
|
|
153
|
+
keys = item.split(".")
|
|
154
|
+
data = self._data
|
|
155
|
+
for key in keys:
|
|
156
|
+
if not isinstance(data, dict) or key not in data:
|
|
157
|
+
return False
|
|
158
|
+
data = data[key]
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
def __bool__(self):
|
|
162
|
+
return bool(self._data)
|
|
163
|
+
|
|
164
|
+
def setdefault(self, key, value):
|
|
165
|
+
key = key.split(".")
|
|
166
|
+
data = self._data
|
|
167
|
+
for k in key[:-1]:
|
|
168
|
+
if k not in data:
|
|
169
|
+
data[k] = {}
|
|
170
|
+
data = data[k]
|
|
171
|
+
return data.setdefault(key[-1], value)
|
|
172
|
+
|
|
173
|
+
def get(self, key, default=None):
|
|
174
|
+
keys = key.split(".")
|
|
175
|
+
data = self._data
|
|
176
|
+
for key in keys:
|
|
177
|
+
if not isinstance(data, dict) or key not in data:
|
|
178
|
+
return default
|
|
179
|
+
data = data[key]
|
|
180
|
+
return data
|
|
181
|
+
|
|
182
|
+
def items(self):
|
|
183
|
+
return self._data.items()
|
|
184
|
+
|
|
185
|
+
def keys(self):
|
|
186
|
+
return self._data.keys()
|
|
187
|
+
|
|
188
|
+
def values(self):
|
|
189
|
+
return self._data.values()
|
|
190
|
+
|
|
191
|
+
def update(self, data: dict):
|
|
192
|
+
self._data.update(data)
|
|
193
|
+
return
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
2
4
|
from typing import TYPE_CHECKING as _TYPE_CHECKING
|
|
3
5
|
import re as _re
|
|
4
6
|
|
|
@@ -8,7 +10,7 @@ from jsonpath_ng import exceptions as _jsonpath_exceptions
|
|
|
8
10
|
import pyserials.exception as _exception
|
|
9
11
|
|
|
10
12
|
if _TYPE_CHECKING:
|
|
11
|
-
from typing import Literal,
|
|
13
|
+
from typing import Literal, Sequence, Any, Callable
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
def dict_from_addon(
|
|
@@ -103,25 +105,29 @@ class TemplateFiller:
|
|
|
103
105
|
|
|
104
106
|
def __init__(
|
|
105
107
|
self,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
marker_start_value: str = "$",
|
|
109
|
+
marker_end_value: str = "$",
|
|
110
|
+
repeater_start_value: str = "{",
|
|
111
|
+
repeater_end_value: str = "}",
|
|
112
|
+
repeater_count_value: int = 2,
|
|
113
|
+
start_list: str = "$[[",
|
|
114
|
+
start_unpack: str = "*{{",
|
|
115
|
+
start_code: str = "#{{",
|
|
116
|
+
end_list: str = "]]$",
|
|
117
|
+
end_unpack: str = "}}*",
|
|
118
|
+
end_code: str = "}}#",
|
|
112
119
|
):
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
self.
|
|
120
|
-
self.
|
|
121
|
-
self.
|
|
122
|
-
|
|
123
|
-
self.
|
|
124
|
-
self._stringer = stringer
|
|
120
|
+
self._marker_start_value = marker_start_value
|
|
121
|
+
self._marker_end_value = marker_end_value
|
|
122
|
+
self._repeater_start_value = repeater_start_value
|
|
123
|
+
self._repeater_end_value = repeater_end_value
|
|
124
|
+
self._repeater_count_value = repeater_count_value
|
|
125
|
+
self._pattern_list = _RegexPattern(start=start_list, end=end_list)
|
|
126
|
+
self._pattern_unpack = _RegexPattern(start=start_unpack, end=end_unpack)
|
|
127
|
+
self._pattern_code = _RegexPattern(start=start_code, end=end_code)
|
|
128
|
+
self._add_prefix = True
|
|
129
|
+
|
|
130
|
+
self._pattern_value: dict[int, _RegexPattern] = {}
|
|
125
131
|
self._data = None
|
|
126
132
|
self._source = None
|
|
127
133
|
self._recursive = None
|
|
@@ -129,93 +135,100 @@ class TemplateFiller:
|
|
|
129
135
|
self._raise_no_match = None
|
|
130
136
|
self._template_keys = None
|
|
131
137
|
self._ignore_templates = True
|
|
138
|
+
self._leave_no_match = False
|
|
139
|
+
self._no_match_value = None
|
|
140
|
+
self._code_context = {}
|
|
141
|
+
self._stringer = str
|
|
142
|
+
self._unpack_string_joiner = ", "
|
|
143
|
+
self._path_history = []
|
|
132
144
|
return
|
|
133
145
|
|
|
146
|
+
def _get_value_regex_pattern(self, level: int = 0) -> _RegexPattern:
|
|
147
|
+
level_patterns = self._pattern_value.setdefault(level, {})
|
|
148
|
+
if level in level_patterns:
|
|
149
|
+
return level_patterns[level]
|
|
150
|
+
count = self._repeater_count_value + level
|
|
151
|
+
pattern = _RegexPattern(
|
|
152
|
+
start=f"{self._marker_start_value}{self._repeater_start_value * count} ",
|
|
153
|
+
end=f" {self._repeater_end_value * count}{self._marker_end_value}",
|
|
154
|
+
)
|
|
155
|
+
level_patterns[level] = pattern
|
|
156
|
+
return pattern
|
|
157
|
+
|
|
134
158
|
def fill(
|
|
135
159
|
self,
|
|
136
160
|
templated_data: dict | list | str,
|
|
137
161
|
source_data: dict | list,
|
|
138
162
|
current_path: str = "",
|
|
139
|
-
always_list: bool = True,
|
|
140
163
|
recursive: bool = True,
|
|
141
164
|
raise_no_match: bool = True,
|
|
165
|
+
leave_no_match: bool = False,
|
|
166
|
+
no_match_value: Any = None,
|
|
167
|
+
code_context: dict[str, Any] | None = None,
|
|
168
|
+
stringer: Callable[[str], str] = str,
|
|
169
|
+
unpack_string_joiner: str = ", ",
|
|
142
170
|
relative_template_keys: list[str] | None = None,
|
|
171
|
+
implicit_root: bool = True,
|
|
172
|
+
level: int = 0,
|
|
143
173
|
):
|
|
144
174
|
self._data = templated_data
|
|
145
175
|
self._source = source_data
|
|
146
176
|
self._recursive = recursive
|
|
147
177
|
self._raise_no_match = raise_no_match
|
|
178
|
+
self._leave_no_match = leave_no_match
|
|
179
|
+
self._no_match_value = no_match_value
|
|
180
|
+
self._code_context = code_context or {}
|
|
181
|
+
self._stringer = stringer
|
|
182
|
+
self._unpack_string_joiner = unpack_string_joiner
|
|
183
|
+
self._add_prefix = implicit_root
|
|
148
184
|
self._template_keys = relative_template_keys or []
|
|
185
|
+
self._path_history = []
|
|
149
186
|
path = (f"$.{current_path}" if self._add_prefix else current_path) if current_path else "$"
|
|
150
187
|
if not relative_template_keys:
|
|
151
188
|
self._ignore_templates = False
|
|
152
189
|
return self._recursive_subst(
|
|
153
190
|
templ=self._data,
|
|
154
191
|
current_path=path,
|
|
155
|
-
always_list=always_list,
|
|
156
192
|
relative_path_anchor=path,
|
|
193
|
+
level=level,
|
|
157
194
|
)
|
|
158
195
|
self._ignore_templates = True
|
|
159
196
|
first_pass = self._recursive_subst(
|
|
160
197
|
templ=self._data,
|
|
161
198
|
current_path=path,
|
|
162
|
-
always_list=always_list,
|
|
163
199
|
relative_path_anchor=path,
|
|
200
|
+
level=level,
|
|
164
201
|
)
|
|
165
202
|
if self._data is self._source:
|
|
166
203
|
self._source = first_pass
|
|
167
204
|
self._data = first_pass
|
|
168
205
|
self._ignore_templates = False
|
|
206
|
+
self._path_history = []
|
|
169
207
|
return self._recursive_subst(
|
|
170
208
|
templ=self._data,
|
|
171
209
|
current_path=path,
|
|
172
|
-
always_list=always_list,
|
|
173
210
|
relative_path_anchor=path,
|
|
211
|
+
level=level,
|
|
174
212
|
)
|
|
175
213
|
|
|
176
|
-
def _recursive_subst(self, templ, current_path: str,
|
|
177
|
-
|
|
178
|
-
def raise_error(
|
|
179
|
-
path_invalid: str,
|
|
180
|
-
description_template: str,
|
|
181
|
-
):
|
|
182
|
-
raise _exception.update.PySerialsUpdateTemplatedDataError(
|
|
183
|
-
description_template=description_template,
|
|
184
|
-
path_invalid=path_invalid,
|
|
185
|
-
path=current_path,
|
|
186
|
-
data=templ,
|
|
187
|
-
data_full=self._data,
|
|
188
|
-
data_source=self._source,
|
|
189
|
-
template_start=self._marker_start,
|
|
190
|
-
template_end=self._marker_end,
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
def _rec_match(expr):
|
|
214
|
+
def _recursive_subst(self, templ, current_path: str, relative_path_anchor: str, level: int, internal=False):
|
|
194
215
|
|
|
195
|
-
|
|
216
|
+
def get_code_value(code_str: str):
|
|
217
|
+
code_lines = ["def __inline_code__():"]
|
|
218
|
+
code_lines.extend([f" {line}" for line in code_str.strip("\n").splitlines()])
|
|
219
|
+
code_str_full = "\n".join(code_lines)
|
|
220
|
+
global_context = self._code_context.copy()
|
|
221
|
+
local_context = {}
|
|
222
|
+
try:
|
|
223
|
+
exec(code_str_full, global_context, local_context)
|
|
224
|
+
return local_context["__inline_code__"]()
|
|
225
|
+
except Exception as e:
|
|
196
226
|
raise_error(
|
|
197
|
-
path_invalid
|
|
198
|
-
|
|
227
|
+
description_template=f"Code at {{path_invalid}} raised an exception: {e}\n{code_str_full}",
|
|
228
|
+
path_invalid=current_path,
|
|
199
229
|
)
|
|
200
230
|
|
|
201
|
-
|
|
202
|
-
if matches:
|
|
203
|
-
return matches
|
|
204
|
-
if isinstance(expr.left, _jsonpath.Root):
|
|
205
|
-
raise_error_path_invalid()
|
|
206
|
-
whole_matches = []
|
|
207
|
-
left_matches = _rec_match(expr.left)
|
|
208
|
-
for left_match in left_matches:
|
|
209
|
-
left_match_filled = self._recursive_subst(
|
|
210
|
-
left_match.value, current_path=str(expr.left), always_list=False, relative_path_anchor=str(expr.left)
|
|
211
|
-
) if isinstance(left_match.value, str) else left_match.value
|
|
212
|
-
right_matches = expr.right.find(left_match_filled)
|
|
213
|
-
whole_matches.extend(right_matches)
|
|
214
|
-
if not whole_matches:
|
|
215
|
-
raise_error_path_invalid()
|
|
216
|
-
return whole_matches
|
|
217
|
-
|
|
218
|
-
def get_address_value(re_match):
|
|
231
|
+
def get_address_value(re_match, return_all_matches: bool = False):
|
|
219
232
|
path, num_periods = self._remove_leading_periods(re_match.group(1).strip())
|
|
220
233
|
if num_periods == 0:
|
|
221
234
|
path = f"$.{path}" if self._add_prefix else path
|
|
@@ -243,21 +256,29 @@ class TemplateFiller:
|
|
|
243
256
|
),
|
|
244
257
|
)
|
|
245
258
|
root_path_expr = root_path_expr.left
|
|
246
|
-
path_expr =
|
|
247
|
-
|
|
259
|
+
path_expr = _jsonpath.Child(root_path_expr, path_expr)
|
|
260
|
+
value, matched = get_value(path_expr, return_all_matches)
|
|
261
|
+
if matched:
|
|
262
|
+
return value
|
|
263
|
+
if self._leave_no_match:
|
|
264
|
+
return re_match.group()
|
|
265
|
+
return self._no_match_value
|
|
248
266
|
|
|
249
|
-
def get_value(jsonpath):
|
|
267
|
+
def get_value(jsonpath, return_all_matches: bool) -> tuple[Any, bool]:
|
|
250
268
|
matches = _rec_match(jsonpath)
|
|
269
|
+
if not matches:
|
|
270
|
+
if return_all_matches:
|
|
271
|
+
return [], True
|
|
272
|
+
if self._raise_no_match:
|
|
273
|
+
raise_error(
|
|
274
|
+
path_invalid=str(jsonpath),
|
|
275
|
+
description_template="JSONPath expression {path_invalid} did not match any data.",
|
|
276
|
+
)
|
|
277
|
+
return None, False
|
|
251
278
|
values = [m.value for m in matches]
|
|
252
|
-
if
|
|
253
|
-
raise_error(
|
|
254
|
-
path_invalid=str(jsonpath),
|
|
255
|
-
description_template="JSONPath expression {path_invalid} did not match any data.",
|
|
256
|
-
)
|
|
257
|
-
single = len(values) == 1 and not always_list
|
|
258
|
-
output = values[0] if single else values
|
|
279
|
+
output = values if return_all_matches or len(values) > 1 else values[0]
|
|
259
280
|
if not self._recursive:
|
|
260
|
-
return output
|
|
281
|
+
return output, True
|
|
261
282
|
if relative_path_anchor == current_path:
|
|
262
283
|
path_fields = self._extract_fields(jsonpath)
|
|
263
284
|
has_template_key = any(field in self._template_keys for field in path_fields)
|
|
@@ -267,49 +288,173 @@ class TemplateFiller:
|
|
|
267
288
|
return self._recursive_subst(
|
|
268
289
|
output,
|
|
269
290
|
current_path=str(jsonpath),
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
)
|
|
291
|
+
relative_path_anchor=_rel_path_anchor,
|
|
292
|
+
level=0,
|
|
293
|
+
), True
|
|
294
|
+
|
|
295
|
+
def _rec_match(expr) -> list:
|
|
296
|
+
matches = expr.find(self._source)
|
|
297
|
+
if matches:
|
|
298
|
+
return matches
|
|
299
|
+
if isinstance(expr.left, _jsonpath.Root):
|
|
300
|
+
return []
|
|
301
|
+
whole_matches = []
|
|
302
|
+
left_matches = _rec_match(expr.left)
|
|
303
|
+
for left_match in left_matches:
|
|
304
|
+
left_match_filled = self._recursive_subst(
|
|
305
|
+
templ=left_match.value,
|
|
306
|
+
current_path=str(expr.left),
|
|
307
|
+
relative_path_anchor=str(expr.left),
|
|
308
|
+
level=0,
|
|
309
|
+
) if isinstance(left_match.value, str) else left_match.value
|
|
310
|
+
right_matches = expr.right.find(left_match_filled)
|
|
311
|
+
whole_matches.extend(right_matches)
|
|
312
|
+
return whole_matches
|
|
273
313
|
|
|
274
314
|
def get_relative_path(new_path):
|
|
275
315
|
return new_path if current_path == relative_path_anchor else relative_path_anchor
|
|
276
316
|
|
|
317
|
+
def raise_error(
|
|
318
|
+
path_invalid: str,
|
|
319
|
+
description_template: str,
|
|
320
|
+
):
|
|
321
|
+
raise _exception.update.PySerialsUpdateTemplatedDataError(
|
|
322
|
+
description_template=description_template,
|
|
323
|
+
path_invalid=path_invalid,
|
|
324
|
+
path=current_path,
|
|
325
|
+
data=templ,
|
|
326
|
+
data_full=self._data,
|
|
327
|
+
data_source=self._source,
|
|
328
|
+
template_start=self._marker_start_value,
|
|
329
|
+
template_end=self._marker_end_value,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def string_filler_unpack(match: _re.Match):
|
|
333
|
+
match_list = self._pattern_list.fullmatch(match.group(1).strip())
|
|
334
|
+
if match_list:
|
|
335
|
+
values = get_address_value(match_list, return_all_matches=True)
|
|
336
|
+
else:
|
|
337
|
+
match_code = self._pattern_code.fullmatch(match.group(1).strip())
|
|
338
|
+
if match_code:
|
|
339
|
+
values = get_code_value(match_code.group(1))
|
|
340
|
+
else:
|
|
341
|
+
values = get_address_value(match)
|
|
342
|
+
return self._unpack_string_joiner.join([self._stringer(val) for val in values])
|
|
343
|
+
|
|
344
|
+
# if not internal:
|
|
345
|
+
# self._path_history.append(current_path)
|
|
346
|
+
# loop = self._find_loop()
|
|
347
|
+
# if loop:
|
|
348
|
+
# loop_str = "\n".join([f"- {path.replace("'", "")}" for path in loop])
|
|
349
|
+
# raise _exception.update.PySerialsUpdateTemplatedDataError(
|
|
350
|
+
# description_template=f"Path {{path_invalid}} starts a loop: {loop_str}",
|
|
351
|
+
# path_invalid=loop[0],
|
|
352
|
+
# path=current_path,
|
|
353
|
+
# data=templ,
|
|
354
|
+
# data_full=self._data,
|
|
355
|
+
# data_source=self._source,
|
|
356
|
+
# template_start=self._marker_start_value,
|
|
357
|
+
# template_end=self._marker_end_value,
|
|
358
|
+
# )
|
|
359
|
+
|
|
277
360
|
if isinstance(templ, str):
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
361
|
+
pattern_nested = self._get_value_regex_pattern(level=level + 1)
|
|
362
|
+
templ_nested_filled = pattern_nested.sub(
|
|
363
|
+
lambda x: self._recursive_subst(
|
|
364
|
+
templ=x.group(),
|
|
365
|
+
current_path=current_path,
|
|
366
|
+
relative_path_anchor=get_relative_path(current_path),
|
|
367
|
+
level=level+1,
|
|
368
|
+
internal=True,
|
|
369
|
+
),
|
|
283
370
|
templ
|
|
284
371
|
)
|
|
372
|
+
pattern_value = self._get_value_regex_pattern(level=level)
|
|
373
|
+
whole_match_value = pattern_value.fullmatch(templ_nested_filled)
|
|
374
|
+
if whole_match_value:
|
|
375
|
+
return get_address_value(whole_match_value)
|
|
376
|
+
templ_values_filled = pattern_value.sub(
|
|
377
|
+
lambda x: str(get_address_value(x)),
|
|
378
|
+
templ_nested_filled
|
|
379
|
+
)
|
|
380
|
+
whole_match_list = self._pattern_list.fullmatch(templ_values_filled.strip())
|
|
381
|
+
if whole_match_list:
|
|
382
|
+
return get_address_value(whole_match_list, return_all_matches=True)
|
|
383
|
+
whole_match_unpack = self._pattern_unpack.fullmatch(templ_values_filled.strip())
|
|
384
|
+
if whole_match_unpack:
|
|
385
|
+
submatch_list = self._pattern_list.fullmatch(whole_match_unpack.group(1).strip())
|
|
386
|
+
if submatch_list:
|
|
387
|
+
return get_address_value(submatch_list, return_all_matches=True)
|
|
388
|
+
submatch_code = self._pattern_code.fullmatch(whole_match_unpack.group(1).strip())
|
|
389
|
+
if submatch_code:
|
|
390
|
+
return get_code_value(submatch_code.group(1))
|
|
391
|
+
return get_address_value(whole_match_unpack)
|
|
392
|
+
whole_match_code = self._pattern_code.fullmatch(templ_values_filled.strip())
|
|
393
|
+
if whole_match_code:
|
|
394
|
+
templ_list_filled = self._pattern_list.sub(
|
|
395
|
+
lambda x: str(get_address_value(x, return_all_matches=True)),
|
|
396
|
+
whole_match_code.group(1)
|
|
397
|
+
)
|
|
398
|
+
return get_code_value(templ_list_filled)
|
|
399
|
+
unpacked_filled = self._pattern_unpack.sub(string_filler_unpack, templ_values_filled)
|
|
400
|
+
return self._pattern_code.sub(
|
|
401
|
+
lambda x: self._stringer(get_code_value(x.group(1))),
|
|
402
|
+
unpacked_filled
|
|
403
|
+
)
|
|
404
|
+
|
|
285
405
|
if isinstance(templ, list):
|
|
286
406
|
out = []
|
|
287
407
|
for idx, elem in enumerate(templ):
|
|
288
408
|
new_path = f"{current_path}[{idx}]"
|
|
289
409
|
elem_filled = self._recursive_subst(
|
|
290
|
-
templ=elem,
|
|
410
|
+
templ=elem,
|
|
411
|
+
current_path=new_path,
|
|
412
|
+
relative_path_anchor=get_relative_path(new_path),
|
|
413
|
+
level=0,
|
|
291
414
|
)
|
|
292
|
-
if isinstance(elem, str) and self.
|
|
415
|
+
if isinstance(elem, str) and self._pattern_unpack.fullmatch(elem.strip()):
|
|
293
416
|
out.extend(elem_filled)
|
|
294
417
|
else:
|
|
295
418
|
out.append(elem_filled)
|
|
296
419
|
return out
|
|
420
|
+
|
|
297
421
|
if isinstance(templ, dict):
|
|
298
422
|
new_dict = {}
|
|
299
423
|
for key, val in templ.items():
|
|
300
424
|
key_filled = self._recursive_subst(
|
|
301
|
-
templ=key,
|
|
425
|
+
templ=key,
|
|
426
|
+
current_path=current_path,
|
|
427
|
+
relative_path_anchor=relative_path_anchor,
|
|
428
|
+
level=0,
|
|
429
|
+
internal=True,
|
|
302
430
|
)
|
|
431
|
+
if isinstance(key, str) and self._pattern_unpack.fullmatch(key.strip()):
|
|
432
|
+
new_dict.update(key_filled)
|
|
433
|
+
continue
|
|
303
434
|
if key_filled in self._template_keys:
|
|
304
435
|
new_dict[key_filled] = val
|
|
305
436
|
continue
|
|
306
437
|
new_path = f"{current_path}.'{key_filled}'"
|
|
307
438
|
new_dict[key_filled] = self._recursive_subst(
|
|
308
|
-
templ=val,
|
|
439
|
+
templ=val,
|
|
440
|
+
current_path=new_path,
|
|
441
|
+
relative_path_anchor=get_relative_path(new_path),
|
|
442
|
+
level=0,
|
|
309
443
|
)
|
|
310
444
|
return new_dict
|
|
311
445
|
return templ
|
|
312
446
|
|
|
447
|
+
def _find_loop(self):
|
|
448
|
+
for pattern_length in range(1, len(self._path_history) // 2 + 1):
|
|
449
|
+
# Slice the end of the list into two consecutive patterns
|
|
450
|
+
pattern = self._path_history[-pattern_length:]
|
|
451
|
+
previous_pattern = self._path_history[-2 * pattern_length:-pattern_length]
|
|
452
|
+
# Check if the two patterns are the same
|
|
453
|
+
if pattern == previous_pattern:
|
|
454
|
+
pattern.insert(0, pattern[-1])
|
|
455
|
+
return pattern
|
|
456
|
+
return
|
|
457
|
+
|
|
313
458
|
@staticmethod
|
|
314
459
|
def _remove_leading_periods(s: str) -> (str, int):
|
|
315
460
|
match = _re.match(r"^(\.*)(.*)", s)
|
|
@@ -334,4 +479,24 @@ class TemplateFiller:
|
|
|
334
479
|
return
|
|
335
480
|
fields = []
|
|
336
481
|
_recursive_extract(jsonpath)
|
|
337
|
-
return fields
|
|
482
|
+
return fields
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class _RegexPattern:
|
|
486
|
+
|
|
487
|
+
def __init__(self, start: str, end: str):
|
|
488
|
+
start_esc = _re.escape(start)
|
|
489
|
+
end_esc = _re.escape(end)
|
|
490
|
+
self.pattern = _re.compile(rf"{start_esc}(.*?)(?={end_esc}){end_esc}", re.DOTALL)
|
|
491
|
+
return
|
|
492
|
+
|
|
493
|
+
def fullmatch(self, string: str) -> _re.Match | None:
|
|
494
|
+
# Use findall to count occurrences of segments in the text
|
|
495
|
+
matches = self.pattern.findall(string)
|
|
496
|
+
if len(matches) == 1:
|
|
497
|
+
# Verify the match spans the entire string
|
|
498
|
+
return self.pattern.fullmatch(string)
|
|
499
|
+
return None
|
|
500
|
+
|
|
501
|
+
def sub(self, repl, string: str) -> str:
|
|
502
|
+
return self.pattern.sub(repl, string)
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations as _annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING as _TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
import pyserials as _ps
|
|
6
|
-
|
|
7
|
-
if _TYPE_CHECKING:
|
|
8
|
-
from typing import Callable
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class NestedDict:
|
|
12
|
-
|
|
13
|
-
def __init__(
|
|
14
|
-
self,
|
|
15
|
-
data: dict | None = None,
|
|
16
|
-
template_marker_start: str = "${{",
|
|
17
|
-
template_marker_end: str = "}}",
|
|
18
|
-
template_marker_unpack_start: str = "*{{",
|
|
19
|
-
template_marker_unpack_end: str = "}}",
|
|
20
|
-
template_implicit_root: bool = True,
|
|
21
|
-
template_stringer: Callable[[str], str] = str,
|
|
22
|
-
relative_template_keys: list[str] | None = None,
|
|
23
|
-
):
|
|
24
|
-
self._data = data or {}
|
|
25
|
-
self._templater = _ps.update.TemplateFiller(
|
|
26
|
-
marker_start=template_marker_start,
|
|
27
|
-
marker_end=template_marker_end,
|
|
28
|
-
marker_unpack_start=template_marker_unpack_start,
|
|
29
|
-
marker_unpack_end=template_marker_unpack_end,
|
|
30
|
-
implicit_root=template_implicit_root,
|
|
31
|
-
stringer=template_stringer,
|
|
32
|
-
)
|
|
33
|
-
self._relative_template_keys = relative_template_keys
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
def fill(
|
|
37
|
-
self,
|
|
38
|
-
path: str = "",
|
|
39
|
-
always_list: bool = False,
|
|
40
|
-
recursive: bool = True,
|
|
41
|
-
):
|
|
42
|
-
if not path:
|
|
43
|
-
value = self._data
|
|
44
|
-
else:
|
|
45
|
-
value = self.__getitem__(path)
|
|
46
|
-
if not value:
|
|
47
|
-
return
|
|
48
|
-
filled_value = self.fill_data(
|
|
49
|
-
data=value, current_path=path, always_list=always_list, recursive=recursive,
|
|
50
|
-
)
|
|
51
|
-
if not path:
|
|
52
|
-
self._data = filled_value
|
|
53
|
-
else:
|
|
54
|
-
self.__setitem__(path, filled_value)
|
|
55
|
-
return filled_value
|
|
56
|
-
|
|
57
|
-
def fill_data(
|
|
58
|
-
self,
|
|
59
|
-
data,
|
|
60
|
-
current_path: str = "",
|
|
61
|
-
always_list: bool = False,
|
|
62
|
-
recursive: bool = True,
|
|
63
|
-
):
|
|
64
|
-
return self._templater.fill(
|
|
65
|
-
templated_data=data,
|
|
66
|
-
source_data=self._data,
|
|
67
|
-
current_path=current_path,
|
|
68
|
-
always_list=always_list,
|
|
69
|
-
recursive=recursive,
|
|
70
|
-
relative_template_keys=self._relative_template_keys,
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
def __call__(self):
|
|
74
|
-
return self._data
|
|
75
|
-
|
|
76
|
-
def __getitem__(self, item: str):
|
|
77
|
-
keys = item.split(".")
|
|
78
|
-
data = self._data
|
|
79
|
-
for key in keys:
|
|
80
|
-
if not isinstance(data, dict):
|
|
81
|
-
raise KeyError(f"Key '{key}' not found in '{data}'.")
|
|
82
|
-
if key not in data:
|
|
83
|
-
return
|
|
84
|
-
data = data[key]
|
|
85
|
-
# if isinstance(data, dict):
|
|
86
|
-
# return NestedDict(data)
|
|
87
|
-
# if isinstance(data, list) and all(isinstance(item, dict) for item in data):
|
|
88
|
-
# return [NestedDict(item) for item in data]
|
|
89
|
-
return data
|
|
90
|
-
|
|
91
|
-
def __setitem__(self, key, value):
|
|
92
|
-
key = key.split(".")
|
|
93
|
-
data = self._data
|
|
94
|
-
for k in key[:-1]:
|
|
95
|
-
if k not in data:
|
|
96
|
-
data[k] = {}
|
|
97
|
-
data = data[k]
|
|
98
|
-
data[key[-1]] = value
|
|
99
|
-
return
|
|
100
|
-
|
|
101
|
-
def __contains__(self, item):
|
|
102
|
-
keys = item.split(".")
|
|
103
|
-
data = self._data
|
|
104
|
-
for key in keys:
|
|
105
|
-
if not isinstance(data, dict) or key not in data:
|
|
106
|
-
return False
|
|
107
|
-
data = data[key]
|
|
108
|
-
return True
|
|
109
|
-
|
|
110
|
-
def __bool__(self):
|
|
111
|
-
return bool(self._data)
|
|
112
|
-
|
|
113
|
-
def setdefault(self, key, value):
|
|
114
|
-
key = key.split(".")
|
|
115
|
-
data = self._data
|
|
116
|
-
for k in key[:-1]:
|
|
117
|
-
if k not in data:
|
|
118
|
-
data[k] = {}
|
|
119
|
-
data = data[k]
|
|
120
|
-
return data.setdefault(key[-1], value)
|
|
121
|
-
|
|
122
|
-
def get(self, key, default=None):
|
|
123
|
-
keys = key.split(".")
|
|
124
|
-
data = self._data
|
|
125
|
-
for key in keys:
|
|
126
|
-
if not isinstance(data, dict) or key not in data:
|
|
127
|
-
return default
|
|
128
|
-
data = data[key]
|
|
129
|
-
return data
|
|
130
|
-
|
|
131
|
-
def items(self):
|
|
132
|
-
return self._data.items()
|
|
133
|
-
|
|
134
|
-
def keys(self):
|
|
135
|
-
return self._data.keys()
|
|
136
|
-
|
|
137
|
-
def values(self):
|
|
138
|
-
return self._data.values()
|
|
139
|
-
|
|
140
|
-
def update(self, data: dict):
|
|
141
|
-
self._data.update(data)
|
|
142
|
-
return
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|