PySerials 0.0.0.dev5__py3-none-any.whl → 0.0.0.dev6__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.dev5.dist-info → PySerials-0.0.0.dev6.dist-info}/METADATA +2 -2
- {PySerials-0.0.0.dev5.dist-info → PySerials-0.0.0.dev6.dist-info}/RECORD +8 -8
- {PySerials-0.0.0.dev5.dist-info → PySerials-0.0.0.dev6.dist-info}/WHEEL +1 -1
- pyserials/exception/_base.py +13 -45
- pyserials/exception/read.py +114 -85
- pyserials/exception/update.py +37 -38
- pyserials/exception/validate.py +113 -140
- {PySerials-0.0.0.dev5.dist-info → PySerials-0.0.0.dev6.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: PySerials
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev6
|
|
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:
|
|
9
|
+
Requires-Dist: mdit
|
|
10
10
|
Requires-Dist: ansi-sgr
|
|
11
11
|
Requires-Dist: ExceptionMan
|
|
12
12
|
Requires-Dist: jsonpath-ng <2,>=1.6.1
|
|
@@ -7,11 +7,11 @@ pyserials/update.py,sha256=GfrFgc1a2qYqR8zs04cngVKcXE0124o20xiJQiSMIxg,9259
|
|
|
7
7
|
pyserials/validate.py,sha256=Ocs0x0BAV9zwRCgg_J5BGU2kEZV6vVjfDBsR3M9Teus,3564
|
|
8
8
|
pyserials/write.py,sha256=pN8w78qVsKJjZd_jvPUcZjYp_RJkP7uQzpiXvPOv4lM,1776
|
|
9
9
|
pyserials/exception/__init__.py,sha256=ZhbggwJUMlTyBhifAivC8ZQxP1Na6lJAwzZs7_YjOSU,151
|
|
10
|
-
pyserials/exception/_base.py,sha256=
|
|
11
|
-
pyserials/exception/read.py,sha256=
|
|
12
|
-
pyserials/exception/update.py,sha256=
|
|
13
|
-
pyserials/exception/validate.py,sha256=
|
|
14
|
-
PySerials-0.0.0.
|
|
15
|
-
PySerials-0.0.0.
|
|
16
|
-
PySerials-0.0.0.
|
|
17
|
-
PySerials-0.0.0.
|
|
10
|
+
pyserials/exception/_base.py,sha256=Juns3cyAEV9bZ4_nYuefQnPOrXvpQTY_7peXnoCjlgI,765
|
|
11
|
+
pyserials/exception/read.py,sha256=4_OQ6SUvxR61TN0_1v5SK5MgBeJlgYz8j9vJb_6abNY,9275
|
|
12
|
+
pyserials/exception/update.py,sha256=P0js2-iY2fgO_KLdqedgWE3TTS5Xz15cusZY_wuKIW4,4222
|
|
13
|
+
pyserials/exception/validate.py,sha256=gsvbq1bIHHKlIf-KJc7-PSzDZmYDxUFWiYEApJgcm74,7465
|
|
14
|
+
PySerials-0.0.0.dev6.dist-info/METADATA,sha256=-o3uQuTjzfx7Q7zLSCi9CRHLgDIKtYKAtnLK6HVqzxc,357
|
|
15
|
+
PySerials-0.0.0.dev6.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
|
16
|
+
PySerials-0.0.0.dev6.dist-info/top_level.txt,sha256=SAks7WjSjdkv3i9Hvt4gY_P7VQbhhYJN5mf5dqx1aao,10
|
|
17
|
+
PySerials-0.0.0.dev6.dist-info/RECORD,,
|
pyserials/exception/_base.py
CHANGED
|
@@ -1,58 +1,26 @@
|
|
|
1
1
|
"""PySerials base Exception class."""
|
|
2
2
|
|
|
3
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
|
|
8
4
|
|
|
5
|
+
from typing import TYPE_CHECKING as _TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from exceptionman import ReporterException as _ReporterException
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
}
|
|
9
|
+
if _TYPE_CHECKING:
|
|
10
|
+
from mdit import Document
|
|
18
11
|
|
|
19
12
|
|
|
20
13
|
class PySerialsException(_ReporterException):
|
|
21
14
|
"""Base class for all exceptions raised by PySerials."""
|
|
22
15
|
|
|
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
|
-
):
|
|
16
|
+
def __init__(self, report: Document):
|
|
31
17
|
super().__init__(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
report=report,
|
|
19
|
+
sphinx_config={
|
|
20
|
+
"extensions": ['myst_parser', 'sphinx_togglebutton'],
|
|
21
|
+
"myst_enable_extensions": ["colon_fence", "fieldlist"],
|
|
22
|
+
"html_theme": "pydata_sphinx_theme",
|
|
23
|
+
"html_title": "PySerials Error Report",
|
|
24
|
+
}
|
|
37
25
|
)
|
|
38
26
|
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,19 +1,19 @@
|
|
|
1
1
|
"""Exceptions raised by `pyserials.read` module."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
from typing import Literal as _Literal
|
|
4
|
+
from typing import Literal as _Literal, TYPE_CHECKING as _TYPE_CHECKING
|
|
5
5
|
from pathlib import Path as _Path
|
|
6
6
|
|
|
7
7
|
import ruamel.yaml as _yaml
|
|
8
8
|
import json as _json
|
|
9
|
+
import mdit as _mdit
|
|
9
10
|
|
|
10
|
-
from markitup import html as _html, md as _md
|
|
11
11
|
from tomlkit.exceptions import TOMLKitError as _TOMLKitError
|
|
12
12
|
|
|
13
|
-
from pyserials.exception
|
|
13
|
+
from pyserials.exception import _base
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class PySerialsReadException(
|
|
16
|
+
class PySerialsReadException(_base.PySerialsException):
|
|
17
17
|
"""Base class for all exceptions raised by `pyserials.read` module.
|
|
18
18
|
|
|
19
19
|
Attributes
|
|
@@ -29,25 +29,28 @@ class PySerialsReadException(_PySerialsException):
|
|
|
29
29
|
def __init__(
|
|
30
30
|
self,
|
|
31
31
|
source_type: _Literal["file", "string"],
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
problem,
|
|
33
|
+
section: dict | None = None,
|
|
34
34
|
data_type: _Literal["json", "yaml", "toml"] | None = None,
|
|
35
35
|
filepath: _Path | None = None,
|
|
36
36
|
):
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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",
|
|
37
|
+
intro = _mdit.inline_container(
|
|
38
|
+
"Failed to read ",
|
|
39
|
+
f"{data_type.upper()} data " if data_type else "data ",
|
|
40
|
+
"from input ",
|
|
41
|
+
"string." if source_type == "string" else _mdit.inline_container(
|
|
42
|
+
"file at ", _mdit.element.code_span(filepath), "."
|
|
43
|
+
),
|
|
50
44
|
)
|
|
45
|
+
report = _mdit.document(
|
|
46
|
+
heading="Data Read Error",
|
|
47
|
+
body={
|
|
48
|
+
"intro": intro,
|
|
49
|
+
"problem": problem,
|
|
50
|
+
},
|
|
51
|
+
section=section,
|
|
52
|
+
)
|
|
53
|
+
super().__init__(report)
|
|
51
54
|
self.source_type: _Literal["file", "string"] = source_type
|
|
52
55
|
self.data_type: _Literal["json", "yaml", "toml"] | None = data_type
|
|
53
56
|
self.filepath: _Path | None = filepath
|
|
@@ -58,8 +61,8 @@ class PySerialsEmptyStringError(PySerialsReadException):
|
|
|
58
61
|
"""Exception raised when a string to be read is empty."""
|
|
59
62
|
|
|
60
63
|
def __init__(self, data_type: _Literal["json", "yaml", "toml"]):
|
|
61
|
-
|
|
62
|
-
super().__init__(
|
|
64
|
+
problem = f"The string is empty."
|
|
65
|
+
super().__init__(problem=problem, source_type="string", data_type=data_type)
|
|
63
66
|
return
|
|
64
67
|
|
|
65
68
|
|
|
@@ -67,12 +70,20 @@ class PySerialsInvalidFileExtensionError(PySerialsReadException):
|
|
|
67
70
|
"""Exception raised when a file to be read has an unrecognized extension."""
|
|
68
71
|
|
|
69
72
|
def __init__(self, filepath: _Path):
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"
|
|
73
|
+
problem = _mdit.inline_container(
|
|
74
|
+
"The file extension must be one of ",
|
|
75
|
+
_mdit.element.code_span('json'),
|
|
76
|
+
", ",
|
|
77
|
+
_mdit.element.code_span('yaml'),
|
|
78
|
+
", ",
|
|
79
|
+
_mdit.element.code_span('yml'),
|
|
80
|
+
", or ",
|
|
81
|
+
_mdit.element.code_span('.toml'),
|
|
82
|
+
", but got ",
|
|
83
|
+
_mdit.element.code_span(str(filepath.suffix.removeprefix('.'))),
|
|
84
|
+
". Please provide the extension explicitly, or rename the file to have a valid extension."
|
|
74
85
|
)
|
|
75
|
-
super().__init__(
|
|
86
|
+
super().__init__(problem=problem, source_type="file", filepath=filepath)
|
|
76
87
|
return
|
|
77
88
|
|
|
78
89
|
|
|
@@ -80,8 +91,8 @@ class PySerialsMissingFileError(PySerialsReadException):
|
|
|
80
91
|
"""Exception raised when a file to be read does not exist."""
|
|
81
92
|
|
|
82
93
|
def __init__(self, data_type: _Literal["json", "yaml", "toml"], filepath: _Path):
|
|
83
|
-
|
|
84
|
-
super().__init__(
|
|
94
|
+
problem = f"The file does not exist."
|
|
95
|
+
super().__init__(problem=problem, source_type="file", data_type=data_type, filepath=filepath)
|
|
85
96
|
return
|
|
86
97
|
|
|
87
98
|
|
|
@@ -89,8 +100,8 @@ class PySerialsEmptyFileError(PySerialsReadException):
|
|
|
89
100
|
"""Exception raised when a file to be read is empty."""
|
|
90
101
|
|
|
91
102
|
def __init__(self, data_type: _Literal["json", "yaml", "toml"], filepath: _Path):
|
|
92
|
-
|
|
93
|
-
super().__init__(
|
|
103
|
+
problem = f"The file is empty."
|
|
104
|
+
super().__init__(problem=problem, source_type="file", data_type=data_type, filepath=filepath)
|
|
94
105
|
return
|
|
95
106
|
|
|
96
107
|
|
|
@@ -121,17 +132,18 @@ class PySerialsInvalidDataError(PySerialsReadException):
|
|
|
121
132
|
self.context_line: int | None = None
|
|
122
133
|
self.context_column: int | None = None
|
|
123
134
|
self.context_data_type: str | None = None
|
|
135
|
+
self.data_type = data_type
|
|
124
136
|
|
|
125
137
|
if isinstance(cause, _yaml.YAMLError):
|
|
126
138
|
self.problem_line = cause.problem_mark.line + 1
|
|
127
139
|
self.problem_column = cause.problem_mark.column + 1
|
|
128
|
-
self.problem_data_type = cause.problem_mark.name
|
|
140
|
+
self.problem_data_type = cause.problem_mark.name.removeprefix("<").removesuffix(">")
|
|
129
141
|
self.problem = cause.problem.strip()
|
|
130
142
|
if cause.context:
|
|
131
143
|
self.context = cause.context.strip()
|
|
132
144
|
self.context_line = cause.context_mark.line + 1
|
|
133
145
|
self.context_column = cause.context_mark.column + 1
|
|
134
|
-
self.context_data_type = cause.context_mark.name
|
|
146
|
+
self.context_data_type = cause.context_mark.name.removeprefix("<").removesuffix(">")
|
|
135
147
|
elif isinstance(cause, _json.JSONDecodeError):
|
|
136
148
|
self.problem = cause.msg
|
|
137
149
|
self.problem_line = cause.lineno
|
|
@@ -141,71 +153,88 @@ class PySerialsInvalidDataError(PySerialsReadException):
|
|
|
141
153
|
self.problem_column = cause.col
|
|
142
154
|
self.problem = cause.args[0].removesuffix(f" at line {self.problem_line} col {self.problem_column}")
|
|
143
155
|
self.problem = self.problem.strip().capitalize().removesuffix(".")
|
|
144
|
-
description = "The data is not valid"
|
|
156
|
+
description = ["The data is not valid"]
|
|
145
157
|
if self.problem_line:
|
|
146
|
-
description
|
|
147
|
-
|
|
148
|
-
|
|
158
|
+
description.extend(
|
|
159
|
+
[
|
|
160
|
+
" at line ",
|
|
161
|
+
_mdit.element.code_span(str(self.problem_line)),
|
|
162
|
+
", column ",
|
|
163
|
+
_mdit.element.code_span(str(self.problem_column)),
|
|
164
|
+
]
|
|
165
|
+
)
|
|
166
|
+
description.append(f": {self.problem}.")
|
|
167
|
+
super().__init__(
|
|
168
|
+
problem=description,
|
|
169
|
+
source_type=source_type,
|
|
170
|
+
section=self._report_content(),
|
|
171
|
+
data_type=data_type,
|
|
172
|
+
filepath=filepath
|
|
173
|
+
)
|
|
149
174
|
return
|
|
150
175
|
|
|
151
|
-
def _report_content(self
|
|
176
|
+
def _report_content(self) -> dict:
|
|
152
177
|
|
|
153
178
|
def make_tabel(problem, line, column, data_type):
|
|
154
|
-
|
|
155
|
-
|
|
179
|
+
items = [
|
|
180
|
+
_mdit.element.field_list_item(title=title, description=value) for title, value in [
|
|
156
181
|
["Description", problem],
|
|
157
182
|
["Line Number", line],
|
|
158
183
|
["Column Number", column],
|
|
159
184
|
["Data Type", data_type],
|
|
160
185
|
] if value is not None
|
|
161
186
|
]
|
|
162
|
-
return
|
|
187
|
+
return _mdit.element.field_list(items)
|
|
163
188
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
]
|
|
189
|
+
content = {
|
|
190
|
+
"problem_details": _mdit.element.admonition(
|
|
191
|
+
type="error",
|
|
192
|
+
title="Problem",
|
|
193
|
+
content=make_tabel(self.problem, self.problem_line, self.problem_column, self.problem_data_type)
|
|
194
|
+
)
|
|
195
|
+
}
|
|
172
196
|
if self.context:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
197
|
+
content["context_details"] = _mdit.element.admonition(
|
|
198
|
+
type="note",
|
|
199
|
+
title="Context",
|
|
200
|
+
content=make_tabel(self.context, self.context_line, self.context_column, self.context_data_type)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
code_block_full = _mdit.element.code_block(
|
|
204
|
+
content=self.data,
|
|
205
|
+
language=self.data_type,
|
|
206
|
+
caption="Data",
|
|
207
|
+
line_num=True,
|
|
208
|
+
emphasize_lines=[line for line in (self.problem_line, self.context_line) if line],
|
|
209
|
+
degrade_to_diff=True,
|
|
210
|
+
)
|
|
211
|
+
content["data_full"] = (code_block_full, "full")
|
|
212
|
+
|
|
213
|
+
if not (self.problem_line or self.context_line):
|
|
214
|
+
code_block_short = _mdit.element.code_block(
|
|
215
|
+
content=self.data[:1000].strip() + "\n..." if len(self.data) > 1000 else self.data,
|
|
216
|
+
language=self.data_type,
|
|
217
|
+
caption="Data" if len(self.data) <= 1000 else "Data (truncated to first 1000 characters)",
|
|
218
|
+
line_num=True,
|
|
180
219
|
)
|
|
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
220
|
else:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
221
|
+
if self.problem_line and self.context_line:
|
|
222
|
+
line_start = min(self.problem_line, self.context_line)
|
|
223
|
+
line_end = max(self.problem_line, self.context_line)
|
|
224
|
+
else:
|
|
225
|
+
line_start = self.problem_line or self.context_line
|
|
226
|
+
line_end = line_start
|
|
227
|
+
data_lines = self.data.splitlines()
|
|
228
|
+
selected_lines = data_lines[line_start - 1:line_end]
|
|
229
|
+
code_block_short = _mdit.element.code_block(
|
|
230
|
+
content="\n".join(selected_lines),
|
|
231
|
+
language=self.data_type,
|
|
232
|
+
caption="Data",
|
|
233
|
+
line_num=True,
|
|
234
|
+
line_num_start=line_start,
|
|
235
|
+
emphasize_lines=list({1, len(selected_lines)}),
|
|
236
|
+
)
|
|
237
|
+
content["data_short"] = (code_block_short, ("short", "console"))
|
|
238
|
+
container = _mdit.block_container(**content)
|
|
239
|
+
doc = _mdit.document(heading="Error Details", body=container)
|
|
240
|
+
return {"details": doc}
|
pyserials/exception/update.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
from typing import Any as _Any, Literal as _Literal
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import mdit as _mdit
|
|
7
7
|
|
|
8
8
|
from pyserials.exception import _base
|
|
9
9
|
|
|
@@ -26,18 +26,23 @@ class PySerialsUpdateException(_base.PySerialsException):
|
|
|
26
26
|
path: str,
|
|
27
27
|
data: dict | list | str | int | float | bool,
|
|
28
28
|
data_full: dict | list | str | int | float | bool,
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
problem,
|
|
30
|
+
section: dict | None = None,
|
|
31
31
|
):
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
intro = _mdit.inline_container(
|
|
33
|
+
"Failed to update data at ",
|
|
34
|
+
_mdit.element.code_span(path),
|
|
35
|
+
"."
|
|
36
|
+
)
|
|
37
|
+
report = _mdit.document(
|
|
38
|
+
heading="Data Update Error",
|
|
39
|
+
body={
|
|
40
|
+
"intro": intro,
|
|
41
|
+
"problem": problem,
|
|
42
|
+
},
|
|
43
|
+
section=section,
|
|
40
44
|
)
|
|
45
|
+
super().__init__(report)
|
|
41
46
|
self.path = path
|
|
42
47
|
self.data = data
|
|
43
48
|
self.data_full = data_full
|
|
@@ -66,31 +71,25 @@ class PySerialsUpdateDictFromAddonError(PySerialsUpdateException):
|
|
|
66
71
|
):
|
|
67
72
|
self.type_data = type(data)
|
|
68
73
|
self.type_data_addon = type(data_addon)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
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."
|
|
74
|
+
problem = _mdit.inline_container(
|
|
75
|
+
"There was a duplicate in the addon dictionary: ",
|
|
76
|
+
"the value of type",
|
|
77
|
+
_mdit.element.code_span(self.type_data_addon.__name__),
|
|
78
|
+
" already exists in the source data."
|
|
79
|
+
) if problem_type == "duplicate" else _mdit.inline_container(
|
|
80
|
+
"There was a type mismatch between the source and addon dictionary values: ",
|
|
81
|
+
"the value is of type ",
|
|
82
|
+
_mdit.element.code_span(self.type_data.__name__),
|
|
83
|
+
" in the source data, ",
|
|
84
|
+
"but of type ",
|
|
85
|
+
_mdit.element.code_span(self.type_data_addon.__name__),
|
|
86
|
+
" in the addon data."
|
|
87
87
|
)
|
|
88
88
|
super().__init__(
|
|
89
|
-
description=description_template.format(**kwargs_console),
|
|
90
|
-
description_html=description_template.format(**kwargs_html),
|
|
91
89
|
path=path,
|
|
92
90
|
data=data,
|
|
93
91
|
data_full=data_full,
|
|
92
|
+
problem=problem,
|
|
94
93
|
)
|
|
95
94
|
self.problem_type: _Literal["duplicate", "type_mismatch"] = problem_type
|
|
96
95
|
self.data_addon = data_addon
|
|
@@ -124,16 +123,16 @@ class PySerialsUpdateTemplatedDataError(PySerialsUpdateException):
|
|
|
124
123
|
template_start: str,
|
|
125
124
|
template_end: str,
|
|
126
125
|
):
|
|
127
|
-
|
|
126
|
+
self.path_invalid = path_invalid.replace("'", "")
|
|
127
|
+
self.data_source = data_source
|
|
128
|
+
self.template_start = template_start
|
|
129
|
+
self.template_end = template_end
|
|
130
|
+
parts = description_template.split("{path_invalid}")
|
|
131
|
+
parts.insert(1, _mdit.element.code_span(self.path_invalid))
|
|
128
132
|
super().__init__(
|
|
129
|
-
description=description_template.format(path_invalid=path_invalid_console),
|
|
130
|
-
description_html=description_template.format(path_invalid=path_invalid_html),
|
|
131
133
|
path=path.replace("'", ""),
|
|
132
134
|
data=data,
|
|
133
135
|
data_full=data_full,
|
|
136
|
+
problem=_mdit.inline_container(*parts),
|
|
134
137
|
)
|
|
135
|
-
self.path_invalid = path_invalid
|
|
136
|
-
self.data_source = data_source
|
|
137
|
-
self.template_start = template_start
|
|
138
|
-
self.template_end = template_end
|
|
139
138
|
return
|
pyserials/exception/validate.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""Exceptions raised by `pyserials.validate` module."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
from typing import Any as _Any
|
|
4
|
+
from typing import Any as _Any
|
|
5
5
|
|
|
6
6
|
import jsonschema as _jsonschema
|
|
7
|
-
|
|
8
|
-
from
|
|
7
|
+
import mdit as _mdit
|
|
8
|
+
from pyprotocol import Stringable
|
|
9
|
+
|
|
9
10
|
from pyserials import write as _write
|
|
10
11
|
from pyserials.exception import _base
|
|
11
12
|
|
|
@@ -25,23 +26,27 @@ class PySerialsValidateException(_base.PySerialsException):
|
|
|
25
26
|
|
|
26
27
|
def __init__(
|
|
27
28
|
self,
|
|
28
|
-
|
|
29
|
+
problem: str,
|
|
29
30
|
data: dict | list | str | int | float | bool,
|
|
30
31
|
schema: dict,
|
|
31
32
|
validator: _Any,
|
|
32
33
|
registry: _Any = None,
|
|
33
|
-
|
|
34
|
+
section: dict | None = None,
|
|
34
35
|
):
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
intro = _mdit.inline_container(
|
|
37
|
+
"Failed to validate data against schema using validator ",
|
|
38
|
+
_mdit.element.code_span(validator.__class__.__name__),
|
|
39
|
+
"."
|
|
40
|
+
)
|
|
41
|
+
report = _mdit.document(
|
|
42
|
+
heading="Schema Validation Error",
|
|
43
|
+
body={
|
|
44
|
+
"intro": intro,
|
|
45
|
+
"problem": problem,
|
|
46
|
+
},
|
|
47
|
+
section=section,
|
|
44
48
|
)
|
|
49
|
+
super().__init__(report)
|
|
45
50
|
self.data = data
|
|
46
51
|
self.schema = schema
|
|
47
52
|
self.validator = validator
|
|
@@ -60,7 +65,7 @@ class PySerialsInvalidJsonSchemaError(PySerialsValidateException):
|
|
|
60
65
|
registry: _Any = None,
|
|
61
66
|
):
|
|
62
67
|
super().__init__(
|
|
63
|
-
|
|
68
|
+
problem="The schema is invalid.",
|
|
64
69
|
data=data,
|
|
65
70
|
schema=schema,
|
|
66
71
|
validator=validator,
|
|
@@ -81,10 +86,9 @@ class PySerialsJsonSchemaValidationError(PySerialsValidateException):
|
|
|
81
86
|
registry: _Any = None,
|
|
82
87
|
):
|
|
83
88
|
self.causes = causes
|
|
84
|
-
description, description_html = self._parse_errors()
|
|
85
89
|
super().__init__(
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
problem=self._generate_problem_statement(),
|
|
91
|
+
section={idx: self._generate_error_report(error) for idx, error in enumerate(self.causes)},
|
|
88
92
|
data=data,
|
|
89
93
|
schema=schema,
|
|
90
94
|
validator=validator,
|
|
@@ -92,133 +96,92 @@ class PySerialsJsonSchemaValidationError(PySerialsValidateException):
|
|
|
92
96
|
)
|
|
93
97
|
return
|
|
94
98
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
intro = intro_html = f"Found {errors} in the data at "
|
|
108
|
-
reports = []
|
|
109
|
-
errors_loc = []
|
|
110
|
-
errors_loc_html = []
|
|
111
|
-
for idx, error in enumerate(self.causes):
|
|
112
|
-
error_loc, error_loc_html = _base.format_code(error.json_path)
|
|
113
|
-
errors_loc.append(error_loc)
|
|
114
|
-
errors_loc_html.append(error_loc_html)
|
|
115
|
-
section_console = self._parse_error(error, section=[idx + 1])
|
|
116
|
-
reports.extend(section_console)
|
|
117
|
-
reports_str = "\n".join(reports)
|
|
118
|
-
if len(errors_loc) == 1:
|
|
119
|
-
errors_loc_str = errors_loc[0]
|
|
120
|
-
errors_loc_html_str = errors_loc_html[0]
|
|
121
|
-
elif len(errors_loc) == 2:
|
|
122
|
-
errors_loc_str = " and ".join(errors_loc)
|
|
123
|
-
errors_loc_html_str = " and ".join(errors_loc_html)
|
|
124
|
-
else:
|
|
125
|
-
errors_loc_str = ", ".join(errors_loc[:-1]) + " and " + errors_loc[-1]
|
|
126
|
-
errors_loc_html_str = ", ".join(errors_loc_html[:-1]) + " and " + errors_loc_html[-1]
|
|
127
|
-
intro += f"{errors_loc_str}:\n\n{reports_str}"
|
|
128
|
-
intro_html += f"{errors_loc_html_str}."
|
|
129
|
-
return intro, intro_html
|
|
99
|
+
def _generate_problem_statement(self):
|
|
100
|
+
error_paths = [_mdit.element.code_span(error.json_path) for error in self.causes]
|
|
101
|
+
error_paths_str = self._join_list(error_paths)
|
|
102
|
+
count_errors = len(error_paths)
|
|
103
|
+
problem = _mdit.inline_container(
|
|
104
|
+
"Found ",
|
|
105
|
+
"an error " if count_errors == 1 else f"{count_errors} errors ",
|
|
106
|
+
"in the data at ",
|
|
107
|
+
error_paths_str,
|
|
108
|
+
"."
|
|
109
|
+
)
|
|
110
|
+
return problem
|
|
130
111
|
|
|
131
|
-
def
|
|
112
|
+
def _generate_error_report(
|
|
132
113
|
self,
|
|
133
114
|
error: _jsonschema.exceptions.ValidationError,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
f"- {_base.ansi_bold("Validator Path")}: {schema_path}",
|
|
115
|
+
) -> _mdit.Document:
|
|
116
|
+
problem = self._parse_error_message(error)
|
|
117
|
+
short_ver_fieldlist_items = [
|
|
118
|
+
_mdit.element.field_list_item("Problem", problem),
|
|
119
|
+
_mdit.element.field_list_item(
|
|
120
|
+
"Validator Path", _mdit.element.code_span(self._create_path(error.absolute_schema_path))
|
|
121
|
+
),
|
|
142
122
|
]
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
self._create_schema_details(error, mode=mode, md=md),
|
|
164
|
-
self._create_instance_details(error, mode=mode, md=md),
|
|
123
|
+
full_ver_items = [
|
|
124
|
+
_mdit.inline_container(problem),
|
|
125
|
+
self._make_yaml_code_admo(
|
|
126
|
+
admo_type="error",
|
|
127
|
+
title="Validator",
|
|
128
|
+
title_details=str(error.validator),
|
|
129
|
+
content=error.validator_value,
|
|
130
|
+
),
|
|
131
|
+
self._make_yaml_code_admo(
|
|
132
|
+
admo_type="error",
|
|
133
|
+
title="Schema",
|
|
134
|
+
title_details=self._create_path(error.absolute_schema_path),
|
|
135
|
+
content=error.schema,
|
|
136
|
+
),
|
|
137
|
+
self._make_yaml_code_admo(
|
|
138
|
+
admo_type="error",
|
|
139
|
+
title="Instance",
|
|
140
|
+
title_details=self._create_path(error.absolute_path),
|
|
141
|
+
content=error.instance,
|
|
142
|
+
),
|
|
165
143
|
]
|
|
144
|
+
section = {}
|
|
166
145
|
if error.context:
|
|
167
|
-
|
|
146
|
+
context_paths = []
|
|
168
147
|
for idx, sub_error in enumerate(sorted(error.context, key=lambda x: len(x.context))):
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
148
|
+
section[idx] = self._generate_error_report(sub_error)
|
|
149
|
+
context_paths.append(_mdit.element.code_span(sub_error.json_path))
|
|
150
|
+
context_paths_joined = self._join_list(context_paths)
|
|
151
|
+
short_ver_fieldlist_items.insert(
|
|
152
|
+
1,
|
|
153
|
+
_mdit.element.field_list_item(
|
|
154
|
+
f"Context Path{'s' if len(context_paths) > 1 else ''}",
|
|
155
|
+
context_paths_joined
|
|
174
156
|
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
content=error.validator_value,
|
|
188
|
-
summary=summary,
|
|
189
|
-
md=md,
|
|
157
|
+
)
|
|
158
|
+
err = "an error" if len(context_paths) == 1 else f"{len(context_paths)} errors"
|
|
159
|
+
full_ver_items[0].append(
|
|
160
|
+
f" This was caused by {err} at {context_paths_joined}.",
|
|
161
|
+
)
|
|
162
|
+
doc = _mdit.document(
|
|
163
|
+
heading=_mdit.element.code_span(error.json_path),
|
|
164
|
+
body={
|
|
165
|
+
"short": (_mdit.element.field_list(short_ver_fieldlist_items), ("short", "console")),
|
|
166
|
+
"full": (_mdit.element.field_list(full_ver_items), "full"),
|
|
167
|
+
},
|
|
168
|
+
section=section,
|
|
190
169
|
)
|
|
170
|
+
return doc
|
|
191
171
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
content=
|
|
196
|
-
|
|
197
|
-
md=md,
|
|
172
|
+
@staticmethod
|
|
173
|
+
def _make_yaml_code_admo(admo_type: str, title: str, title_details: str, content: dict) -> _mdit.element.Admonition:
|
|
174
|
+
code_block = _mdit.element.code_block(
|
|
175
|
+
content=_write.to_yaml_string(content, end_of_file_newline=False),
|
|
176
|
+
language="yaml",
|
|
198
177
|
)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
summary=summary,
|
|
205
|
-
md=md,
|
|
178
|
+
admo = _mdit.element.admonition(
|
|
179
|
+
type=admo_type,
|
|
180
|
+
title=_mdit.inline_container(f"**{title}**: ", _mdit.element.code_span(title_details)),
|
|
181
|
+
content=code_block,
|
|
182
|
+
dropdown=True,
|
|
206
183
|
)
|
|
207
|
-
|
|
208
|
-
@staticmethod
|
|
209
|
-
def _make_details(content: dict, summary: str, md: bool) -> _html.Details:
|
|
210
|
-
yaml = _write.to_yaml_string(content, end_of_file_newline=False)
|
|
211
|
-
details_summary = _html.summary(summary)
|
|
212
|
-
details_content = _html.pre(
|
|
213
|
-
_html.code(yaml, {"class": "language-yaml"})
|
|
214
|
-
) if not md else _md.code_fence(yaml, info="yaml")
|
|
215
|
-
return _html.details([details_summary, details_content])
|
|
216
|
-
|
|
217
|
-
def _create_title(self, error: _jsonschema.exceptions.ValidationError) -> tuple[str, str]:
|
|
218
|
-
problem = self._parse_error_message(error)
|
|
219
|
-
title_shell = f"'{error.json_path}': {problem}"
|
|
220
|
-
title_html = f"{_html.code(error.json_path)}: {problem}"
|
|
221
|
-
return title_shell, title_html
|
|
184
|
+
return admo
|
|
222
185
|
|
|
223
186
|
@staticmethod
|
|
224
187
|
def _parse_error_message(error: _jsonschema.exceptions.ValidationError) -> str:
|
|
@@ -228,16 +191,26 @@ class PySerialsJsonSchemaValidationError(PySerialsValidateException):
|
|
|
228
191
|
problem = f"Data {msg}"
|
|
229
192
|
else:
|
|
230
193
|
problem = error.message
|
|
231
|
-
return problem
|
|
232
|
-
|
|
233
|
-
@staticmethod
|
|
234
|
-
def _indent(text: list, indent: int = 2) -> list:
|
|
235
|
-
return [f"{' ' * indent}{line}" for line in text]
|
|
194
|
+
return f"{problem.removeprefix(".")}."
|
|
236
195
|
|
|
237
196
|
@staticmethod
|
|
238
197
|
def _create_path(path):
|
|
239
198
|
return "$." + ".".join(str(path_component) for path_component in path)
|
|
240
199
|
|
|
241
200
|
@staticmethod
|
|
242
|
-
def
|
|
243
|
-
|
|
201
|
+
def _join_list(
|
|
202
|
+
items: list,
|
|
203
|
+
sep: Stringable = ", ",
|
|
204
|
+
sep_last: Stringable = ", and ",
|
|
205
|
+
sep_pair: Stringable = " and ",
|
|
206
|
+
) -> Stringable:
|
|
207
|
+
if len(items) == 1:
|
|
208
|
+
return items[0]
|
|
209
|
+
elif len(items) == 2:
|
|
210
|
+
return _mdit.inline_container(items[0], sep_pair, items[1])
|
|
211
|
+
container = []
|
|
212
|
+
for item in items[:-1]:
|
|
213
|
+
container.extend([item, sep])
|
|
214
|
+
container.pop()
|
|
215
|
+
container.extend([sep_last, items[-1]])
|
|
216
|
+
return _mdit.inline_container(*container)
|
|
File without changes
|