PySerials 0.0.0.dev3__py3-none-any.whl → 0.0.0.dev5__py3-none-any.whl
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.dev3.dist-info → PySerials-0.0.0.dev5.dist-info}/METADATA +5 -1
- PySerials-0.0.0.dev5.dist-info/RECORD +17 -0
- {PySerials-0.0.0.dev3.dist-info → PySerials-0.0.0.dev5.dist-info}/WHEEL +1 -1
- pyserials/__init__.py +2 -1
- pyserials/compare.py +29 -0
- pyserials/exception/_base.py +52 -4
- pyserials/exception/read.py +163 -71
- pyserials/exception/update.py +97 -89
- pyserials/exception/validate.py +185 -15
- pyserials/nested_dict.py +124 -0
- pyserials/read.py +53 -16
- pyserials/update.py +179 -93
- pyserials/validate.py +22 -11
- pyserials/write.py +27 -11
- PySerials-0.0.0.dev3.dist-info/RECORD +0 -15
- {PySerials-0.0.0.dev3.dist-info → PySerials-0.0.0.dev5.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PySerials
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev5
|
|
4
4
|
Requires-Python: >=3.10
|
|
5
5
|
Requires-Dist: jsonschema <5,>=4.21.0
|
|
6
6
|
Requires-Dist: ruamel.yaml <0.18,>=0.17.32
|
|
7
7
|
Requires-Dist: ruamel.yaml.string <1,>=0.1.1
|
|
8
8
|
Requires-Dist: tomlkit <0.12,>=0.11.8
|
|
9
|
+
Requires-Dist: markitup
|
|
10
|
+
Requires-Dist: ansi-sgr
|
|
11
|
+
Requires-Dist: ExceptionMan
|
|
12
|
+
Requires-Dist: jsonpath-ng <2,>=1.6.1
|
|
9
13
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
pyserials/__init__.py,sha256=-ySdqDuoUXdi2Pa8uuFa5m1CTAtbZS3SWc5qzaOdR5o,142
|
|
2
|
+
pyserials/compare.py,sha256=j62A1UIiAm08_xONlbZmU2EcH1GMEpDyEQH66dZ2YMM,1297
|
|
3
|
+
pyserials/format.py,sha256=dTukpab6WHSyVRQ9SteY5fhr3GFjWFboEl-1cw_udVY,1729
|
|
4
|
+
pyserials/nested_dict.py,sha256=8cPs4LykXh4stFpaLINPjrAeSyaOyyTdhg0FZZ2YOYA,3361
|
|
5
|
+
pyserials/read.py,sha256=uucYQH1V4GStwRgRZ2eQIXkH4ukB5qz0EA885grwi68,6592
|
|
6
|
+
pyserials/update.py,sha256=GfrFgc1a2qYqR8zs04cngVKcXE0124o20xiJQiSMIxg,9259
|
|
7
|
+
pyserials/validate.py,sha256=Ocs0x0BAV9zwRCgg_J5BGU2kEZV6vVjfDBsR3M9Teus,3564
|
|
8
|
+
pyserials/write.py,sha256=pN8w78qVsKJjZd_jvPUcZjYp_RJkP7uQzpiXvPOv4lM,1776
|
|
9
|
+
pyserials/exception/__init__.py,sha256=ZhbggwJUMlTyBhifAivC8ZQxP1Na6lJAwzZs7_YjOSU,151
|
|
10
|
+
pyserials/exception/_base.py,sha256=JFg1XFOlTfn3HX_AjaM7lCat1M49rnow8pT8WZwH1gk,1956
|
|
11
|
+
pyserials/exception/read.py,sha256=Hvz-Hp2I83WyM8m7JAEa2iVn8o3fOcMhSzG0FvyX1GU,8841
|
|
12
|
+
pyserials/exception/update.py,sha256=LZ8_fX9UvqwHIZdANsp9NUaxOEoMHTuOQ6LPff79k3I,4885
|
|
13
|
+
pyserials/exception/validate.py,sha256=j-WT2F67NEFitv7mQJhvqzj4duvcgSCbzqioE6iJTDo,9683
|
|
14
|
+
PySerials-0.0.0.dev5.dist-info/METADATA,sha256=nmJja_tGErd652uk1PlynTmQ7U-P60uhZyy3vKCCSBY,361
|
|
15
|
+
PySerials-0.0.0.dev5.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
|
16
|
+
PySerials-0.0.0.dev5.dist-info/top_level.txt,sha256=SAks7WjSjdkv3i9Hvt4gY_P7VQbhhYJN5mf5dqx1aao,10
|
|
17
|
+
PySerials-0.0.0.dev5.dist-info/RECORD,,
|
pyserials/__init__.py
CHANGED
pyserials/compare.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
def items(source, target, path: str = "$"):
|
|
2
|
+
def recursive_compare(src, trg, curr_path):
|
|
3
|
+
if type(src) is not type(trg):
|
|
4
|
+
comp["modified"].append(curr_path)
|
|
5
|
+
return
|
|
6
|
+
if isinstance(src, dict):
|
|
7
|
+
for key in src:
|
|
8
|
+
if key not in trg:
|
|
9
|
+
comp["added"].append(f"{curr_path}.{key}")
|
|
10
|
+
continue
|
|
11
|
+
recursive_compare(src[key], trg[key], f"{curr_path}.{key}")
|
|
12
|
+
for key in trg:
|
|
13
|
+
if key not in src:
|
|
14
|
+
comp["removed"].append(f"{curr_path}.{key}")
|
|
15
|
+
return
|
|
16
|
+
if isinstance(src, (list, tuple)):
|
|
17
|
+
len_src = len(src)
|
|
18
|
+
len_trg = len(trg)
|
|
19
|
+
min_len = min(len_src, len_trg)
|
|
20
|
+
for i in range(min_len):
|
|
21
|
+
recursive_compare(src[i], trg[i], f"{curr_path}[{i}]")
|
|
22
|
+
for i in range(min_len, max(len_src, len_trg)):
|
|
23
|
+
comp["added" if len_src > len_trg else "removed"].append(f"{curr_path}[{i}]")
|
|
24
|
+
return
|
|
25
|
+
comp["unchanged" if src == trg else "modified"].append(curr_path)
|
|
26
|
+
return
|
|
27
|
+
comp = {"added": [], "removed": [], "modified": [], "unchanged": []}
|
|
28
|
+
recursive_compare(source, target, path)
|
|
29
|
+
return {key: sorted(comp[key]) for key in comp}
|
pyserials/exception/_base.py
CHANGED
|
@@ -1,10 +1,58 @@
|
|
|
1
1
|
"""PySerials base Exception class."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import Literal as _Literal
|
|
5
|
+
import ansi_sgr as _sgr
|
|
6
|
+
from markitup import html as _html, doc as _doc
|
|
7
|
+
from exceptionman import ReporterException as _ReporterException
|
|
3
8
|
|
|
4
|
-
|
|
9
|
+
|
|
10
|
+
_ANSI_HEADING_STYLE = {
|
|
11
|
+
1: _sgr.style(text_styles="bold", background_color="red"),
|
|
12
|
+
2: _sgr.style(text_styles="bold", background_color="yellow"),
|
|
13
|
+
3: _sgr.style(text_styles="bold", background_color="green"),
|
|
14
|
+
4: _sgr.style(text_styles="bold", background_color="blue"),
|
|
15
|
+
5: _sgr.style(text_styles="bold", background_color="magenta"),
|
|
16
|
+
6: _sgr.style(text_styles="bold", background_color="cyan"),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PySerialsException(_ReporterException):
|
|
5
21
|
"""Base class for all exceptions raised by PySerials."""
|
|
6
22
|
|
|
7
|
-
def __init__(
|
|
8
|
-
self
|
|
9
|
-
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
message: str,
|
|
26
|
+
description: str | None = None,
|
|
27
|
+
message_html: str | _html.Element | None = None,
|
|
28
|
+
description_html: str | _html.Element | None = None,
|
|
29
|
+
report_heading: str = "PySerials Error Report",
|
|
30
|
+
):
|
|
31
|
+
super().__init__(
|
|
32
|
+
message=message,
|
|
33
|
+
description=description,
|
|
34
|
+
message_html=message_html,
|
|
35
|
+
description_html=description_html,
|
|
36
|
+
report_heading=report_heading,
|
|
37
|
+
)
|
|
10
38
|
return
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def ansi_heading(section: list[int], title: str) -> str:
|
|
42
|
+
"""Get an ANSI SGR formatted heading."""
|
|
43
|
+
sec = ".".join(str(n) for n in section)
|
|
44
|
+
sec_level = min(len(section), 6)
|
|
45
|
+
heading = f" {sec}. {title} "
|
|
46
|
+
return _sgr.format(heading, control_sequence=_ANSI_HEADING_STYLE[sec_level])
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def ansi_bold(text: str) -> str:
|
|
50
|
+
return _sgr.format(text, control_sequence=_sgr.style(text_styles="bold"))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def format_code(code: str) -> tuple[str, str]:
|
|
54
|
+
console = _sgr.format(
|
|
55
|
+
code, control_sequence=_sgr.style(text_color=(220, 220, 220), background_color=(20, 20, 20))
|
|
56
|
+
)
|
|
57
|
+
html = str(_html.elem.code(code))
|
|
58
|
+
return console, html
|
pyserials/exception/read.py
CHANGED
|
@@ -1,119 +1,211 @@
|
|
|
1
1
|
"""Exceptions raised by `pyserials.read` module."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
from __future__ import annotations
|
|
4
4
|
from typing import Literal as _Literal
|
|
5
5
|
from pathlib import Path as _Path
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import ruamel.yaml as _yaml
|
|
8
|
+
import json as _json
|
|
8
9
|
|
|
10
|
+
from markitup import html as _html, md as _md
|
|
11
|
+
from tomlkit.exceptions import TOMLKitError as _TOMLKitError
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
"""Base class for all exceptions raised by `pyserials.read` module."""
|
|
12
|
-
|
|
13
|
-
def __init__(self, message: str, source_type: _Literal["file", "string"]):
|
|
14
|
-
super().__init__(message=f"Failed to read data from {source_type}. {message}.")
|
|
15
|
-
return
|
|
13
|
+
from pyserials.exception._base import PySerialsException as _PySerialsException
|
|
16
14
|
|
|
17
15
|
|
|
18
|
-
class
|
|
19
|
-
"""Base class for all exceptions raised
|
|
16
|
+
class PySerialsReadException(_PySerialsException):
|
|
17
|
+
"""Base class for all exceptions raised by `pyserials.read` module.
|
|
20
18
|
|
|
21
19
|
Attributes
|
|
22
20
|
----------
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
source_type : {"file", "string"}
|
|
22
|
+
Type of source from which data was read.
|
|
23
|
+
data_type : {"json", "yaml", "toml"} or None
|
|
24
|
+
Type of input data, if known.
|
|
25
|
+
filepath : pathlib.Path or None
|
|
26
|
+
Path to the input datafile, if data was read from a file.
|
|
25
27
|
"""
|
|
26
|
-
def __init__(self, message: str, filepath: _Path):
|
|
27
|
-
super().__init__(message=message, source_type="file")
|
|
28
|
-
self.filepath: _Path = filepath
|
|
29
|
-
return
|
|
30
28
|
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
source_type: _Literal["file", "string"],
|
|
32
|
+
description: str,
|
|
33
|
+
description_html: str | _html.Element | None = None,
|
|
34
|
+
data_type: _Literal["json", "yaml", "toml"] | None = None,
|
|
35
|
+
filepath: _Path | None = None,
|
|
36
|
+
):
|
|
37
|
+
if source_type == "string":
|
|
38
|
+
source = source_html = "string"
|
|
39
|
+
else:
|
|
40
|
+
source = f"file at '{filepath}'"
|
|
41
|
+
source_html = f"file at <code>{filepath}</code>"
|
|
42
|
+
data_ = f"{data_type.upper()} data" if data_type else "data"
|
|
43
|
+
message_template = f"Failed to read {data_} from input {{source}}."
|
|
44
|
+
super().__init__(
|
|
45
|
+
message=message_template.format(source=source),
|
|
46
|
+
message_html=message_template.format(source=source_html),
|
|
47
|
+
description=description,
|
|
48
|
+
description_html=description_html,
|
|
49
|
+
report_heading="PySerials Read Error Report",
|
|
50
|
+
)
|
|
51
|
+
self.source_type: _Literal["file", "string"] = source_type
|
|
52
|
+
self.data_type: _Literal["json", "yaml", "toml"] | None = data_type
|
|
53
|
+
self.filepath: _Path | None = filepath
|
|
54
|
+
return
|
|
31
55
|
|
|
32
|
-
class PySerialsReadFromStringException(PySerialsReadException):
|
|
33
|
-
"""Base class for all exceptions raised when reading data from a string.
|
|
34
56
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
data_type : {"json", "yaml", "toml"}
|
|
38
|
-
The type of data.
|
|
39
|
-
"""
|
|
57
|
+
class PySerialsEmptyStringError(PySerialsReadException):
|
|
58
|
+
"""Exception raised when a string to be read is empty."""
|
|
40
59
|
|
|
41
|
-
def __init__(self,
|
|
42
|
-
|
|
43
|
-
|
|
60
|
+
def __init__(self, data_type: _Literal["json", "yaml", "toml"]):
|
|
61
|
+
description = f"The string is empty."
|
|
62
|
+
super().__init__(description=description, source_type="string", data_type=data_type)
|
|
44
63
|
return
|
|
45
64
|
|
|
46
65
|
|
|
47
|
-
class PySerialsInvalidFileExtensionError(
|
|
66
|
+
class PySerialsInvalidFileExtensionError(PySerialsReadException):
|
|
48
67
|
"""Exception raised when a file to be read has an unrecognized extension."""
|
|
49
68
|
|
|
50
69
|
def __init__(self, filepath: _Path):
|
|
51
|
-
|
|
52
|
-
f"The extension
|
|
53
|
-
f"
|
|
70
|
+
description = (
|
|
71
|
+
f"The file extension must be one of 'json', 'yaml', 'yml', or '.toml', "
|
|
72
|
+
f"but got '{filepath.suffix.removeprefix('.')}'. "
|
|
54
73
|
"Please provide the extension explicitly, or rename the file to have a valid extension."
|
|
55
74
|
)
|
|
56
|
-
super().__init__(
|
|
75
|
+
super().__init__(description=description, source_type="file", filepath=filepath)
|
|
57
76
|
return
|
|
58
77
|
|
|
59
78
|
|
|
60
|
-
class PySerialsMissingFileError(
|
|
79
|
+
class PySerialsMissingFileError(PySerialsReadException):
|
|
61
80
|
"""Exception raised when a file to be read does not exist."""
|
|
62
81
|
|
|
63
|
-
def __init__(self, filepath: _Path):
|
|
64
|
-
|
|
65
|
-
super().__init__(
|
|
82
|
+
def __init__(self, data_type: _Literal["json", "yaml", "toml"], filepath: _Path):
|
|
83
|
+
description = f"The file does not exist."
|
|
84
|
+
super().__init__(description=description, source_type="file", data_type=data_type, filepath=filepath)
|
|
66
85
|
return
|
|
67
86
|
|
|
68
87
|
|
|
69
|
-
class PySerialsEmptyFileError(
|
|
88
|
+
class PySerialsEmptyFileError(PySerialsReadException):
|
|
70
89
|
"""Exception raised when a file to be read is empty."""
|
|
71
90
|
|
|
72
|
-
def __init__(self, filepath: _Path):
|
|
73
|
-
|
|
74
|
-
super().__init__(
|
|
91
|
+
def __init__(self, data_type: _Literal["json", "yaml", "toml"], filepath: _Path):
|
|
92
|
+
description = f"The file is empty."
|
|
93
|
+
super().__init__(description=description, source_type="file", data_type=data_type, filepath=filepath)
|
|
75
94
|
return
|
|
76
95
|
|
|
77
96
|
|
|
78
|
-
class
|
|
79
|
-
"""Exception raised when
|
|
97
|
+
class PySerialsInvalidDataError(PySerialsReadException):
|
|
98
|
+
"""Exception raised when the data is invalid.
|
|
80
99
|
|
|
81
100
|
Attributes
|
|
82
101
|
----------
|
|
83
102
|
data : str
|
|
84
103
|
The input data that was supposed to be read.
|
|
85
|
-
data_type : {"json", "yaml", "toml"}
|
|
86
|
-
The type of data.
|
|
87
104
|
"""
|
|
88
105
|
|
|
89
|
-
def __init__(
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
source_type: _Literal["file", "string"],
|
|
109
|
+
data_type: _Literal["json", "yaml", "toml"],
|
|
110
|
+
data: str,
|
|
111
|
+
cause: Exception,
|
|
112
|
+
filepath: _Path | None = None,
|
|
113
|
+
):
|
|
92
114
|
self.data = data
|
|
93
|
-
self.
|
|
115
|
+
self.cause = cause
|
|
116
|
+
self.problem: str = str(cause)
|
|
117
|
+
self.problem_line: int | None = None
|
|
118
|
+
self.problem_column: int | None = None
|
|
119
|
+
self.problem_data_type: str | None = None
|
|
120
|
+
self.context: str | None = None
|
|
121
|
+
self.context_line: int | None = None
|
|
122
|
+
self.context_column: int | None = None
|
|
123
|
+
self.context_data_type: str | None = None
|
|
124
|
+
|
|
125
|
+
if isinstance(cause, _yaml.YAMLError):
|
|
126
|
+
self.problem_line = cause.problem_mark.line + 1
|
|
127
|
+
self.problem_column = cause.problem_mark.column + 1
|
|
128
|
+
self.problem_data_type = cause.problem_mark.name
|
|
129
|
+
self.problem = cause.problem.strip()
|
|
130
|
+
if cause.context:
|
|
131
|
+
self.context = cause.context.strip()
|
|
132
|
+
self.context_line = cause.context_mark.line + 1
|
|
133
|
+
self.context_column = cause.context_mark.column + 1
|
|
134
|
+
self.context_data_type = cause.context_mark.name
|
|
135
|
+
elif isinstance(cause, _json.JSONDecodeError):
|
|
136
|
+
self.problem = cause.msg
|
|
137
|
+
self.problem_line = cause.lineno
|
|
138
|
+
self.problem_column = cause.colno
|
|
139
|
+
elif isinstance(cause, _TOMLKitError):
|
|
140
|
+
self.problem_line = cause.line
|
|
141
|
+
self.problem_column = cause.col
|
|
142
|
+
self.problem = cause.args[0].removesuffix(f" at line {self.problem_line} col {self.problem_column}")
|
|
143
|
+
self.problem = self.problem.strip().capitalize().removesuffix(".")
|
|
144
|
+
description = "The data is not valid"
|
|
145
|
+
if self.problem_line:
|
|
146
|
+
description += f" at line {self.problem_line}, column {self.problem_column}"
|
|
147
|
+
description += f": {self.problem}."
|
|
148
|
+
super().__init__(description=description, source_type=source_type, data_type=data_type, filepath=filepath)
|
|
94
149
|
return
|
|
95
150
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
151
|
+
def _report_content(self, mode: _Literal["full", "short"], md: bool) -> _html.elem.Ul:
|
|
152
|
+
|
|
153
|
+
def make_tabel(problem, line, column, data_type):
|
|
154
|
+
rows = [
|
|
155
|
+
[title, value] for title, value in [
|
|
156
|
+
["Description", problem],
|
|
157
|
+
["Line Number", line],
|
|
158
|
+
["Column Number", column],
|
|
159
|
+
["Data Type", data_type],
|
|
160
|
+
] if value is not None
|
|
161
|
+
]
|
|
162
|
+
return _html.elem.table_from_rows(rows_body=rows)
|
|
163
|
+
|
|
164
|
+
content_list = [
|
|
165
|
+
_html.elem.details(
|
|
166
|
+
[
|
|
167
|
+
_html.elem.summary("❌ Problem"),
|
|
168
|
+
make_tabel(self.problem, self.problem_line, self.problem_column, self.problem_data_type)
|
|
169
|
+
]
|
|
170
|
+
),
|
|
171
|
+
]
|
|
172
|
+
if self.context:
|
|
173
|
+
content_list.append(
|
|
174
|
+
_html.elem.details(
|
|
175
|
+
[
|
|
176
|
+
_html.elem.summary("🔍 Context"),
|
|
177
|
+
make_tabel(self.context, self.context_line, self.context_column, self.context_data_type)
|
|
178
|
+
]
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
data_lines = self.data.splitlines()
|
|
182
|
+
if self.problem_line:
|
|
183
|
+
line_idx = self.problem_line - 1
|
|
184
|
+
problem_line = data_lines[line_idx]
|
|
185
|
+
if md:
|
|
186
|
+
problem_line = f"- {problem_line}"
|
|
187
|
+
else:
|
|
188
|
+
if self.problem_column:
|
|
189
|
+
col_idx = self.problem_column - 1
|
|
190
|
+
prob_col = problem_line[col_idx]
|
|
191
|
+
prob_col_highlight = _html.elem.span(prob_col, {"class": "highlight-char"})
|
|
192
|
+
problem_line = f"{problem_line[:col_idx]}{prob_col_highlight}{problem_line[col_idx+1:]}"
|
|
193
|
+
problem_line = str(_html.elem.span(problem_line, {"class": "highlight-line"}))
|
|
194
|
+
data_lines[line_idx] = problem_line
|
|
195
|
+
if mode == "short":
|
|
196
|
+
if self.problem_line is not None:
|
|
197
|
+
problem_line_idx = self.problem_line - 1
|
|
198
|
+
start_line = max(0, problem_line_idx)
|
|
199
|
+
end_line = min(len(data_lines), problem_line_idx + 1)
|
|
200
|
+
if self.context_line is not None:
|
|
201
|
+
context_line_idx = self.context_line - 1
|
|
202
|
+
start_line = max(0, context_line_idx, problem_line_idx)
|
|
203
|
+
end_line = min(len(data_lines), context_line_idx + 1, problem_line_idx + 1)
|
|
204
|
+
data_lines = data_lines[start_line:end_line]
|
|
205
|
+
data = "\n".join(data_lines)
|
|
206
|
+
if md:
|
|
207
|
+
code_block = _md.elem.code_fence(data, info="diff")
|
|
208
|
+
else:
|
|
209
|
+
code_block = _html.elem.pre(_html.elem.code(data, {"class": f"language-{self.data_type}"}))
|
|
210
|
+
content_list.append(_html.elem.details([_html.elem.summary("📄 Data"), code_block]))
|
|
211
|
+
return _html.elem.ul([_html.elem.li(content) for content in content_list])
|
pyserials/exception/update.py
CHANGED
|
@@ -1,131 +1,139 @@
|
|
|
1
1
|
"""Exceptions raised by `pyserials.update` module."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import Any as _Any, Literal as _Literal
|
|
4
5
|
|
|
5
|
-
from
|
|
6
|
+
from markitup.html import elem as _html
|
|
6
7
|
|
|
8
|
+
from pyserials.exception import _base
|
|
7
9
|
|
|
8
|
-
class PySerialsUpdateException(_PySerialsException):
|
|
9
|
-
"""Base class for all exceptions raised by `pyserials.update` module."""
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class PySerialsUpdateDictFromAddonException(PySerialsUpdateException):
|
|
17
|
-
"""Base class for all exceptions raised by `pyserials.update.dict_from_addon`.
|
|
11
|
+
class PySerialsUpdateException(_base.PySerialsException):
|
|
12
|
+
"""Base class for all exceptions raised by `pyserials.update` module.
|
|
18
13
|
|
|
19
14
|
Attributes
|
|
20
15
|
----------
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
path : str
|
|
17
|
+
JSONPath to where the update failed.
|
|
18
|
+
data : dict | list | str | int | float | bool
|
|
19
|
+
Data that failed to update.
|
|
20
|
+
data_full : dict | list | str | int | float | bool
|
|
21
|
+
Full data input.
|
|
27
22
|
"""
|
|
28
23
|
|
|
29
24
|
def __init__(
|
|
30
25
|
self,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
path: str,
|
|
27
|
+
data: dict | list | str | int | float | bool,
|
|
28
|
+
data_full: dict | list | str | int | float | bool,
|
|
29
|
+
description: str,
|
|
30
|
+
description_html: str | _html.Element | None = None,
|
|
35
31
|
):
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
message_template = "Failed to update data at {path}."
|
|
33
|
+
path_console, path_html = _base.format_code(path)
|
|
34
|
+
super().__init__(
|
|
35
|
+
message=message_template.format(path=path_console),
|
|
36
|
+
message_html=message_template.format(path=path_html),
|
|
37
|
+
description=description,
|
|
38
|
+
description_html=description_html,
|
|
39
|
+
report_heading="PySerials Update Error Report",
|
|
40
|
+
)
|
|
41
|
+
self.path = path
|
|
42
|
+
self.data = data
|
|
43
|
+
self.data_full = data_full
|
|
40
44
|
return
|
|
41
45
|
|
|
42
46
|
|
|
43
|
-
class
|
|
44
|
-
"""Base class for all exceptions raised by `pyserials.update.
|
|
47
|
+
class PySerialsUpdateDictFromAddonError(PySerialsUpdateException):
|
|
48
|
+
"""Base class for all exceptions raised by `pyserials.update.dict_from_addon`.
|
|
45
49
|
|
|
46
50
|
Attributes
|
|
47
51
|
----------
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
template_start : str
|
|
53
|
-
The start marker of the template.
|
|
54
|
-
template_end : str
|
|
55
|
-
The end marker of the template.
|
|
52
|
+
data_addon : Any
|
|
53
|
+
Value of the failed data in the addon dictionary.
|
|
54
|
+
data_addon_full : dictionary
|
|
55
|
+
Full addon input.
|
|
56
56
|
"""
|
|
57
57
|
|
|
58
58
|
def __init__(
|
|
59
59
|
self,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
problem_type: _Literal["duplicate", "type_mismatch"],
|
|
61
|
+
path: str,
|
|
62
|
+
data: _Any,
|
|
63
|
+
data_full: dict,
|
|
64
|
+
data_addon: _Any,
|
|
65
|
+
data_addon_full: dict,
|
|
65
66
|
):
|
|
66
|
-
|
|
67
|
-
self.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
message = (
|
|
79
|
-
f"There was a type mismatch between the source and addon dictionary values at '{address}'; "
|
|
80
|
-
f"the value is of type '{type(value_data).__name__}' in the source data, "
|
|
81
|
-
f"but of type '{type(value_addon).__name__}' in the addon data"
|
|
67
|
+
self.type_data = type(data)
|
|
68
|
+
self.type_data_addon = type(data_addon)
|
|
69
|
+
type_data_console, type_data_html = _base.format_code(self.type_data.__name__)
|
|
70
|
+
type_data_addon_console, type_data_addon_html = _base.format_code(self.type_data_addon.__name__)
|
|
71
|
+
path_console, path_html = _base.format_code(path)
|
|
72
|
+
kwargs_console, kwargs_html = (
|
|
73
|
+
{"path": path, "type_data": type_data, "type_data_addon": type_data_addon}
|
|
74
|
+
for path, type_data, type_data_addon in zip(
|
|
75
|
+
(path_console, path_html),
|
|
76
|
+
(type_data_console, type_data_html),
|
|
77
|
+
(type_data_addon_console, type_data_addon_html),
|
|
78
|
+
)
|
|
82
79
|
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
80
|
+
description_template = (
|
|
81
|
+
"There was a duplicate in the addon dictionary; "
|
|
82
|
+
"the value of type {type_data_addon} already exists in the source data."
|
|
83
|
+
) if problem_type == "duplicate" else (
|
|
84
|
+
"There was a type mismatch between the source and addon dictionary values; "
|
|
85
|
+
"the value is of type {type_data} in the source data, "
|
|
86
|
+
"but of type {type_data_addon} in the addon data."
|
|
87
|
+
)
|
|
88
|
+
super().__init__(
|
|
89
|
+
description=description_template.format(**kwargs_console),
|
|
90
|
+
description_html=description_template.format(**kwargs_html),
|
|
91
|
+
path=path,
|
|
92
|
+
data=data,
|
|
93
|
+
data_full=data_full,
|
|
94
94
|
)
|
|
95
|
-
|
|
95
|
+
self.problem_type: _Literal["duplicate", "type_mismatch"] = problem_type
|
|
96
|
+
self.data_addon = data_addon
|
|
97
|
+
self.data_addon_full = data_addon_full
|
|
96
98
|
return
|
|
97
99
|
|
|
98
100
|
|
|
99
|
-
class
|
|
100
|
-
"""Exception raised when
|
|
101
|
+
class PySerialsUpdateTemplatedDataError(PySerialsUpdateException):
|
|
102
|
+
"""Exception raised when updating templated data fails.
|
|
101
103
|
|
|
102
104
|
Attributes
|
|
103
105
|
----------
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
path_invalid : str
|
|
107
|
+
JSONPath that caused the update to fail.
|
|
108
|
+
data_source : dict
|
|
109
|
+
Source data that was used to update the template.
|
|
110
|
+
template_start : str
|
|
111
|
+
The start marker of the template.
|
|
112
|
+
template_end : str
|
|
113
|
+
The end marker of the template.
|
|
108
114
|
"""
|
|
109
115
|
|
|
110
116
|
def __init__(
|
|
111
117
|
self,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
description_template: str,
|
|
119
|
+
path_invalid: str,
|
|
120
|
+
path: str,
|
|
121
|
+
data: str,
|
|
122
|
+
data_full: dict | list | str | int | float | bool,
|
|
123
|
+
data_source: dict,
|
|
116
124
|
template_start: str,
|
|
117
|
-
template_end: str
|
|
125
|
+
template_end: str,
|
|
118
126
|
):
|
|
119
|
-
|
|
120
|
-
f"The key/index '{address_missing}' is missing in the source data at '{address_full}'"
|
|
121
|
-
)
|
|
127
|
+
path_invalid_console, path_invalid_html = _base.format_code(path_invalid.replace("'", ""))
|
|
122
128
|
super().__init__(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
description=description_template.format(path_invalid=path_invalid_console),
|
|
130
|
+
description_html=description_template.format(path_invalid=path_invalid_html),
|
|
131
|
+
path=path.replace("'", ""),
|
|
132
|
+
data=data,
|
|
133
|
+
data_full=data_full,
|
|
128
134
|
)
|
|
129
|
-
self.
|
|
130
|
-
self.
|
|
135
|
+
self.path_invalid = path_invalid
|
|
136
|
+
self.data_source = data_source
|
|
137
|
+
self.template_start = template_start
|
|
138
|
+
self.template_end = template_end
|
|
131
139
|
return
|