amati 0.2.1__py3-none-any.whl → 0.2.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/amati.py +37 -14
- amati/logging.py +1 -1
- amati/model_validators.py +32 -32
- amati/validators/generic.py +3 -3
- amati/validators/oas304.py +4 -4
- amati/validators/oas311.py +5 -5
- {amati-0.2.1.dist-info → amati-0.2.2.dist-info}/METADATA +1 -1
- {amati-0.2.1.dist-info → amati-0.2.2.dist-info}/RECORD +11 -11
- {amati-0.2.1.dist-info → amati-0.2.2.dist-info}/WHEEL +0 -0
- {amati-0.2.1.dist-info → amati-0.2.2.dist-info}/entry_points.txt +0 -0
- {amati-0.2.1.dist-info → amati-0.2.2.dist-info}/licenses/LICENSE +0 -0
amati/amati.py
CHANGED
@@ -16,7 +16,7 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
16
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,
|
19
|
+
from amati.logging import Log, Logger
|
20
20
|
|
21
21
|
type JSONPrimitive = str | int | float | bool | None
|
22
22
|
type JSONArray = list["JSONValue"]
|
@@ -113,9 +113,9 @@ def run(
|
|
113
113
|
|
114
114
|
logs: list[Log] = []
|
115
115
|
|
116
|
-
with
|
116
|
+
with Logger.context():
|
117
117
|
result, errors = dispatch(data)
|
118
|
-
logs.extend(
|
118
|
+
logs.extend(Logger.logs)
|
119
119
|
|
120
120
|
if errors or logs:
|
121
121
|
|
@@ -158,19 +158,38 @@ def run(
|
|
158
158
|
if result and consistency_check:
|
159
159
|
return check(data, result)
|
160
160
|
|
161
|
+
return True
|
161
162
|
|
162
|
-
|
163
|
+
|
164
|
+
def discover(spec: str, discover_dir: str = ".") -> list[Path]:
|
163
165
|
"""
|
164
166
|
Finds OpenAPI Specification files to validate
|
165
167
|
|
166
168
|
Args:
|
169
|
+
spec: The path to a specific OpenAPI specification file.
|
167
170
|
discover_dir: The directory to search through.
|
168
171
|
Returns:
|
169
|
-
A list of
|
172
|
+
A list of specifications to validate.
|
170
173
|
"""
|
171
174
|
|
172
175
|
specs: list[Path] = []
|
173
176
|
|
177
|
+
# If a spec is provided, check if it exists and erorr if not
|
178
|
+
if spec:
|
179
|
+
spec_path = Path(spec)
|
180
|
+
|
181
|
+
if not spec_path.exists():
|
182
|
+
raise FileNotFoundError(f"File {spec} does not exist.")
|
183
|
+
|
184
|
+
if not spec_path.is_file():
|
185
|
+
raise IsADirectoryError(f"{spec} is a directory, not a file.")
|
186
|
+
|
187
|
+
specs.append(spec_path)
|
188
|
+
|
189
|
+
# End early if we're not also trying to find files
|
190
|
+
if not discover_dir:
|
191
|
+
return specs
|
192
|
+
|
174
193
|
if Path("openapi.json").exists():
|
175
194
|
specs.append(Path("openapi.json"))
|
176
195
|
|
@@ -258,16 +277,20 @@ if __name__ == "__main__":
|
|
258
277
|
)
|
259
278
|
|
260
279
|
args = parser.parse_args()
|
280
|
+
|
281
|
+
print('Starting amati...')
|
261
282
|
|
262
|
-
|
263
|
-
|
264
|
-
else:
|
265
|
-
specifications = discover(args.discover)
|
283
|
+
specifications = discover(args.spec, args.discover)
|
284
|
+
print(specifications)
|
266
285
|
|
267
286
|
for specification in specifications:
|
268
|
-
|
287
|
+
successful_check = run(
|
269
288
|
specification, args.consistency_check, args.local, args.html_report
|
270
|
-
)
|
271
|
-
|
272
|
-
|
273
|
-
print("Consistency check
|
289
|
+
)
|
290
|
+
|
291
|
+
if args.consistency_check and successful_check:
|
292
|
+
print(f"Consistency check successful for {specification}")
|
293
|
+
elif args.consistency_check:
|
294
|
+
print(f"Consistency check failed for {specification}")
|
295
|
+
|
296
|
+
print('completed.')
|
amati/logging.py
CHANGED
amati/model_validators.py
CHANGED
@@ -10,7 +10,7 @@ from pydantic._internal._decorators import (
|
|
10
10
|
PydanticDescriptorProxy,
|
11
11
|
)
|
12
12
|
|
13
|
-
from amati.logging import
|
13
|
+
from amati.logging import Logger
|
14
14
|
from amati.validators.generic import GenericObject
|
15
15
|
|
16
16
|
|
@@ -107,7 +107,7 @@ def at_least_one_of(
|
|
107
107
|
The validator that ensures at least one public field is non-empty.
|
108
108
|
|
109
109
|
Example:
|
110
|
-
>>>
|
110
|
+
>>> Logger.logs = []
|
111
111
|
>>>
|
112
112
|
>>> class User(GenericObject):
|
113
113
|
... name: str = ""
|
@@ -116,8 +116,8 @@ def at_least_one_of(
|
|
116
116
|
... _reference_uri = "https://example.com"
|
117
117
|
...
|
118
118
|
>>> user = User()
|
119
|
-
>>> assert len(
|
120
|
-
>>>
|
119
|
+
>>> assert len(Logger.logs) == 1
|
120
|
+
>>> Logger.logs = []
|
121
121
|
|
122
122
|
>>> class User(GenericObject):
|
123
123
|
... name: str = ""
|
@@ -128,11 +128,11 @@ def at_least_one_of(
|
|
128
128
|
...
|
129
129
|
>>>
|
130
130
|
>>> user = User(name="John") # Works fine
|
131
|
-
>>> assert not
|
131
|
+
>>> assert not Logger.logs
|
132
132
|
>>> user = User()
|
133
|
-
>>> assert len(
|
133
|
+
>>> assert len(Logger.logs) == 1
|
134
134
|
>>> user = User(age=30)
|
135
|
-
>>> assert len(
|
135
|
+
>>> assert len(Logger.logs) == 2
|
136
136
|
|
137
137
|
|
138
138
|
Note:
|
@@ -157,7 +157,7 @@ def at_least_one_of(
|
|
157
157
|
public_fields = ", ".join(f"{name}" for name in candidates.keys())
|
158
158
|
|
159
159
|
msg = f"{public_fields} do not have values, expected at least one."
|
160
|
-
|
160
|
+
Logger.log(
|
161
161
|
{
|
162
162
|
"msg": msg,
|
163
163
|
"type": "value_error",
|
@@ -191,7 +191,7 @@ def only_one_of(
|
|
191
191
|
The validator that ensures at one public field is non-empty.
|
192
192
|
|
193
193
|
Example:
|
194
|
-
>>>
|
194
|
+
>>> Logger.logs = []
|
195
195
|
>>>
|
196
196
|
>>> class User(GenericObject):
|
197
197
|
... email: str = ""
|
@@ -201,10 +201,10 @@ def only_one_of(
|
|
201
201
|
...
|
202
202
|
>>> user = User(email="test@example.com") # Works fine
|
203
203
|
>>> user = User(name="123-456-7890") # Works fine
|
204
|
-
>>> assert not
|
204
|
+
>>> assert not Logger.logs
|
205
205
|
>>> user = User(email="a@b.com", name="123")
|
206
|
-
>>> assert
|
207
|
-
>>>
|
206
|
+
>>> assert Logger.logs
|
207
|
+
>>> Logger.logs = []
|
208
208
|
|
209
209
|
>>> class User(GenericObject):
|
210
210
|
... name: str = ""
|
@@ -216,11 +216,11 @@ def only_one_of(
|
|
216
216
|
>>> user = User(name="Bob") # Works fine
|
217
217
|
>>> user = User(email="test@example.com") # Works fine
|
218
218
|
>>> user = User(name="Bob", age=30) # Works fine
|
219
|
-
>>> assert not
|
219
|
+
>>> assert not Logger.logs
|
220
220
|
>>> user = User(name="Bob", email="a@b.com")
|
221
|
-
>>> assert len(
|
221
|
+
>>> assert len(Logger.logs) == 1
|
222
222
|
>>> user = User(age=30)
|
223
|
-
>>> assert len(
|
223
|
+
>>> assert len(Logger.logs) == 2
|
224
224
|
|
225
225
|
Note:
|
226
226
|
Only public fields (not starting with '_') are checked. Private fields
|
@@ -249,7 +249,7 @@ def only_one_of(
|
|
249
249
|
field_string = "none"
|
250
250
|
msg = f"Expected at most one field to have a value, {field_string} did"
|
251
251
|
|
252
|
-
|
252
|
+
Logger.log(
|
253
253
|
{
|
254
254
|
"msg": msg,
|
255
255
|
"type": type_ or "value_error",
|
@@ -282,7 +282,7 @@ def all_of(
|
|
282
282
|
The validator that ensures at most one public field is non-empty.
|
283
283
|
|
284
284
|
Example:
|
285
|
-
>>>
|
285
|
+
>>> Logger.logs = []
|
286
286
|
>>>
|
287
287
|
>>> class User(GenericObject):
|
288
288
|
... email: str = ""
|
@@ -291,11 +291,11 @@ def all_of(
|
|
291
291
|
... _reference_uri = "https://example.com"
|
292
292
|
...
|
293
293
|
>>> user = User(email="a@b.com", name="123") # Works fine
|
294
|
-
>>> assert not
|
294
|
+
>>> assert not Logger.logs
|
295
295
|
>>> user = User(email="test@example.com")
|
296
|
-
>>> assert len(
|
296
|
+
>>> assert len(Logger.logs) == 1
|
297
297
|
>>> user = User(name="123-456-7890")
|
298
|
-
>>> assert len(
|
298
|
+
>>> assert len(Logger.logs) == 2
|
299
299
|
|
300
300
|
>>> class User(GenericObject):
|
301
301
|
... name: str = ""
|
@@ -304,17 +304,17 @@ def all_of(
|
|
304
304
|
... _all_of = all_of(["name", "email"])
|
305
305
|
... _reference_uri = "https://example.com"
|
306
306
|
...
|
307
|
-
>>>
|
307
|
+
>>> Logger.logs = []
|
308
308
|
>>> user = User(name="Bob", email="a@b.com") # Works fine
|
309
|
-
>>> assert not
|
309
|
+
>>> assert not Logger.logs
|
310
310
|
>>> user = User(name="Bob")
|
311
|
-
>>> assert len(
|
311
|
+
>>> assert len(Logger.logs) == 1
|
312
312
|
>>> user = User(email="test@example.com")
|
313
|
-
>>> assert len(
|
313
|
+
>>> assert len(Logger.logs) == 2
|
314
314
|
>>> user = User(age=30)
|
315
|
-
>>> assert len(
|
315
|
+
>>> assert len(Logger.logs) == 3
|
316
316
|
>>> user = User(name="Bob", age=30)
|
317
|
-
>>> assert len(
|
317
|
+
>>> assert len(Logger.logs) == 4
|
318
318
|
|
319
319
|
Note:
|
320
320
|
Only public fields (not starting with '_') are checked. Private fields
|
@@ -339,7 +339,7 @@ def all_of(
|
|
339
339
|
if falsy:
|
340
340
|
msg = f"Expected at all fields to have a value, {", ".join(falsy)} did not"
|
341
341
|
|
342
|
-
|
342
|
+
Logger.log(
|
343
343
|
{
|
344
344
|
"msg": msg,
|
345
345
|
"type": "value_error",
|
@@ -378,7 +378,7 @@ def if_then(
|
|
378
378
|
ValueError: If a condition and consequence are not present
|
379
379
|
|
380
380
|
Example:
|
381
|
-
>>>
|
381
|
+
>>> Logger.logs = []
|
382
382
|
>>>
|
383
383
|
>>> class User(GenericObject):
|
384
384
|
... role: str = ""
|
@@ -390,11 +390,11 @@ def if_then(
|
|
390
390
|
... _reference_uri = "https://example.com"
|
391
391
|
...
|
392
392
|
>>> user = User(role="admin", can_edit=True) # Works fine
|
393
|
-
>>> assert not
|
393
|
+
>>> assert not Logger.logs
|
394
394
|
>>> user = User(role="admin", can_edit=False) # Fails validation
|
395
|
-
>>> assert len(
|
395
|
+
>>> assert len(Logger.logs) == 1
|
396
396
|
>>> user = User(role="user", can_edit=False) # Works fine
|
397
|
-
>>> assert len(
|
397
|
+
>>> assert len(Logger.logs) == 1
|
398
398
|
"""
|
399
399
|
|
400
400
|
@model_validator(mode="after")
|
@@ -431,7 +431,7 @@ def if_then(
|
|
431
431
|
if value == actual:
|
432
432
|
continue
|
433
433
|
|
434
|
-
|
434
|
+
Logger.log(
|
435
435
|
{
|
436
436
|
"msg": f"Expected {field} to be {"in " if iterable else ""}"
|
437
437
|
f"{value} found {actual}",
|
amati/validators/generic.py
CHANGED
@@ -20,7 +20,7 @@ from typing import (
|
|
20
20
|
from pydantic import BaseModel, ConfigDict, PrivateAttr
|
21
21
|
from pydantic_core._pydantic_core import PydanticUndefined
|
22
22
|
|
23
|
-
from amati.logging import
|
23
|
+
from amati.logging import Logger
|
24
24
|
|
25
25
|
|
26
26
|
class GenericObject(BaseModel):
|
@@ -47,7 +47,7 @@ class GenericObject(BaseModel):
|
|
47
47
|
and field not in self.get_field_aliases()
|
48
48
|
):
|
49
49
|
message = f"{field} is not a valid field for {self.__repr_name__()}."
|
50
|
-
|
50
|
+
Logger.log(
|
51
51
|
{
|
52
52
|
"msg": message,
|
53
53
|
"type": "value_error",
|
@@ -79,7 +79,7 @@ class GenericObject(BaseModel):
|
|
79
79
|
|
80
80
|
for field in excess_fields:
|
81
81
|
message = f"{field} is not a valid field for {self.__repr_name__()}."
|
82
|
-
|
82
|
+
Logger.log(
|
83
83
|
{
|
84
84
|
"msg": message,
|
85
85
|
"type": "value_error",
|
amati/validators/oas304.py
CHANGED
@@ -42,7 +42,7 @@ from amati.fields import (
|
|
42
42
|
from amati.fields.commonmark import CommonMark
|
43
43
|
from amati.fields.json import JSON
|
44
44
|
from amati.fields.oas import OpenAPI, RuntimeExpression
|
45
|
-
from amati.logging import
|
45
|
+
from amati.logging import Logger
|
46
46
|
from amati.validators.generic import GenericObject, allow_extra_fields
|
47
47
|
|
48
48
|
type JSONPrimitive = str | int | float | bool | None
|
@@ -179,7 +179,7 @@ class ServerVariableObject(GenericObject):
|
|
179
179
|
return self
|
180
180
|
|
181
181
|
if self.default not in self.enum:
|
182
|
-
|
182
|
+
Logger.log(
|
183
183
|
{
|
184
184
|
"msg": f"The default value {self.default} is not in the enum list {self.enum}", # pylint: disable=line-too-long
|
185
185
|
"type": "warning",
|
@@ -647,7 +647,7 @@ class XMLObject(GenericObject):
|
|
647
647
|
"""
|
648
648
|
if value.type == URIType.RELATIVE:
|
649
649
|
message = "XML namespace {value} cannot be a relative URI"
|
650
|
-
|
650
|
+
Logger.log(
|
651
651
|
{
|
652
652
|
"msg": message,
|
653
653
|
"type": "value_error",
|
@@ -726,7 +726,7 @@ class SchemaObject(GenericObject):
|
|
726
726
|
# This will validate the structure conforms to JSON Schema
|
727
727
|
validator_cls(meta_schema).validate(schema_dict) # type: ignore
|
728
728
|
except JSONVSchemeValidationError as e:
|
729
|
-
|
729
|
+
Logger.log(
|
730
730
|
{
|
731
731
|
"msg": f"Invalid JSON Schema: {e.message}",
|
732
732
|
"type": "value_error",
|
amati/validators/oas311.py
CHANGED
@@ -37,7 +37,7 @@ from amati.fields.commonmark import CommonMark
|
|
37
37
|
from amati.fields.json import JSON
|
38
38
|
from amati.fields.oas import OpenAPI
|
39
39
|
from amati.fields.spdx_licences import VALID_LICENCES
|
40
|
-
from amati.logging import
|
40
|
+
from amati.logging import Logger
|
41
41
|
from amati.validators.generic import GenericObject, allow_extra_fields
|
42
42
|
from amati.validators.oas304 import (
|
43
43
|
CallbackObject,
|
@@ -105,7 +105,7 @@ class LicenceObject(GenericObject):
|
|
105
105
|
try:
|
106
106
|
SPDXURL(self.url)
|
107
107
|
except AmatiValueError:
|
108
|
-
|
108
|
+
Logger.log(
|
109
109
|
{
|
110
110
|
"msg": f"{str(self.url)} is not a valid SPDX URL",
|
111
111
|
"type": "warning",
|
@@ -122,7 +122,7 @@ class LicenceObject(GenericObject):
|
|
122
122
|
and self.identifier
|
123
123
|
and str(self.url) not in VALID_LICENCES[self.identifier]
|
124
124
|
):
|
125
|
-
|
125
|
+
Logger.log(
|
126
126
|
{
|
127
127
|
"msg": f"{self.url} is not associated with the identifier {self.identifier}", # pylint: disable=line-too-long
|
128
128
|
"type": "warning",
|
@@ -214,7 +214,7 @@ class ServerVariableObject(GenericObject):
|
|
214
214
|
return self
|
215
215
|
|
216
216
|
if self.default not in self.enum:
|
217
|
-
|
217
|
+
Logger.log(
|
218
218
|
{
|
219
219
|
"msg": f"The default value {self.default} is not in the enum list {self.enum}", # pylint: disable=line-too-long
|
220
220
|
"type": "value_error",
|
@@ -396,7 +396,7 @@ class SchemaObject(GenericObject):
|
|
396
396
|
# This will validate the structure conforms to JSON Schema
|
397
397
|
validator_cls(meta_schema).validate(schema_dict) # type: ignore
|
398
398
|
except JSONVSchemeValidationError as e:
|
399
|
-
|
399
|
+
Logger.log(
|
400
400
|
{
|
401
401
|
"msg": f"Invalid JSON Schema: {e.message}",
|
402
402
|
"type": "value_error",
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: amati
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.2
|
4
4
|
Summary: Validates that a .yaml or .json file conforms to the OpenAPI Specifications 3.x.
|
5
5
|
Project-URL: Homepage, https://github.com/ben-alexander/amati
|
6
6
|
Project-URL: Issues, https://github.com/ben-alexander/amati/issues
|
@@ -1,11 +1,11 @@
|
|
1
1
|
amati/__init__.py,sha256=bsPoaYcFmejsqQN6eDxcuGKXg8ZXgk1NoD6Hujwhkkw,428
|
2
2
|
amati/_error_handler.py,sha256=s_Nhnq8hz7xQP_yeCCJ8Hwzs5xyrxlisd1XFYYECtSY,1244
|
3
3
|
amati/_resolve_forward_references.py,sha256=iOibCv_XuIUpe7qbRBtPzd7H5rvL-aldUzlXV_9FJaw,6511
|
4
|
-
amati/amati.py,sha256=
|
4
|
+
amati/amati.py,sha256=y-dHwu137R18hC-vWmOMmmFvagOJ0rpIsMwFUG4QOpw,8367
|
5
5
|
amati/exceptions.py,sha256=X_RDBVjcFn_gXPFkhKuREmm5nDCdGs4rBy6sZsXBJDo,609
|
6
6
|
amati/file_handler.py,sha256=h95t7TDxDA_qr4rIe2bddCjh2Xw5Ht27yLK7zCQzT68,5085
|
7
|
-
amati/logging.py,sha256=
|
8
|
-
amati/model_validators.py,sha256=
|
7
|
+
amati/logging.py,sha256=srg-T3z4mDFlP-RpcQhz685omnL24nXTthrTEIfKNTg,1337
|
8
|
+
amati/model_validators.py,sha256=ffOmhhGRGPKZsr9TPW1q_57PoLfSq7zYNTq86t3BvJc,14915
|
9
9
|
amati/data/http-status-codes.json,sha256=xEGBlE7eCXaQFG2gNSeK1aCs7A8XxOe6JcXwAKxwWBU,10903
|
10
10
|
amati/data/iso9110.json,sha256=YLv6V8dPrgagjAnHUWKMmade5xboV_eJB9smnUfwgDY,1692
|
11
11
|
amati/data/media-types.json,sha256=tSRZTUqIQeiW5cmw5aDAWNqXLGcvX4gwwEkDL5H6lDg,47108
|
@@ -27,11 +27,11 @@ amati/grammars/oas.py,sha256=vTMnJIxeiTC3gJATOo0RRZDlRsQOWkY6jG1VRzazLjM,1666
|
|
27
27
|
amati/grammars/rfc6901.py,sha256=ChpKnoPDTsaNMvJT_UCpY8xwbl9zWZcD36MjRsD8IpQ,656
|
28
28
|
amati/grammars/rfc7159.py,sha256=eksX1m7WRzQ9iGeANG1BgmsXbAhV6gQVRkPpLqPjq6U,2218
|
29
29
|
amati/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
-
amati/validators/generic.py,sha256=
|
31
|
-
amati/validators/oas304.py,sha256=
|
32
|
-
amati/validators/oas311.py,sha256=
|
33
|
-
amati-0.2.
|
34
|
-
amati-0.2.
|
35
|
-
amati-0.2.
|
36
|
-
amati-0.2.
|
37
|
-
amati-0.2.
|
30
|
+
amati/validators/generic.py,sha256=huFG9_Fbk5ih8y9S7fl8vRGl5Fr1158yb08967oUjw4,3977
|
31
|
+
amati/validators/oas304.py,sha256=KUthNRVLRCjcLvN6IQuqLAUmui0WfKc8llRFt0qJaf4,32464
|
32
|
+
amati/validators/oas311.py,sha256=XX8nUq0mGrbe5FR9C0_mTmIzMnWG09XbGwqpt8GHY8g,17702
|
33
|
+
amati-0.2.2.dist-info/METADATA,sha256=jFpRbk4OdLgFrxuedrk9VjM0J-kqmNxfI7c93SzHWQA,6425
|
34
|
+
amati-0.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
35
|
+
amati-0.2.2.dist-info/entry_points.txt,sha256=sacBb6g0f0ZJtNjNYx93_Xe4y5xzawvklCFVXup9ru0,37
|
36
|
+
amati-0.2.2.dist-info/licenses/LICENSE,sha256=WAA01ZXeNs1bwpNWKR6aVucjtYjYm_iQIUYkCAENjqM,1070
|
37
|
+
amati-0.2.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|