amati 0.1.0__py3-none-any.whl → 0.2__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.
- amati/__init__.py +3 -2
- amati/_error_handler.py +48 -0
- amati/amati.py +156 -26
- amati/exceptions.py +2 -4
- amati/fields/email.py +3 -7
- amati/fields/http_status_codes.py +4 -5
- amati/fields/iso9110.py +4 -5
- amati/fields/media.py +3 -7
- amati/fields/oas.py +6 -12
- amati/fields/spdx_licences.py +4 -7
- amati/fields/uri.py +3 -11
- amati/logging.py +8 -8
- amati/model_validators.py +42 -33
- amati/validators/generic.py +18 -13
- amati/validators/oas304.py +93 -130
- amati/validators/oas311.py +58 -156
- {amati-0.1.0.dist-info → amati-0.2.dist-info}/METADATA +72 -10
- amati-0.2.dist-info/RECORD +37 -0
- amati/references.py +0 -33
- amati-0.1.0.dist-info/RECORD +0 -37
- {amati-0.1.0.dist-info → amati-0.2.dist-info}/WHEEL +0 -0
- {amati-0.1.0.dist-info → amati-0.2.dist-info}/entry_points.txt +0 -0
- {amati-0.1.0.dist-info → amati-0.2.dist-info}/licenses/LICENSE +0 -0
amati/__init__.py
CHANGED
@@ -3,7 +3,9 @@ Amati is a specification validator, built to put a specification into
|
|
3
3
|
a single datatype and validate on instantiation.
|
4
4
|
"""
|
5
5
|
|
6
|
-
|
6
|
+
import importlib.metadata
|
7
|
+
|
8
|
+
__version__ = importlib.metadata.version("amati")
|
7
9
|
|
8
10
|
# Imports are here for convenience, they're not going to be used here
|
9
11
|
# pylint: disable=unused-import
|
@@ -11,4 +13,3 @@ __version__ = "0.1.0"
|
|
11
13
|
|
12
14
|
from amati.amati import dispatch, run
|
13
15
|
from amati.exceptions import AmatiValueError
|
14
|
-
from amati.references import AmatiReferenceException, Reference, References
|
amati/_error_handler.py
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
"""
|
2
|
+
Handles Pydantic errors and amati logs to provide a consistent view to the user.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import json
|
6
|
+
from typing import cast
|
7
|
+
|
8
|
+
from amati.logging import Log
|
9
|
+
|
10
|
+
type JSONPrimitive = str | int | float | bool | None
|
11
|
+
type JSONArray = list["JSONValue"]
|
12
|
+
type JSONObject = dict[str, "JSONValue"]
|
13
|
+
type JSONValue = JSONPrimitive | JSONArray | JSONObject
|
14
|
+
|
15
|
+
|
16
|
+
def remove_duplicates(data: list[JSONObject]) -> list[JSONObject]:
|
17
|
+
"""
|
18
|
+
Remove duplicates by converting each dict to a JSON string for comparison.
|
19
|
+
"""
|
20
|
+
seen: set[str] = set()
|
21
|
+
unique_data: list[JSONObject] = []
|
22
|
+
|
23
|
+
for item in data:
|
24
|
+
# Convert to JSON string with sorted keys for consistent hashing
|
25
|
+
item_json = json.dumps(item, sort_keys=True, separators=(",", ":"))
|
26
|
+
if item_json not in seen:
|
27
|
+
seen.add(item_json)
|
28
|
+
unique_data.append(item)
|
29
|
+
|
30
|
+
return unique_data
|
31
|
+
|
32
|
+
|
33
|
+
def handle_errors(errors: list[JSONObject] | None, logs: list[Log]) -> list[JSONObject]:
|
34
|
+
"""
|
35
|
+
Makes errors and logs consistent for user consumption.
|
36
|
+
"""
|
37
|
+
|
38
|
+
result: list[JSONObject] = []
|
39
|
+
|
40
|
+
if errors:
|
41
|
+
result.extend(errors)
|
42
|
+
|
43
|
+
if logs:
|
44
|
+
result.extend(cast(list[JSONObject], logs))
|
45
|
+
|
46
|
+
result = remove_duplicates(result)
|
47
|
+
|
48
|
+
return result
|
amati/amati.py
CHANGED
@@ -7,15 +7,16 @@ import json
|
|
7
7
|
import sys
|
8
8
|
from pathlib import Path
|
9
9
|
|
10
|
-
import
|
10
|
+
from jinja2 import Environment, FileSystemLoader
|
11
11
|
from pydantic import BaseModel, ValidationError
|
12
|
-
from pydantic_core import ErrorDetails
|
13
12
|
|
14
13
|
# pylint: disable=wrong-import-position
|
15
14
|
|
16
15
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
16
|
+
from amati._error_handler import handle_errors
|
17
17
|
from amati._resolve_forward_references import resolve_forward_references
|
18
18
|
from amati.file_handler import load_file
|
19
|
+
from amati.logging import Log, LogMixin
|
19
20
|
|
20
21
|
type JSONPrimitive = str | int | float | bool | None
|
21
22
|
type JSONArray = list["JSONValue"]
|
@@ -23,7 +24,7 @@ type JSONObject = dict[str, "JSONValue"]
|
|
23
24
|
type JSONValue = JSONPrimitive | JSONArray | JSONObject
|
24
25
|
|
25
26
|
|
26
|
-
def dispatch(data: JSONObject) -> tuple[BaseModel | None, list[
|
27
|
+
def dispatch(data: JSONObject) -> tuple[BaseModel | None, list[JSONObject] | None]:
|
27
28
|
"""
|
28
29
|
Returns the correct model for the passed spec
|
29
30
|
|
@@ -37,10 +38,10 @@ def dispatch(data: JSONObject) -> tuple[BaseModel | None, list[ErrorDetails] | N
|
|
37
38
|
version: JSONValue = data.get("openapi")
|
38
39
|
|
39
40
|
if not isinstance(version, str):
|
40
|
-
raise
|
41
|
+
raise TypeError("A OpenAPI specification version must be a string.")
|
41
42
|
|
42
43
|
if not version:
|
43
|
-
raise
|
44
|
+
raise TypeError("An OpenAPI Specfication must contain a version.")
|
44
45
|
|
45
46
|
version_map: dict[str, str] = {
|
46
47
|
"3.1.1": "311",
|
@@ -59,7 +60,7 @@ def dispatch(data: JSONObject) -> tuple[BaseModel | None, list[ErrorDetails] | N
|
|
59
60
|
try:
|
60
61
|
model = module.OpenAPIObject(**data)
|
61
62
|
except ValidationError as e:
|
62
|
-
return None, e.
|
63
|
+
return None, json.loads(e.json())
|
63
64
|
|
64
65
|
return model, None
|
65
66
|
|
@@ -86,27 +87,113 @@ def check(original: JSONObject, validated: BaseModel) -> bool:
|
|
86
87
|
return original_ == new_
|
87
88
|
|
88
89
|
|
89
|
-
def run(
|
90
|
+
def run(
|
91
|
+
file_path: str | Path,
|
92
|
+
consistency_check: bool = False,
|
93
|
+
local: bool = False,
|
94
|
+
html_report: bool = False,
|
95
|
+
):
|
90
96
|
"""
|
91
|
-
Runs the full amati process
|
97
|
+
Runs the full amati process on a specific specification file.
|
98
|
+
|
99
|
+
* Parses the YAML or JSON specification, gunzipping if necessary.
|
100
|
+
* Validates the specification.
|
101
|
+
* Runs a consistency check on the ouput of the validation to verify
|
102
|
+
that the output is identical to the input.
|
103
|
+
* Stores any errors found during validation.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
file_path: The specification to be validated
|
107
|
+
consistency_check: Whether or not to verify the output against the input
|
92
108
|
"""
|
93
109
|
|
94
|
-
|
110
|
+
spec = Path(file_path)
|
111
|
+
|
112
|
+
data = load_file(spec)
|
113
|
+
|
114
|
+
logs: list[Log] = []
|
115
|
+
|
116
|
+
with LogMixin.context():
|
117
|
+
result, errors = dispatch(data)
|
118
|
+
logs.extend(LogMixin.logs)
|
119
|
+
|
120
|
+
if errors or logs:
|
121
|
+
|
122
|
+
handled_errors: list[JSONObject] = handle_errors(errors, logs)
|
123
|
+
|
124
|
+
file_name = Path(Path(file_path).parts[-1])
|
125
|
+
error_file = file_name.with_suffix(file_name.suffix + ".errors")
|
126
|
+
error_path = spec.parent
|
127
|
+
|
128
|
+
if local:
|
129
|
+
error_path = Path(".amati")
|
95
130
|
|
96
|
-
|
131
|
+
if not error_path.exists():
|
132
|
+
error_path.mkdir()
|
133
|
+
|
134
|
+
with open(
|
135
|
+
error_path / error_file.with_suffix(error_file.suffix + ".json"),
|
136
|
+
"w",
|
137
|
+
encoding="utf-8",
|
138
|
+
) as f:
|
139
|
+
f.write(json.dumps(handled_errors))
|
140
|
+
|
141
|
+
if html_report:
|
142
|
+
env = Environment(
|
143
|
+
loader=FileSystemLoader(".")
|
144
|
+
) # Assumes template is in the same directory
|
145
|
+
template = env.get_template("TEMPLATE.html")
|
146
|
+
|
147
|
+
# Render the template with your data
|
148
|
+
html_output = template.render(errors=handled_errors)
|
149
|
+
|
150
|
+
# Save the output to a file
|
151
|
+
with open(
|
152
|
+
error_path / error_file.with_suffix(error_file.suffix + ".html"),
|
153
|
+
"w",
|
154
|
+
encoding="utf-8",
|
155
|
+
) as f:
|
156
|
+
f.write(html_output)
|
97
157
|
|
98
158
|
if result and consistency_check:
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
159
|
+
return check(data, result)
|
160
|
+
|
161
|
+
|
162
|
+
def discover(discover_dir: str = ".") -> list[Path]:
|
163
|
+
"""
|
164
|
+
Finds OpenAPI Specification files to validate
|
165
|
+
|
166
|
+
Args:
|
167
|
+
discover_dir: The directory to search through.
|
168
|
+
Returns:
|
169
|
+
A list of paths to validate.
|
170
|
+
"""
|
171
|
+
|
172
|
+
specs: list[Path] = []
|
173
|
+
|
174
|
+
if Path("openapi.json").exists():
|
175
|
+
specs.append(Path("openapi.json"))
|
176
|
+
|
177
|
+
if Path("openapi.yaml").exists():
|
178
|
+
specs.append(Path("openapi.yaml"))
|
103
179
|
|
104
|
-
if
|
105
|
-
|
106
|
-
Path(".amati").mkdir()
|
180
|
+
if specs:
|
181
|
+
return specs
|
107
182
|
|
108
|
-
|
109
|
-
|
183
|
+
if discover_dir == ".":
|
184
|
+
raise FileNotFoundError(
|
185
|
+
"openapi.json or openapi.yaml can't be found, use --discover or --spec."
|
186
|
+
)
|
187
|
+
|
188
|
+
specs = specs + list(Path(discover_dir).glob("**/openapi.json"))
|
189
|
+
specs = specs + list(Path(discover_dir).glob("**/openapi.yaml"))
|
190
|
+
|
191
|
+
if not specs:
|
192
|
+
raise FileNotFoundError(
|
193
|
+
"openapi.json or openapi.yaml can't be found, use --spec."
|
194
|
+
)
|
195
|
+
|
196
|
+
return specs
|
110
197
|
|
111
198
|
|
112
199
|
if __name__ == "__main__":
|
@@ -115,11 +202,23 @@ if __name__ == "__main__":
|
|
115
202
|
|
116
203
|
parser = argparse.ArgumentParser(
|
117
204
|
prog="amati",
|
118
|
-
description="
|
205
|
+
description="""
|
206
|
+
Tests whether a OpenAPI specification is valid. Will look an openapi.json
|
207
|
+
or openapi.yaml file in the directory that amati is called from. If
|
208
|
+
--discover is set will search the directory tree. If the specification
|
209
|
+
does not follow the naming recommendation the --spec switch should be
|
210
|
+
used.
|
211
|
+
|
212
|
+
Creates a file <filename>.errors.json alongside the original specification
|
213
|
+
containing a JSON representation of all the errors.
|
214
|
+
""",
|
119
215
|
)
|
120
216
|
|
121
217
|
parser.add_argument(
|
122
|
-
"-s",
|
218
|
+
"-s",
|
219
|
+
"--spec",
|
220
|
+
required=False,
|
221
|
+
help="The specification to be parsed",
|
123
222
|
)
|
124
223
|
|
125
224
|
parser.add_argument(
|
@@ -127,17 +226,48 @@ if __name__ == "__main__":
|
|
127
226
|
"--consistency-check",
|
128
227
|
required=False,
|
129
228
|
action="store_true",
|
130
|
-
help="Runs a consistency check between the input specification and
|
229
|
+
help="Runs a consistency check between the input specification and the"
|
230
|
+
" parsed specification",
|
231
|
+
)
|
232
|
+
|
233
|
+
parser.add_argument(
|
234
|
+
"-d",
|
235
|
+
"--discover",
|
236
|
+
required=False,
|
237
|
+
default=".",
|
238
|
+
help="Searches the specified directory tree for openapi.yaml or openapi.json.",
|
239
|
+
)
|
240
|
+
|
241
|
+
parser.add_argument(
|
242
|
+
"-l",
|
243
|
+
"--local",
|
244
|
+
required=False,
|
245
|
+
action="store_true",
|
246
|
+
help="Store errors local to the caller in a file called <file-name>.errors.json"
|
247
|
+
"; a .amati/ directory will be created.",
|
131
248
|
)
|
132
249
|
|
133
250
|
parser.add_argument(
|
134
|
-
"-
|
135
|
-
"--
|
251
|
+
"-hr",
|
252
|
+
"--html-report",
|
136
253
|
required=False,
|
137
254
|
action="store_true",
|
138
|
-
help="
|
255
|
+
help="Creates an HTML report of the errors, called <file-name>.errors.html,"
|
256
|
+
" alongside the original file or in a .amati/ directory if the --local switch"
|
257
|
+
" is used",
|
139
258
|
)
|
140
259
|
|
141
260
|
args = parser.parse_args()
|
142
261
|
|
143
|
-
|
262
|
+
if args.spec:
|
263
|
+
specifications: list[Path] = [Path(args.spec)]
|
264
|
+
else:
|
265
|
+
specifications = discover(args.discover)
|
266
|
+
|
267
|
+
for specification in specifications:
|
268
|
+
if successful_check := run(
|
269
|
+
specification, args.consistency_check, args.local, args.html_report
|
270
|
+
):
|
271
|
+
print("Consistency check successful for {specification}")
|
272
|
+
else:
|
273
|
+
print("Consistency check failed for {specification}")
|
amati/exceptions.py
CHANGED
@@ -4,8 +4,6 @@ Exceptions, declared here to not put in __init__
|
|
4
4
|
|
5
5
|
from typing import Optional
|
6
6
|
|
7
|
-
from amati.references import References
|
8
|
-
|
9
7
|
|
10
8
|
class AmatiValueError(ValueError):
|
11
9
|
"""
|
@@ -21,6 +19,6 @@ class AmatiValueError(ValueError):
|
|
21
19
|
ValueError
|
22
20
|
"""
|
23
21
|
|
24
|
-
def __init__(self, message: str,
|
22
|
+
def __init__(self, message: str, reference_uri: Optional[str] = None):
|
25
23
|
self.message = message
|
26
|
-
self.
|
24
|
+
self.reference_uri = reference_uri
|
amati/fields/email.py
CHANGED
@@ -5,14 +5,10 @@ Validates an email according to the RFC5322 ABNF grammar - §3:
|
|
5
5
|
from abnf import ParseError
|
6
6
|
from abnf.grammars import rfc5322
|
7
7
|
|
8
|
-
from amati import AmatiValueError
|
8
|
+
from amati import AmatiValueError
|
9
9
|
from amati.fields import Str as _Str
|
10
10
|
|
11
|
-
|
12
|
-
title="Internet Message Format",
|
13
|
-
url="https://www.rfc-editor.org/rfc/rfc5322#section-3",
|
14
|
-
section="Syntax",
|
15
|
-
)
|
11
|
+
reference_uri = "https://www.rfc-editor.org/rfc/rfc5322#section-3"
|
16
12
|
|
17
13
|
|
18
14
|
class Email(_Str):
|
@@ -23,5 +19,5 @@ class Email(_Str):
|
|
23
19
|
rfc5322.Rule("address").parse_all(value)
|
24
20
|
except ParseError as e:
|
25
21
|
raise AmatiValueError(
|
26
|
-
|
22
|
+
f"{value} is not a valid email address", reference_uri
|
27
23
|
) from e
|
@@ -11,12 +11,11 @@ import pathlib
|
|
11
11
|
import re
|
12
12
|
from typing import Optional, Self
|
13
13
|
|
14
|
-
from amati import AmatiValueError
|
14
|
+
from amati import AmatiValueError
|
15
15
|
from amati.fields import Str as _Str
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
url="https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml",
|
17
|
+
reference_uri = (
|
18
|
+
"https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml"
|
20
19
|
)
|
21
20
|
|
22
21
|
DATA_DIRECTORY = pathlib.Path(__file__).parent.parent.resolve() / "data"
|
@@ -88,7 +87,7 @@ class HTTPStatusCode(_Str):
|
|
88
87
|
self.is_range = True
|
89
88
|
else:
|
90
89
|
raise AmatiValueError(
|
91
|
-
f"{value} is not a valid HTTP Status Code",
|
90
|
+
f"{value} is not a valid HTTP Status Code", reference_uri
|
92
91
|
)
|
93
92
|
|
94
93
|
if self.description != "Unassigned":
|
amati/fields/iso9110.py
CHANGED
@@ -9,12 +9,11 @@ and a class for scheme validation.
|
|
9
9
|
import json
|
10
10
|
import pathlib
|
11
11
|
|
12
|
-
from amati import AmatiValueError
|
12
|
+
from amati import AmatiValueError
|
13
13
|
from amati.fields import Str as _Str
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
url="https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml",
|
15
|
+
reference_uri = (
|
16
|
+
"https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml"
|
18
17
|
)
|
19
18
|
|
20
19
|
|
@@ -57,5 +56,5 @@ class HTTPAuthenticationScheme(_Str):
|
|
57
56
|
if value.lower() not in HTTP_AUTHENTICATION_SCHEMES:
|
58
57
|
raise AmatiValueError(
|
59
58
|
f"{value} is not a valid HTTP authentication schema.",
|
60
|
-
|
59
|
+
reference_uri,
|
61
60
|
)
|
amati/fields/media.py
CHANGED
@@ -9,14 +9,10 @@ from typing import Optional
|
|
9
9
|
from abnf import ParseError
|
10
10
|
from abnf.grammars import rfc7231
|
11
11
|
|
12
|
-
from amati import AmatiValueError
|
12
|
+
from amati import AmatiValueError
|
13
13
|
from amati.fields import Str as _Str
|
14
14
|
|
15
|
-
|
16
|
-
title="Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content",
|
17
|
-
url="https://datatracker.ietf.org/doc/html/rfc7231#appendix-D",
|
18
|
-
section="Appendix D",
|
19
|
-
)
|
15
|
+
reference_uri = "https://datatracker.ietf.org/doc/html/rfc7231#appendix-D"
|
20
16
|
|
21
17
|
DATA_DIRECTORY = pathlib.Path(__file__).parent.parent.resolve() / "data"
|
22
18
|
|
@@ -69,7 +65,7 @@ class MediaType(_Str):
|
|
69
65
|
|
70
66
|
except ParseError as e:
|
71
67
|
raise AmatiValueError(
|
72
|
-
"Invalid media type or media type range",
|
68
|
+
"Invalid media type or media type range", reference_uri
|
73
69
|
) from e
|
74
70
|
|
75
71
|
if self.type in MEDIA_TYPES:
|
amati/fields/oas.py
CHANGED
@@ -6,7 +6,7 @@ from typing import ClassVar
|
|
6
6
|
|
7
7
|
from abnf import ParseError
|
8
8
|
|
9
|
-
from amati import AmatiValueError
|
9
|
+
from amati import AmatiValueError
|
10
10
|
from amati.fields import Str as _Str
|
11
11
|
from amati.grammars import oas
|
12
12
|
|
@@ -31,10 +31,8 @@ class RuntimeExpression(_Str):
|
|
31
31
|
It is validated against the ABNF grammar in the OpenAPI spec.
|
32
32
|
"""
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
section="Runtime Expressions",
|
37
|
-
url="https://spec.openapis.org/oas/v3.1.1.html#runtime-expressions",
|
34
|
+
_reference_uri: ClassVar[str] = (
|
35
|
+
"https://spec.openapis.org/oas/v3.1.1.html#runtime-expressions"
|
38
36
|
)
|
39
37
|
|
40
38
|
def __init__(self, value: str):
|
@@ -50,7 +48,7 @@ class RuntimeExpression(_Str):
|
|
50
48
|
except ParseError as e:
|
51
49
|
raise AmatiValueError(
|
52
50
|
f"{value} is not a valid runtime expression",
|
53
|
-
|
51
|
+
self._reference_uri,
|
54
52
|
) from e
|
55
53
|
|
56
54
|
|
@@ -59,11 +57,7 @@ class OpenAPI(_Str):
|
|
59
57
|
Represents an OpenAPI version string.s
|
60
58
|
"""
|
61
59
|
|
62
|
-
|
63
|
-
title="OpenAPI Initiative Publications",
|
64
|
-
url="https://spec.openapis.org/#openapi-specification",
|
65
|
-
section="OpenAPI Specification ",
|
66
|
-
)
|
60
|
+
_reference_uri: ClassVar[str] = "https://spec.openapis.org/#openapi-specification"
|
67
61
|
|
68
62
|
def __init__(self, value: str):
|
69
63
|
"""
|
@@ -75,5 +69,5 @@ class OpenAPI(_Str):
|
|
75
69
|
if value not in OPENAPI_VERSIONS:
|
76
70
|
raise AmatiValueError(
|
77
71
|
f"{value} is not a valid OpenAPI version",
|
78
|
-
|
72
|
+
self._reference_uri,
|
79
73
|
)
|
amati/fields/spdx_licences.py
CHANGED
@@ -6,14 +6,11 @@ Exchange (SPDX) licence list.
|
|
6
6
|
import json
|
7
7
|
import pathlib
|
8
8
|
|
9
|
-
from amati import AmatiValueError
|
9
|
+
from amati import AmatiValueError
|
10
10
|
from amati.fields import Str as _Str
|
11
11
|
from amati.fields.uri import URI
|
12
12
|
|
13
|
-
|
14
|
-
title="SPDX License List",
|
15
|
-
url="https://spdx.org/licenses/",
|
16
|
-
)
|
13
|
+
reference_uri = "https://spdx.org/licenses/"
|
17
14
|
|
18
15
|
|
19
16
|
DATA_DIRECTORY = pathlib.Path(__file__).parent.parent.resolve() / "data"
|
@@ -55,7 +52,7 @@ class SPDXIdentifier(_Str):
|
|
55
52
|
|
56
53
|
if value not in VALID_LICENCES:
|
57
54
|
raise AmatiValueError(
|
58
|
-
f"{value} is not a valid SPDX licence identifier",
|
55
|
+
f"{value} is not a valid SPDX licence identifier", reference_uri
|
59
56
|
)
|
60
57
|
|
61
58
|
|
@@ -88,5 +85,5 @@ class SPDXURL(URI): # pylint: disable=invalid-name
|
|
88
85
|
|
89
86
|
if value not in VALID_URLS:
|
90
87
|
raise AmatiValueError(
|
91
|
-
f"{value} is not associated with any identifier.",
|
88
|
+
f"{value} is not associated with any identifier.", reference_uri
|
92
89
|
)
|
amati/fields/uri.py
CHANGED
@@ -11,7 +11,7 @@ import idna
|
|
11
11
|
from abnf import Node, ParseError, Rule
|
12
12
|
from abnf.grammars import rfc3986, rfc3987
|
13
13
|
|
14
|
-
from amati import AmatiValueError
|
14
|
+
from amati import AmatiValueError
|
15
15
|
from amati.fields import Str as _Str
|
16
16
|
from amati.grammars import rfc6901
|
17
17
|
|
@@ -61,11 +61,7 @@ class Scheme(_Str):
|
|
61
61
|
except ParseError as e:
|
62
62
|
raise AmatiValueError(
|
63
63
|
f"{value} is not a valid URI scheme",
|
64
|
-
|
65
|
-
title="Uniform Resource Identifier (URI): Generic Syntax",
|
66
|
-
section="3.1 - Scheme",
|
67
|
-
url="https://www.rfc-editor.org/rfc/rfc3986#section-3.1",
|
68
|
-
),
|
64
|
+
"https://www.rfc-editor.org/rfc/rfc3986#section-3.1",
|
69
65
|
) from e
|
70
66
|
|
71
67
|
# Look up the scheme in the IANA registry to get status info
|
@@ -211,11 +207,7 @@ class URI(_Str):
|
|
211
207
|
except ParseError as e:
|
212
208
|
raise AmatiValueError(
|
213
209
|
f"{value} is not a valid JSON pointer",
|
214
|
-
|
215
|
-
title="JavaScript Object Notation (JSON) Pointer",
|
216
|
-
section="6 - URI Fragment Identifier Representation",
|
217
|
-
url="https://www.rfc-editor.org/rfc/rfc6901#section-6",
|
218
|
-
),
|
210
|
+
"https://www.rfc-editor.org/rfc/rfc6901#section-6",
|
219
211
|
) from e
|
220
212
|
|
221
213
|
# Attempt parsing with multiple RFC specifications in order of preference.
|
amati/logging.py
CHANGED
@@ -4,21 +4,21 @@ Logging utilities for Amati.
|
|
4
4
|
|
5
5
|
from contextlib import contextmanager
|
6
6
|
from dataclasses import dataclass
|
7
|
-
from typing import ClassVar, Generator,
|
8
|
-
|
9
|
-
from amati.references import References
|
7
|
+
from typing import Any, ClassVar, Generator, NotRequired, TypedDict
|
10
8
|
|
11
9
|
type LogType = Exception | Warning
|
12
10
|
|
13
11
|
|
14
12
|
@dataclass
|
15
|
-
class Log:
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
class Log(TypedDict):
|
14
|
+
type: str
|
15
|
+
loc: NotRequired[tuple[int | str, ...]]
|
16
|
+
msg: str
|
17
|
+
input: NotRequired[Any]
|
18
|
+
url: NotRequired[str]
|
19
19
|
|
20
20
|
|
21
|
-
class LogMixin
|
21
|
+
class LogMixin:
|
22
22
|
"""
|
23
23
|
A mixin class that provides logging functionality.
|
24
24
|
|