amati 0.2.21__py3-none-any.whl → 0.2.23__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 +10 -5
- amati/fields/email.py +19 -0
- amati/fields/iso9110.py +1 -1
- amati/fields/media.py +2 -1
- amati/fields/spdx_licences.py +1 -1
- amati/fields/uri.py +15 -2
- amati/file_handler.py +116 -9
- amati/model_validators.py +3 -3
- amati/py.typed +0 -0
- amati/validators/generic.py +33 -7
- amati/validators/oas304.py +7 -5
- amati/validators/oas311.py +4 -2
- {amati-0.2.21.dist-info → amati-0.2.23.dist-info}/METADATA +2 -1
- {amati-0.2.21.dist-info → amati-0.2.23.dist-info}/RECORD +17 -16
- {amati-0.2.21.dist-info → amati-0.2.23.dist-info}/WHEEL +0 -0
- {amati-0.2.21.dist-info → amati-0.2.23.dist-info}/entry_points.txt +0 -0
- {amati-0.2.21.dist-info → amati-0.2.23.dist-info}/licenses/LICENSE +0 -0
amati/amati.py
CHANGED
|
@@ -92,7 +92,7 @@ def run(
|
|
|
92
92
|
consistency_check: bool = False,
|
|
93
93
|
local: bool = False,
|
|
94
94
|
html_report: bool = False,
|
|
95
|
-
):
|
|
95
|
+
) -> bool:
|
|
96
96
|
"""
|
|
97
97
|
Runs the full amati process on a specific specification file.
|
|
98
98
|
|
|
@@ -220,7 +220,7 @@ def discover(spec: str, discover_dir: str = ".") -> list[Path]:
|
|
|
220
220
|
if __name__ == "__main__":
|
|
221
221
|
import argparse
|
|
222
222
|
|
|
223
|
-
parser = argparse.ArgumentParser(
|
|
223
|
+
parser: argparse.ArgumentParser = argparse.ArgumentParser(
|
|
224
224
|
prog="amati",
|
|
225
225
|
description="""
|
|
226
226
|
Tests whether a OpenAPI specification is valid. Will look an openapi.json
|
|
@@ -232,6 +232,7 @@ if __name__ == "__main__":
|
|
|
232
232
|
Creates a file <filename>.errors.json alongside the original specification
|
|
233
233
|
containing a JSON representation of all the errors.
|
|
234
234
|
""",
|
|
235
|
+
suggest_on_error=True,
|
|
235
236
|
)
|
|
236
237
|
|
|
237
238
|
parser.add_argument(
|
|
@@ -282,7 +283,7 @@ if __name__ == "__main__":
|
|
|
282
283
|
"or , media types from IANA",
|
|
283
284
|
)
|
|
284
285
|
|
|
285
|
-
args = parser.parse_args()
|
|
286
|
+
args: argparse.Namespace = parser.parse_args()
|
|
286
287
|
|
|
287
288
|
logger.remove() # Remove the default logger
|
|
288
289
|
# Add a new logger that outputs to stderr with a specific format
|
|
@@ -301,15 +302,19 @@ if __name__ == "__main__":
|
|
|
301
302
|
sys.exit(1)
|
|
302
303
|
|
|
303
304
|
try:
|
|
304
|
-
specifications = discover(args.spec, args.discover)
|
|
305
|
+
specifications: list[Path] = discover(args.spec, args.discover)
|
|
305
306
|
except Exception as e:
|
|
306
307
|
logger.error(str(e))
|
|
307
308
|
sys.exit(1)
|
|
308
309
|
|
|
310
|
+
specification: Path
|
|
309
311
|
for specification in specifications:
|
|
310
|
-
successful_check = False
|
|
312
|
+
successful_check: bool = False
|
|
311
313
|
logger.info(f"Processing specification {specification}")
|
|
312
314
|
|
|
315
|
+
# Top-level try/except to ensure one failed spec doesn't stop the rest
|
|
316
|
+
# from being processed.
|
|
317
|
+
e: Exception
|
|
313
318
|
try:
|
|
314
319
|
successful_check = run(
|
|
315
320
|
specification, args.consistency_check, args.local, args.html_report
|
amati/fields/email.py
CHANGED
|
@@ -12,6 +12,25 @@ reference_uri = "https://www.rfc-editor.org/rfc/rfc5322#section-3"
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class Email(_Str):
|
|
15
|
+
"""A string subclass representing a validated RFC 5322 email address.
|
|
16
|
+
|
|
17
|
+
This class ensures that email addresses conform to the RFC 5322 specification
|
|
18
|
+
by validating the input during initialization. Invalid addresses raise an
|
|
19
|
+
AmatiValueError.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
value: The email address string to validate.
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
AmatiValueError: If the value is not a valid RFC 5322 email address.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
>>> email = Email("user@example.com")
|
|
29
|
+
>>> invalid = Email("not-an-email")
|
|
30
|
+
Traceback (most recent call last):
|
|
31
|
+
amati.exceptions.AmatiValueError: message
|
|
32
|
+
"""
|
|
33
|
+
|
|
15
34
|
def __init__(self, value: str):
|
|
16
35
|
try:
|
|
17
36
|
rfc5322.Rule("address").parse_all(value)
|
amati/fields/iso9110.py
CHANGED
amati/fields/media.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Validates a media type or media type range according to RFC7321
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from annotationlib import get_annotations
|
|
5
6
|
from typing import cast
|
|
6
7
|
|
|
7
8
|
from abnf import ParseError
|
|
@@ -55,7 +56,7 @@ class MediaType(_Str):
|
|
|
55
56
|
media_type = rfc7231.Rule("media-type").parse_all(value)
|
|
56
57
|
|
|
57
58
|
for node in media_type.children:
|
|
58
|
-
if node.name in self.
|
|
59
|
+
if node.name in get_annotations(self.__class__):
|
|
59
60
|
self.__dict__[node.name] = node.value
|
|
60
61
|
|
|
61
62
|
except ParseError as e:
|
amati/fields/spdx_licences.py
CHANGED
|
@@ -11,7 +11,7 @@ from amati.fields.uri import URI
|
|
|
11
11
|
|
|
12
12
|
reference_uri = "https://spdx.org/licenses/"
|
|
13
13
|
|
|
14
|
-
data = cast(list[dict[str, Any]], get("spdx_licences"))
|
|
14
|
+
data: list[dict[str, Any]] = cast(list[dict[str, Any]], get("spdx_licences"))
|
|
15
15
|
|
|
16
16
|
# `seeAlso` is the list of URLs associated with each licence
|
|
17
17
|
VALID_LICENCES: dict[str, list[str]] = {
|
amati/fields/uri.py
CHANGED
|
@@ -65,9 +65,22 @@ class Scheme(_Str):
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
class URIType(str, Enum):
|
|
68
|
+
"""Enumeration of URI reference types.
|
|
69
|
+
|
|
70
|
+
Categorizes different types of URI references as defined in RFC 3986,
|
|
71
|
+
along with JSON Pointer references from RFC 6901.
|
|
72
|
+
|
|
73
|
+
Attributes:
|
|
74
|
+
ABSOLUTE: A URI with a scheme component (e.g., "https://example.com/path").
|
|
75
|
+
RELATIVE: A relative reference without a scheme (e.g., "../path/file.json").
|
|
76
|
+
NETWORK_PATH: A network path reference starting with "//"
|
|
77
|
+
(e.g., "//example.com").
|
|
78
|
+
JSON_POINTER: A JSON Pointer as defined in RFC 6901 (e.g., "#/foo/bar/0").
|
|
79
|
+
"""
|
|
80
|
+
|
|
68
81
|
ABSOLUTE = "absolute"
|
|
69
82
|
RELATIVE = "relative"
|
|
70
|
-
|
|
83
|
+
NETWORK_PATH = "network-path"
|
|
71
84
|
JSON_POINTER = "JSON pointer"
|
|
72
85
|
|
|
73
86
|
|
|
@@ -152,7 +165,7 @@ class URI(_Str):
|
|
|
152
165
|
if self.scheme:
|
|
153
166
|
return URIType.ABSOLUTE
|
|
154
167
|
if self.authority:
|
|
155
|
-
return URIType.
|
|
168
|
+
return URIType.NETWORK_PATH
|
|
156
169
|
if self.path:
|
|
157
170
|
if str(self).startswith("#"):
|
|
158
171
|
return URIType.JSON_POINTER
|
amati/file_handler.py
CHANGED
|
@@ -19,9 +19,9 @@ Supported File Types:
|
|
|
19
19
|
- YAML: .yaml, .yml (optionally gzip compressed)
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
import gzip
|
|
23
22
|
import json
|
|
24
23
|
from abc import ABC, abstractmethod
|
|
24
|
+
from compression import gzip
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
|
|
27
27
|
import yaml
|
|
@@ -33,16 +33,37 @@ type JSONValue = JSONPrimitive | JSONArray | JSONObject
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
class FileLoader(ABC):
|
|
36
|
-
"""Abstract base class for file loaders.
|
|
36
|
+
"""Abstract base class for file loaders.
|
|
37
|
+
|
|
38
|
+
Defines the interface for loading and parsing files of different formats.
|
|
39
|
+
Implementations should provide format-specific handling logic.
|
|
40
|
+
"""
|
|
37
41
|
|
|
38
42
|
@abstractmethod
|
|
39
43
|
def can_handle(self, file_path: Path) -> bool:
|
|
40
|
-
"""Check if this loader can handle the given file.
|
|
44
|
+
"""Check if this loader can handle the given file.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
file_path: Path to the file to check.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
True if this loader can handle the file, False otherwise.
|
|
51
|
+
"""
|
|
41
52
|
pass
|
|
42
53
|
|
|
43
54
|
@abstractmethod
|
|
44
55
|
def load(self, content: str) -> JSONObject:
|
|
45
|
-
"""Load and parse the file content.
|
|
56
|
+
"""Load and parse the file content.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
content: The raw file content as a string.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The parsed content as a JSONObject.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
May raise implementation-specific exceptions for parsing errors.
|
|
66
|
+
"""
|
|
46
67
|
pass
|
|
47
68
|
|
|
48
69
|
|
|
@@ -50,9 +71,28 @@ class JSONLoader(FileLoader):
|
|
|
50
71
|
"""Loader for JSON files."""
|
|
51
72
|
|
|
52
73
|
def can_handle(self, file_path: Path) -> bool:
|
|
74
|
+
"""Check if this loader can handle the given file.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
file_path: Path to the file to check.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
True if this loader can handle the file, False otherwise.
|
|
81
|
+
"""
|
|
53
82
|
return file_path.suffix.lower() in {".json", ".js"}
|
|
54
83
|
|
|
55
84
|
def load(self, content: str) -> JSONObject:
|
|
85
|
+
"""Load and parse the file content.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
content: The raw file content as a string.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The parsed content as a JSONObject.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
May raise implementation-specific exceptions for parsing errors.
|
|
95
|
+
"""
|
|
56
96
|
return json.loads(content)
|
|
57
97
|
|
|
58
98
|
|
|
@@ -60,21 +100,56 @@ class YAMLLoader(FileLoader):
|
|
|
60
100
|
"""Loader for YAML files."""
|
|
61
101
|
|
|
62
102
|
def can_handle(self, file_path: Path) -> bool:
|
|
103
|
+
"""Check if this loader can handle the given file.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
file_path: Path to the file to check.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
True if this loader can handle the file, False otherwise.
|
|
110
|
+
"""
|
|
63
111
|
return file_path.suffix.lower() in {".yaml", ".yml"}
|
|
64
112
|
|
|
65
113
|
def load(self, content: str) -> JSONObject:
|
|
114
|
+
"""Load and parse the file content.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
content: The raw file content as a string.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
The parsed content as a JSONObject.
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
May raise implementation-specific exceptions for parsing errors.
|
|
124
|
+
"""
|
|
66
125
|
return yaml.safe_load(content)
|
|
67
126
|
|
|
68
127
|
|
|
69
128
|
class FileProcessor:
|
|
70
|
-
"""Main processor for handling gzipped and regular files.
|
|
129
|
+
"""Main processor for handling gzipped and regular files.
|
|
130
|
+
|
|
131
|
+
Processes files in various formats (JSON, YAML) with optional gzip compression.
|
|
132
|
+
Automatically detects compression and selects the appropriate loader based on
|
|
133
|
+
file extension.
|
|
134
|
+
|
|
135
|
+
Attributes:
|
|
136
|
+
loaders: List of available file loaders for different formats.
|
|
137
|
+
"""
|
|
71
138
|
|
|
72
139
|
def __init__(self) -> None:
|
|
140
|
+
"""Initialize the FileProcessor with default loaders."""
|
|
73
141
|
self.loaders: list[FileLoader] = [JSONLoader(), YAMLLoader()]
|
|
74
142
|
|
|
75
143
|
@staticmethod
|
|
76
144
|
def _is_gzip_file(file_path: Path) -> bool:
|
|
77
|
-
"""Check if file is gzipped by reading magic bytes.
|
|
145
|
+
"""Check if file is gzipped by reading magic bytes.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
file_path: Path to the file to check.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
True if the file is gzip-compressed, False otherwise.
|
|
152
|
+
"""
|
|
78
153
|
try:
|
|
79
154
|
with open(file_path, "rb") as f:
|
|
80
155
|
magic = f.read(2)
|
|
@@ -84,13 +159,32 @@ class FileProcessor:
|
|
|
84
159
|
|
|
85
160
|
@staticmethod
|
|
86
161
|
def _get_decompressed_path(file_path: Path) -> Path:
|
|
87
|
-
"""Get the path without .gz extension for determining file type.
|
|
162
|
+
"""Get the path without .gz extension for determining file type.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
file_path: Path to the potentially compressed file.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
The file path with .gz extension removed if present, otherwise
|
|
169
|
+
the original path unchanged.
|
|
170
|
+
"""
|
|
88
171
|
if file_path.suffix.lower() == ".gz":
|
|
89
172
|
return file_path.with_suffix("")
|
|
90
173
|
return file_path
|
|
91
174
|
|
|
92
175
|
def _read_file_content(self, file_path: Path) -> str:
|
|
93
|
-
"""Read file content, decompressing if necessary.
|
|
176
|
+
"""Read file content, decompressing if necessary.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
file_path: Path to the file to read.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
The file content as a UTF-8 encoded string.
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
OSError: If the file cannot be read.
|
|
186
|
+
gzip.BadGzipFile: If the file appears to be gzipped but is corrupted.
|
|
187
|
+
"""
|
|
94
188
|
if self._is_gzip_file(file_path):
|
|
95
189
|
with gzip.open(file_path, "rt", encoding="utf-8") as f:
|
|
96
190
|
return f.read()
|
|
@@ -99,7 +193,20 @@ class FileProcessor:
|
|
|
99
193
|
return f.read()
|
|
100
194
|
|
|
101
195
|
def _get_appropriate_loader(self, file_path: Path) -> FileLoader:
|
|
102
|
-
"""Get the appropriate loader for the file type.
|
|
196
|
+
"""Get the appropriate loader for the file type.
|
|
197
|
+
|
|
198
|
+
Determines the correct loader based on the file extension, ignoring
|
|
199
|
+
any .gz compression extension.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
file_path: Path to the file needing a loader.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
The appropriate FileLoader instance for the file type.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
ValueError: If no suitable loader is found for the file type.
|
|
209
|
+
"""
|
|
103
210
|
# Use the decompressed path to determine file type
|
|
104
211
|
target_path = self._get_decompressed_path(file_path)
|
|
105
212
|
|
amati/model_validators.py
CHANGED
|
@@ -21,15 +21,15 @@ class UnknownValue:
|
|
|
21
21
|
|
|
22
22
|
_instance = None
|
|
23
23
|
|
|
24
|
-
def __new__(cls):
|
|
24
|
+
def __new__(cls) -> "UnknownValue":
|
|
25
25
|
if cls._instance is None:
|
|
26
26
|
cls._instance = super().__new__(cls)
|
|
27
27
|
return cls._instance
|
|
28
28
|
|
|
29
|
-
def __repr__(self): # pragma: no cover
|
|
29
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
30
30
|
return "UNKNOWN"
|
|
31
31
|
|
|
32
|
-
def __str__(self): # pragma: no cover
|
|
32
|
+
def __str__(self) -> str: # pragma: no cover
|
|
33
33
|
return "UNKNOWN"
|
|
34
34
|
|
|
35
35
|
|
amati/py.typed
ADDED
|
File without changes
|
amati/validators/generic.py
CHANGED
|
@@ -20,15 +20,30 @@ from amati._logging import Logger
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class GenericObject(BaseModel):
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
"""A generic model extending Pydantic BaseModel with enhanced validation.
|
|
24
|
+
|
|
25
|
+
Provides additional functionality for handling extra fields, including pattern
|
|
26
|
+
matching validation and detailed logging of invalid fields. This class validates
|
|
27
|
+
extra fields against optional regex patterns and logs violations without raising
|
|
28
|
+
exceptions.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
_reference_uri: URI reference for error reporting and documentation.
|
|
32
|
+
_extra_field_pattern: Optional regex pattern to validate extra field names.
|
|
26
33
|
"""
|
|
27
34
|
|
|
28
35
|
_reference_uri: ClassVar[str] = PrivateAttr()
|
|
29
36
|
_extra_field_pattern: re.Pattern[str] | None = PrivateAttr()
|
|
30
37
|
|
|
31
38
|
def __init__(self, **data: Any) -> None:
|
|
39
|
+
"""Initialize the model and validate extra fields.
|
|
40
|
+
|
|
41
|
+
Logs any fields that are not recognized as valid model fields or aliases
|
|
42
|
+
when extra fields are not allowed by the model configuration.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
**data: Arbitrary keyword arguments representing model data.
|
|
46
|
+
"""
|
|
32
47
|
super().__init__(**data)
|
|
33
48
|
|
|
34
49
|
if self.model_config.get("extra") == "allow":
|
|
@@ -53,6 +68,15 @@ class GenericObject(BaseModel):
|
|
|
53
68
|
)
|
|
54
69
|
|
|
55
70
|
def model_post_init(self, __context: Any) -> None:
|
|
71
|
+
"""Validate extra fields against the configured pattern after initialization.
|
|
72
|
+
|
|
73
|
+
If an extra field pattern is configured, checks all extra fields against
|
|
74
|
+
the pattern and logs any fields that don't match. This allows for flexible
|
|
75
|
+
validation of dynamically named fields.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
__context: Pydantic context object passed during initialization.
|
|
79
|
+
"""
|
|
56
80
|
if not self.model_extra:
|
|
57
81
|
return
|
|
58
82
|
|
|
@@ -85,12 +109,14 @@ class GenericObject(BaseModel):
|
|
|
85
109
|
)
|
|
86
110
|
|
|
87
111
|
def get_field_aliases(self) -> list[str]:
|
|
88
|
-
"""
|
|
89
|
-
|
|
90
|
-
fields
|
|
112
|
+
"""Get all field aliases defined for the model.
|
|
113
|
+
|
|
114
|
+
Collects aliases from all model fields to help validate whether provided
|
|
115
|
+
field names are valid, even if they use alias names instead of field names.
|
|
91
116
|
|
|
92
117
|
Returns:
|
|
93
|
-
A list of field aliases
|
|
118
|
+
A list of all field aliases defined in the model. Empty list if no
|
|
119
|
+
aliases are defined.
|
|
94
120
|
"""
|
|
95
121
|
|
|
96
122
|
aliases: list[str] = []
|
amati/validators/oas304.py
CHANGED
|
@@ -94,7 +94,9 @@ class ReferenceObject(GenericObject):
|
|
|
94
94
|
as per RFC6901.
|
|
95
95
|
"""
|
|
96
96
|
|
|
97
|
-
model_config = ConfigDict(
|
|
97
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(
|
|
98
|
+
extra="forbid", populate_by_name=True
|
|
99
|
+
)
|
|
98
100
|
|
|
99
101
|
ref: URI = Field(alias="$ref")
|
|
100
102
|
_reference_uri: ClassVar[str] = (
|
|
@@ -225,7 +227,7 @@ class ExternalDocumentationObject(GenericObject):
|
|
|
225
227
|
class PathsObject(GenericObject):
|
|
226
228
|
"""Validates the OpenAPI Specification paths object - §4.8.8"""
|
|
227
229
|
|
|
228
|
-
model_config = ConfigDict(extra="allow")
|
|
230
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow")
|
|
229
231
|
|
|
230
232
|
@model_validator(mode="before")
|
|
231
233
|
@classmethod
|
|
@@ -403,7 +405,7 @@ class ResponsesObject(GenericObject):
|
|
|
403
405
|
Validates the OpenAPI Specification responses object - §4.8.16
|
|
404
406
|
"""
|
|
405
407
|
|
|
406
|
-
model_config = ConfigDict(
|
|
408
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(
|
|
407
409
|
extra="allow",
|
|
408
410
|
)
|
|
409
411
|
|
|
@@ -498,7 +500,7 @@ class CallbackObject(GenericObject):
|
|
|
498
500
|
Validates the OpenAPI Specification callback object - §4.8.18
|
|
499
501
|
"""
|
|
500
502
|
|
|
501
|
-
model_config = ConfigDict(extra="allow")
|
|
503
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow")
|
|
502
504
|
|
|
503
505
|
# The keys are runtime expressions that resolve to a URL
|
|
504
506
|
# The values are Response Objects or Reference Objects
|
|
@@ -664,7 +666,7 @@ class SchemaObject(GenericObject):
|
|
|
664
666
|
and validated through jsonschema.
|
|
665
667
|
"""
|
|
666
668
|
|
|
667
|
-
model_config = ConfigDict(
|
|
669
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(
|
|
668
670
|
populate_by_name=True,
|
|
669
671
|
extra="allow", # Allow all standard JSON Schema fields
|
|
670
672
|
)
|
amati/validators/oas311.py
CHANGED
|
@@ -142,7 +142,9 @@ class ReferenceObject(GenericObject):
|
|
|
142
142
|
as per RFC6901.
|
|
143
143
|
"""
|
|
144
144
|
|
|
145
|
-
model_config = ConfigDict(
|
|
145
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(
|
|
146
|
+
extra="forbid", populate_by_name=True
|
|
147
|
+
)
|
|
146
148
|
|
|
147
149
|
ref: URI = Field(alias="$ref")
|
|
148
150
|
summary: str | None
|
|
@@ -336,7 +338,7 @@ class SchemaObject(GenericObject):
|
|
|
336
338
|
and validated through jsonschema.
|
|
337
339
|
"""
|
|
338
340
|
|
|
339
|
-
model_config = ConfigDict(
|
|
341
|
+
model_config: ClassVar[ConfigDict] = ConfigDict(
|
|
340
342
|
populate_by_name=True,
|
|
341
343
|
extra="allow", # Allow all standard JSON Schema fields
|
|
342
344
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amati
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.23
|
|
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
|
|
@@ -118,6 +118,7 @@ This project uses:
|
|
|
118
118
|
* [Ruff](https://docs.astral.sh/ruff/) as a linter and formatter
|
|
119
119
|
* [Hypothesis](https://hypothesis.readthedocs.io/en/latest/index.html) for test data generation
|
|
120
120
|
* [Coverage](https://coverage.readthedocs.io/en/7.6.8/) on both the tests and code for test coverage
|
|
121
|
+
* [Shellcheck](https://github.com/koalaman/shellcheck/wiki) for as SAST for shell scripts
|
|
121
122
|
|
|
122
123
|
It's expected that there are no errors and 100% of the code is reached and executed. The strategy for test coverage is based on parsing test specifications and not unit tests.
|
|
123
124
|
amati runs tests on the external specifications, detailed in `tests/data/.amati.tests.yaml`. To be able to run these tests the GitHub repos containing the specifications need to be available locally. Specific revisions of the repos can be downloaded by running the following, which will clone the repos into `.amati/amati-tests-specs/<repo-name>`.
|
|
@@ -2,10 +2,11 @@ amati/__init__.py,sha256=IXbWlVtFGTIdvxaafs8Qy8zeD3KjTDgK0omllFq8Jio,461
|
|
|
2
2
|
amati/_error_handler.py,sha256=_zs0hWqNf6NlXPk9-2MWyk6t5QGttDxNqatZ5YnCm6U,1245
|
|
3
3
|
amati/_logging.py,sha256=y7D0YwYleXx-8ainr-ydDHe4usykI9Fw_c0b90p7tqQ,1352
|
|
4
4
|
amati/_resolve_forward_references.py,sha256=-9MORpUhgusGJdvnH502p8-dc9Q6zqaB5XkPZvp4o7E,6509
|
|
5
|
-
amati/amati.py,sha256=
|
|
5
|
+
amati/amati.py,sha256=Lj-cEBNqOHuxGIEegYSlUPH6UpkFUixJrfpTQAal86s,9911
|
|
6
6
|
amati/exceptions.py,sha256=MUzW7FsE0_4BJC99hStokMItKk7OvNBiqO6Zr6d_8cY,577
|
|
7
|
-
amati/file_handler.py,sha256=
|
|
8
|
-
amati/model_validators.py,sha256=
|
|
7
|
+
amati/file_handler.py,sha256=59kAsyVHE8mddRrNgyEowgZLEsgLcsZsFq6GtH7ZYBo,8065
|
|
8
|
+
amati/model_validators.py,sha256=VJmPAhNRXm4WR5lx9Gpe2PdRB70dzrkE2r8TT5IJbEU,14889
|
|
9
|
+
amati/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
10
|
amati/_data/http_status_code.py,sha256=-BRAsm-EHOmvdKu0vHRWRPqYJu8pYUZ0bu9I4PyvYw4,867
|
|
10
11
|
amati/_data/iso9110.py,sha256=4NXWgAJYUrHW4USScOomt5LrilJ4pERI1EZrPjcFneo,569
|
|
11
12
|
amati/_data/media_types.py,sha256=N6yqhYijtRLLhuVSbFUGTFC7d-ewfQgatxCj0OlbS2c,1477
|
|
@@ -22,23 +23,23 @@ amati/_data/files/tlds.json,sha256=reuo17MSM9QfShj_esErUEDXRhUA-RY8sOk4OewsXCs,2
|
|
|
22
23
|
amati/fields/__init__.py,sha256=dv5xKYZ-i9ARt9mg72vIjiu8DwgboqiWM4WL5AFxD8c,552
|
|
23
24
|
amati/fields/_custom_types.py,sha256=zHQdzyntVEGbLSblOOdKNzpZTCkmpUZ4Ji6WS6TkcPc,2513
|
|
24
25
|
amati/fields/commonmark.py,sha256=ssRc_338xd76y0MXR9U5OMkFE1ARCy-CWVOMY7AB8SU,220
|
|
25
|
-
amati/fields/email.py,sha256=
|
|
26
|
+
amati/fields/email.py,sha256=o9OOrGWFySl4UzJxiP5Je-tH25mO4XRPSwGGxiiQdpI,1173
|
|
26
27
|
amati/fields/http_status_codes.py,sha256=TNvzztqVWFsamtrxtL-JpU-FvEKAdcOst522RGvbaCo,3205
|
|
27
|
-
amati/fields/iso9110.py,sha256=
|
|
28
|
+
amati/fields/iso9110.py,sha256=TxK2vf8YsmMYoBQ07aqmIc7CW-D4Utg3QvMOM127E_g,1745
|
|
28
29
|
amati/fields/json.py,sha256=TsNMW5gcCc3tpjDd0Sew_8okzDQu6S_FLKjGGvoRVos,304
|
|
29
|
-
amati/fields/media.py,sha256=
|
|
30
|
+
amati/fields/media.py,sha256=P5CILzL_MZN5huo9SVN6JguIDT7cXVIYlID_tuNk0VY,3049
|
|
30
31
|
amati/fields/oas.py,sha256=oRvruhRmY9VQtlqiPaRvGKeeOgjYE-JG5aUZYZay6Kg,1733
|
|
31
|
-
amati/fields/spdx_licences.py,sha256=
|
|
32
|
-
amati/fields/uri.py,sha256=
|
|
32
|
+
amati/fields/spdx_licences.py,sha256=lkmF4pRVMPjFAt-j7-pwbivhY8Ydhgo25f5AhQaLlis,2648
|
|
33
|
+
amati/fields/uri.py,sha256=h1wHIAoGI9rZtlekbL_EDALP-UI_nmE7o76AdN9gVvA,12353
|
|
33
34
|
amati/grammars/oas.py,sha256=78G9wyOYnG0zxE880DTcAfckR9TN7aSL2GfaJYKcHqU,1628
|
|
34
35
|
amati/grammars/rfc6901.py,sha256=vQPk-JROikiB6Qzujs9EDszYbpRggc5EA1vkkGNJ36U,642
|
|
35
36
|
amati/grammars/rfc7159.py,sha256=ZYPGKh7zpcrZO7FA8pysUJ4jav2yq4WxFyi7r2GbjDY,2204
|
|
36
37
|
amati/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
amati/validators/generic.py,sha256=
|
|
38
|
-
amati/validators/oas304.py,sha256=
|
|
39
|
-
amati/validators/oas311.py,sha256=
|
|
40
|
-
amati-0.2.
|
|
41
|
-
amati-0.2.
|
|
42
|
-
amati-0.2.
|
|
43
|
-
amati-0.2.
|
|
44
|
-
amati-0.2.
|
|
38
|
+
amati/validators/generic.py,sha256=NM6ZkuGdM8SDvSUBbMcVz2KIYBWFH2lYTFMVpS1I5QM,5270
|
|
39
|
+
amati/validators/oas304.py,sha256=F_U5vS44NfM0FJxbwodQ1XLXD6ke_IQGHIeP6Rv-_i8,32059
|
|
40
|
+
amati/validators/oas311.py,sha256=a5xSZ-wsZ-uFWWbZBKrRt9w7ZJKaPQKclicFZpwINIk,17370
|
|
41
|
+
amati-0.2.23.dist-info/METADATA,sha256=5KU8L9BIU3r1sYPY6Q0Lpglk7s7DiDnFmUAly1Gb-oU,7163
|
|
42
|
+
amati-0.2.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
43
|
+
amati-0.2.23.dist-info/entry_points.txt,sha256=sacBb6g0f0ZJtNjNYx93_Xe4y5xzawvklCFVXup9ru0,37
|
|
44
|
+
amati-0.2.23.dist-info/licenses/LICENSE,sha256=WAA01ZXeNs1bwpNWKR6aVucjtYjYm_iQIUYkCAENjqM,1070
|
|
45
|
+
amati-0.2.23.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|