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 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
@@ -16,7 +16,7 @@ reference_uri = (
16
16
  )
17
17
 
18
18
 
19
- data = cast(list[dict[str, str]], get("iso9110"))
19
+ data: list[dict[str, str]] = cast(list[dict[str, str]], get("iso9110"))
20
20
 
21
21
 
22
22
  HTTP_AUTHENTICATION_SCHEMES: set[str] = {
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.__annotations__:
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:
@@ -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
- NON_RELATIVE = "non-relative"
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.NON_RELATIVE
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
@@ -20,15 +20,30 @@ from amati._logging import Logger
20
20
 
21
21
 
22
22
  class GenericObject(BaseModel):
23
- """
24
- A generic model to overwrite provide extra functionality
25
- to pydantic.BaseModel.
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
- Gets a list of aliases for confirming whether extra
90
- fields are allowed.
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 for the class.
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] = []
@@ -94,7 +94,9 @@ class ReferenceObject(GenericObject):
94
94
  as per RFC6901.
95
95
  """
96
96
 
97
- model_config = ConfigDict(extra="forbid", populate_by_name=True)
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
  )
@@ -142,7 +142,9 @@ class ReferenceObject(GenericObject):
142
142
  as per RFC6901.
143
143
  """
144
144
 
145
- model_config = ConfigDict(extra="forbid", populate_by_name=True)
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.21
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=ZgNKfJ1-Yh5lNX6lFt_L3ofK8c4Q6OV-9xZGE0EO0NY,9653
5
+ amati/amati.py,sha256=Lj-cEBNqOHuxGIEegYSlUPH6UpkFUixJrfpTQAal86s,9911
6
6
  amati/exceptions.py,sha256=MUzW7FsE0_4BJC99hStokMItKk7OvNBiqO6Zr6d_8cY,577
7
- amati/file_handler.py,sha256=voFQ9FzvHTAfTAQ5ch2T_1T8TV2_huguF6dcp3wXXF8,5082
8
- amati/model_validators.py,sha256=us_l6MJlellsPJbYRKh_rEYOqIY0JNQeGJS4BKl105M,14857
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=nQaFhoR41p4LabL1-FAPMKraMQ9nmaBEADbjZwzrltc,557
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=xV8AzlG45ayErhBqqRiMBnwn_7so4efSY0ebV0iJtdo,1723
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=OxuO620xpNZ6vwoKIJxFTSPAy6d87dmX1kWKWxENoOQ,2996
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=59MFqSpklnaL1ZBRlnxZ4WlGnm0O63WDpGfplv-htO4,2626
32
- amati/fields/uri.py,sha256=MzRaIkjxzeA-GxvSNtZ_y1Vjj7M9jF26XgIeO50hH7g,11800
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=W-zqvSGdfGGKreK91ZjTPnAX8ukwucZR_vCdwEqS4F4,3943
38
- amati/validators/oas304.py,sha256=R1lCDaeD4SYfT0FRE3--08HfO2O31ss6NLtmQOkhBIo,31935
39
- amati/validators/oas311.py,sha256=BOI5T6liLfG0Qiw5Xq2QdH8NiUbD7-dgxpvFRCB9kHU,17312
40
- amati-0.2.21.dist-info/METADATA,sha256=w6-lJL-zsFItg6QF4vBNdQXOHf_GkRlTsEOjGAuI7N8,7073
41
- amati-0.2.21.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
42
- amati-0.2.21.dist-info/entry_points.txt,sha256=sacBb6g0f0ZJtNjNYx93_Xe4y5xzawvklCFVXup9ru0,37
43
- amati-0.2.21.dist-info/licenses/LICENSE,sha256=WAA01ZXeNs1bwpNWKR6aVucjtYjYm_iQIUYkCAENjqM,1070
44
- amati-0.2.21.dist-info/RECORD,,
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