amati 0.3.1__tar.gz → 0.3.3__tar.gz

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.
Files changed (98) hide show
  1. {amati-0.3.1 → amati-0.3.3}/PKG-INFO +2 -1
  2. {amati-0.3.1 → amati-0.3.3}/amati/_data/files/spdx-licences.json +33 -0
  3. {amati-0.3.1 → amati-0.3.3}/amati/_data/media_types.py +1 -1
  4. {amati-0.3.1 → amati-0.3.3}/amati/_data/refresh.py +3 -1
  5. {amati-0.3.1 → amati-0.3.3}/amati/amati.py +12 -12
  6. {amati-0.3.1 → amati-0.3.3}/amati/fields/uri.py +4 -4
  7. {amati-0.3.1 → amati-0.3.3}/amati/file_handler.py +2 -2
  8. {amati-0.3.1 → amati-0.3.3}/amati/grammars/oas.py +3 -3
  9. {amati-0.3.1 → amati-0.3.3}/amati/model_validators.py +3 -5
  10. {amati-0.3.1 → amati-0.3.3}/amati/validators/generic.py +2 -7
  11. {amati-0.3.1 → amati-0.3.3}/amati/validators/oas304.py +4 -4
  12. {amati-0.3.1 → amati-0.3.3}/amati/validators/oas311.py +3 -3
  13. {amati-0.3.1 → amati-0.3.3}/pyproject.toml +39 -4
  14. {amati-0.3.1 → amati-0.3.3}/scripts/setup_test_specs.py +3 -2
  15. {amati-0.3.1 → amati-0.3.3}/tests/model_validators/test_all_of.py +1 -1
  16. {amati-0.3.1 → amati-0.3.3}/tests/model_validators/test_if_then.py +1 -1
  17. {amati-0.3.1 → amati-0.3.3}/tests/test_external_specs.py +4 -6
  18. {amati-0.3.1 → amati-0.3.3}/tests/validators/test_generic.py +4 -4
  19. {amati-0.3.1 → amati-0.3.3}/tests/validators/test_licence_object.py +2 -3
  20. {amati-0.3.1 → amati-0.3.3}/uv.lock +1 -1
  21. {amati-0.3.1 → amati-0.3.3}/.dockerignore +0 -0
  22. {amati-0.3.1 → amati-0.3.3}/.github/actions/setup/action.yaml +0 -0
  23. {amati-0.3.1 → amati-0.3.3}/.github/dependabot.yml +0 -0
  24. {amati-0.3.1 → amati-0.3.3}/.github/workflows/checks.yaml +0 -0
  25. {amati-0.3.1 → amati-0.3.3}/.github/workflows/codeql.yml +0 -0
  26. {amati-0.3.1 → amati-0.3.3}/.github/workflows/coverage.yaml +0 -0
  27. {amati-0.3.1 → amati-0.3.3}/.github/workflows/data-refresh.yaml +0 -0
  28. {amati-0.3.1 → amati-0.3.3}/.github/workflows/dependency-review.yml +0 -0
  29. {amati-0.3.1 → amati-0.3.3}/.github/workflows/publish.yaml +0 -0
  30. {amati-0.3.1 → amati-0.3.3}/.github/workflows/scorecards.yml +0 -0
  31. {amati-0.3.1 → amati-0.3.3}/.github/workflows/tag-and-create-release.yaml +0 -0
  32. {amati-0.3.1 → amati-0.3.3}/.gitignore +0 -0
  33. {amati-0.3.1 → amati-0.3.3}/.pre-commit-config.yaml +0 -0
  34. {amati-0.3.1 → amati-0.3.3}/.python-version +0 -0
  35. {amati-0.3.1 → amati-0.3.3}/Dockerfile +0 -0
  36. {amati-0.3.1 → amati-0.3.3}/LICENSE +0 -0
  37. {amati-0.3.1 → amati-0.3.3}/README.md +0 -0
  38. {amati-0.3.1 → amati-0.3.3}/SECURITY.md +0 -0
  39. {amati-0.3.1 → amati-0.3.3}/TEMPLATE.html +0 -0
  40. {amati-0.3.1 → amati-0.3.3}/amati/__init__.py +0 -0
  41. {amati-0.3.1 → amati-0.3.3}/amati/_data/files/http-status-codes.json +0 -0
  42. {amati-0.3.1 → amati-0.3.3}/amati/_data/files/iso9110.json +0 -0
  43. {amati-0.3.1 → amati-0.3.3}/amati/_data/files/media-types.json +0 -0
  44. {amati-0.3.1 → amati-0.3.3}/amati/_data/files/schemes.json +0 -0
  45. {amati-0.3.1 → amati-0.3.3}/amati/_data/files/tlds.json +0 -0
  46. {amati-0.3.1 → amati-0.3.3}/amati/_data/http_status_code.py +0 -0
  47. {amati-0.3.1 → amati-0.3.3}/amati/_data/iso9110.py +0 -0
  48. {amati-0.3.1 → amati-0.3.3}/amati/_data/schemes.py +0 -0
  49. {amati-0.3.1 → amati-0.3.3}/amati/_data/spdx_licences.py +0 -0
  50. {amati-0.3.1 → amati-0.3.3}/amati/_data/tlds.py +0 -0
  51. {amati-0.3.1 → amati-0.3.3}/amati/_error_handler.py +0 -0
  52. {amati-0.3.1 → amati-0.3.3}/amati/_logging.py +0 -0
  53. {amati-0.3.1 → amati-0.3.3}/amati/_resolve_forward_references.py +0 -0
  54. {amati-0.3.1 → amati-0.3.3}/amati/exceptions.py +0 -0
  55. {amati-0.3.1 → amati-0.3.3}/amati/fields/__init__.py +0 -0
  56. {amati-0.3.1 → amati-0.3.3}/amati/fields/_custom_types.py +0 -0
  57. {amati-0.3.1 → amati-0.3.3}/amati/fields/commonmark.py +0 -0
  58. {amati-0.3.1 → amati-0.3.3}/amati/fields/email.py +0 -0
  59. {amati-0.3.1 → amati-0.3.3}/amati/fields/http_status_codes.py +0 -0
  60. {amati-0.3.1 → amati-0.3.3}/amati/fields/iso9110.py +0 -0
  61. {amati-0.3.1 → amati-0.3.3}/amati/fields/json.py +0 -0
  62. {amati-0.3.1 → amati-0.3.3}/amati/fields/media.py +0 -0
  63. {amati-0.3.1 → amati-0.3.3}/amati/fields/oas.py +0 -0
  64. {amati-0.3.1 → amati-0.3.3}/amati/fields/spdx_licences.py +0 -0
  65. {amati-0.3.1 → amati-0.3.3}/amati/grammars/rfc6901.py +0 -0
  66. {amati-0.3.1 → amati-0.3.3}/amati/grammars/rfc7159.py +0 -0
  67. {amati-0.3.1 → amati-0.3.3}/amati/py.typed +0 -0
  68. {amati-0.3.1 → amati-0.3.3}/amati/validators/__init__.py +0 -0
  69. {amati-0.3.1 → amati-0.3.3}/bin/checks.sh +0 -0
  70. {amati-0.3.1 → amati-0.3.3}/bin/startup.sh +0 -0
  71. {amati-0.3.1 → amati-0.3.3}/bin/upgrade-python.sh +0 -0
  72. {amati-0.3.1 → amati-0.3.3}/bin/uv-upgrade-from-main.sh +0 -0
  73. {amati-0.3.1 → amati-0.3.3}/tests/__init__.py +0 -0
  74. {amati-0.3.1 → amati-0.3.3}/tests/data/.amati.tests.yaml +0 -0
  75. {amati-0.3.1 → amati-0.3.3}/tests/data/DigitalOcean-public.v2.errors.json +0 -0
  76. {amati-0.3.1 → amati-0.3.3}/tests/data/api.github.com.yaml.errors.json +0 -0
  77. {amati-0.3.1 → amati-0.3.3}/tests/data/discourse.yml.errors.json +0 -0
  78. {amati-0.3.1 → amati-0.3.3}/tests/data/invalid-openapi.yaml +0 -0
  79. {amati-0.3.1 → amati-0.3.3}/tests/data/next-api.github.com.yaml.errors.json +0 -0
  80. {amati-0.3.1 → amati-0.3.3}/tests/data/openapi.yaml +0 -0
  81. {amati-0.3.1 → amati-0.3.3}/tests/data/openapi.yaml.gz +0 -0
  82. {amati-0.3.1 → amati-0.3.3}/tests/data/redocly.openapi.yaml.errors.json +0 -0
  83. {amati-0.3.1 → amati-0.3.3}/tests/fields/__init__.py +0 -0
  84. {amati-0.3.1 → amati-0.3.3}/tests/fields/test_email.py +0 -0
  85. {amati-0.3.1 → amati-0.3.3}/tests/fields/test_http_status_codes.py +0 -0
  86. {amati-0.3.1 → amati-0.3.3}/tests/fields/test_iso9110.py +0 -0
  87. {amati-0.3.1 → amati-0.3.3}/tests/fields/test_media.py +0 -0
  88. {amati-0.3.1 → amati-0.3.3}/tests/fields/test_oas.py +0 -0
  89. {amati-0.3.1 → amati-0.3.3}/tests/fields/test_spdx_licences.py +0 -0
  90. {amati-0.3.1 → amati-0.3.3}/tests/fields/test_uri.py +0 -0
  91. {amati-0.3.1 → amati-0.3.3}/tests/helpers.py +0 -0
  92. {amati-0.3.1 → amati-0.3.3}/tests/model_validators/test_at_least_one.py +0 -0
  93. {amati-0.3.1 → amati-0.3.3}/tests/model_validators/test_only_one.py +0 -0
  94. {amati-0.3.1 → amati-0.3.3}/tests/test_amati.py +0 -0
  95. {amati-0.3.1 → amati-0.3.3}/tests/test_logging.py +0 -0
  96. {amati-0.3.1 → amati-0.3.3}/tests/validators/__init__.py +0 -0
  97. {amati-0.3.1 → amati-0.3.3}/tests/validators/test_security_scheme_object.py +0 -0
  98. {amati-0.3.1 → amati-0.3.3}/tests/validators/test_server_variable_object.py +0 -0
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amati
3
- Version: 0.3.1
3
+ Version: 0.3.3
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/gwyli/amati
6
6
  Project-URL: Issues, https://github.com/gwyli/amati/issues
7
7
  Author-email: Ben <2551337+ben-alexander@users.noreply.github.com>
8
8
  License-File: LICENSE
9
+ Keywords: json,openapi,openapi3,openapi3.0,openapi3.1,swagger,validation,validator,yaml
9
10
  Classifier: Development Status :: 3 - Alpha
10
11
  Classifier: Intended Audience :: Developers
11
12
  Classifier: License :: OSI Approved :: MIT License
@@ -2817,6 +2817,17 @@
2817
2817
  ],
2818
2818
  "isOsiApproved": false
2819
2819
  },
2820
+ {
2821
+ "reference": "https://spdx.org/licenses/ESA-PL-strong-copyleft-2.4.html",
2822
+ "isDeprecatedLicenseId": false,
2823
+ "detailsUrl": "https://spdx.org/licenses/ESA-PL-strong-copyleft-2.4.json",
2824
+ "name": "European Space Agency Public License (ESA-PL) - V2.4 - Strong Copyleft (Type 1)",
2825
+ "licenseId": "ESA-PL-strong-copyleft-2.4",
2826
+ "seeAlso": [
2827
+ "https://essr.esa.int/license/european-space-agency-public-license-v2-4-strong-copyleft-type-1"
2828
+ ],
2829
+ "isOsiApproved": false
2830
+ },
2820
2831
  {
2821
2832
  "reference": "https://spdx.org/licenses/ESA-PL-weak-copyleft-2.4.html",
2822
2833
  "isDeprecatedLicenseId": false,
@@ -6416,6 +6427,17 @@
6416
6427
  "isOsiApproved": true,
6417
6428
  "isFsfLibre": true
6418
6429
  },
6430
+ {
6431
+ "reference": "https://spdx.org/licenses/OSSP.html",
6432
+ "isDeprecatedLicenseId": false,
6433
+ "detailsUrl": "https://spdx.org/licenses/OSSP.json",
6434
+ "name": "OSSP License",
6435
+ "licenseId": "OSSP",
6436
+ "seeAlso": [
6437
+ "https://git.sr.ht/~nabijaczleweli/ossp-var"
6438
+ ],
6439
+ "isOsiApproved": false
6440
+ },
6419
6441
  {
6420
6442
  "reference": "https://spdx.org/licenses/PADL.html",
6421
6443
  "isDeprecatedLicenseId": false,
@@ -6934,6 +6956,17 @@
6934
6956
  ],
6935
6957
  "isOsiApproved": false
6936
6958
  },
6959
+ {
6960
+ "reference": "https://spdx.org/licenses/SGMLUG-PM.html",
6961
+ "isDeprecatedLicenseId": false,
6962
+ "detailsUrl": "https://spdx.org/licenses/SGMLUG-PM.json",
6963
+ "name": "SGMLUG Parser Materials License",
6964
+ "licenseId": "SGMLUG-PM",
6965
+ "seeAlso": [
6966
+ "https://gitweb.gentoo.org/repo/gentoo.git/tree/licenses/SGMLUG?id=7d999af4a47bf55e53e54713d98d145f935935c1"
6967
+ ],
6968
+ "isOsiApproved": false
6969
+ },
6937
6970
  {
6938
6971
  "reference": "https://spdx.org/licenses/SGP4.html",
6939
6972
  "isDeprecatedLicenseId": false,
@@ -8,7 +8,7 @@ DATA_FILE = Path("media-types.json")
8
8
  DATA_SOURCE: dict[str, str] = {
9
9
  "application": "https://www.iana.org/assignments/media-types/application.csv",
10
10
  "audio": "https://www.iana.org/assignments/media-types/audio.csv",
11
- # "example": "currently no valid values",
11
+ # "example": "currently no valid values", # noqa ERA001
12
12
  "font": "https://www.iana.org/assignments/media-types/font.csv",
13
13
  "haptics": "https://www.iana.org/assignments/media-types/haptics.csv",
14
14
  "image": "https://www.iana.org/assignments/media-types/image.csv",
@@ -157,7 +157,9 @@ def get(data_type: data_types) -> dict[str, JSONValue] | list[JSONValue] | None:
157
157
  for module_ in to_get:
158
158
  module = importlib.import_module(f"amati._data.{module_}")
159
159
 
160
- with open(current_path / module.DATA_FILE, encoding="utf-8") as f:
160
+ data_file: Path = current_path / module.DATA_FILE
161
+
162
+ with data_file.open(encoding="utf-8") as f:
161
163
  data = json.loads(f.read())
162
164
 
163
165
  if not data:
@@ -132,11 +132,11 @@ def run(
132
132
  if not error_path.exists():
133
133
  error_path.mkdir()
134
134
 
135
- with open(
136
- error_path / error_file.with_suffix(error_file.suffix + ".json"),
137
- "w",
138
- encoding="utf-8",
139
- ) as f:
135
+ json_error_file: Path = error_path / error_file.with_suffix(
136
+ error_file.suffix + ".json"
137
+ )
138
+
139
+ with json_error_file.open("w", encoding="utf-8") as f:
140
140
  f.write(json.dumps(handled_errors))
141
141
 
142
142
  if html_report:
@@ -150,11 +150,11 @@ def run(
150
150
  html_output = template.render(errors=handled_errors)
151
151
 
152
152
  # Save the output to a file
153
- with open(
154
- error_path / error_file.with_suffix(error_file.suffix + ".html"),
155
- "w",
156
- encoding="utf-8",
157
- ) as f:
153
+
154
+ html_error_file: Path = error_path / error_file.with_suffix(
155
+ error_file.suffix + ".html"
156
+ )
157
+ with html_error_file.open("w", encoding="utf-8") as f:
158
158
  f.write(html_output)
159
159
 
160
160
  if result and consistency_check:
@@ -247,7 +247,7 @@ if __name__ == "__main__":
247
247
  logger.info("Data refreshed successfully.")
248
248
  sys.exit(0)
249
249
  except Exception as e:
250
- logger.error(f"Error refreshing data: {str(e)}")
250
+ logger.error(f"Error refreshing data: {e}")
251
251
  sys.exit(1)
252
252
 
253
253
  specification: Path = Path(args.spec)
@@ -262,7 +262,7 @@ if __name__ == "__main__":
262
262
  )
263
263
  logger.info(f"Specification {specification} processed successfully.")
264
264
  except Exception as e:
265
- logger.error(f"Error processing {specification}, {str(e)}")
265
+ logger.error(f"Error processing {specification}, {e}")
266
266
  sys.exit(1)
267
267
 
268
268
  if args.consistency_check and successful_check:
@@ -3,7 +3,7 @@ Validates a URI according to the RFC3986 ABNF grammar
3
3
  """
4
4
 
5
5
  from enum import Enum
6
- from typing import Self, cast
6
+ from typing import ClassVar, Self, cast
7
7
 
8
8
  import idna
9
9
  from abnf import Node, ParseError, Rule
@@ -61,7 +61,7 @@ class Scheme(_Str):
61
61
 
62
62
  # Look up the scheme in the IANA registry to get status info
63
63
  # Returns None if the scheme is not in the registry
64
- self.status = SCHEMES.get(value, None)
64
+ self.status = SCHEMES.get(value)
65
65
 
66
66
 
67
67
  class URIType(str, Enum):
@@ -123,7 +123,7 @@ class URI(_Str):
123
123
  # RFC 3987 Internationalized Resource Identifier (IRI) flag
124
124
  is_iri: bool = False
125
125
 
126
- _attribute_map: dict[str, str] = {
126
+ _attribute_map: ClassVar[dict[str, str]] = {
127
127
  "authority": "authority",
128
128
  "iauthority": "authority",
129
129
  "host": "host",
@@ -176,7 +176,7 @@ class URI(_Str):
176
176
  # an additional return there is a code path in type() that doesn't return a
177
177
  # value. It's better to deal with the potential error case than ignore the
178
178
  # lack of a return value.
179
- raise TypeError(f"{str(self)} does not have a URI type.") # pragma: no cover
179
+ raise TypeError(f"{self!s} does not have a URI type.") # pragma: no cover
180
180
 
181
181
  def __init__(self, value: str):
182
182
  """
@@ -151,7 +151,7 @@ class FileProcessor:
151
151
  True if the file is gzip-compressed, False otherwise.
152
152
  """
153
153
  try:
154
- with open(file_path, "rb") as f:
154
+ with file_path.open("rb") as f:
155
155
  magic = f.read(2)
156
156
  return magic == b"\x1f\x8b"
157
157
  except OSError:
@@ -189,7 +189,7 @@ class FileProcessor:
189
189
  with gzip.open(file_path, "rt", encoding="utf-8") as f:
190
190
  return f.read()
191
191
  else:
192
- with open(file_path, encoding="utf-8") as f:
192
+ with file_path.open(encoding="utf-8") as f:
193
193
  return f.read()
194
194
 
195
195
  def _get_appropriate_loader(self, file_path: Path) -> FileLoader:
@@ -36,10 +36,10 @@ class Rule(_Rule):
36
36
  # reference-token = *( unescaped / escaped )
37
37
  # unescaped = %x00-2E / %x30-7D / %x7F-10FFFF
38
38
  # ; %x2F ('/') and %x7E ('~') are excluded from 'unescaped'
39
- # escaped = "~" ( "0" / "1" )
39
+ # escaped = "~" ( "0" / "1" ) # noqa: ERA001
40
40
  # ; representing '~' and '/', respectively
41
41
  "name = *( CHAR )",
42
- # token = 1*tchar,
43
- # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "."
42
+ # token = 1*tchar, # noqa: ERA001
43
+ # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." # noqa: ERA001 E501
44
44
  # / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
45
45
  ]
@@ -154,7 +154,7 @@ def at_least_one_of(
154
154
  if is_truthy_with_numeric_zero(value):
155
155
  return self
156
156
 
157
- public_fields = ", ".join(f"{name}" for name in candidates.keys())
157
+ public_fields = ", ".join(f"{name}" for name in candidates)
158
158
 
159
159
  msg = f"{public_fields} do not have values, expected at least one."
160
160
  Logger.log(
@@ -245,10 +245,8 @@ def only_one_of(
245
245
  truthy.append(name)
246
246
 
247
247
  if len(truthy) != 1:
248
- if truthy:
249
- field_string = ", ".join(truthy)
250
- else:
251
- field_string = "none"
248
+ field_string = ", ".join(truthy) if truthy else "none"
249
+
252
250
  msg = f"Expected at most one field to have a value, {field_string} did"
253
251
 
254
252
  Logger.log(
@@ -52,10 +52,7 @@ class GenericObject(BaseModel):
52
52
  # If extra fields aren't allowed log those that aren't going to be added
53
53
  # to the model.
54
54
  for field in data:
55
- if (
56
- field not in self.model_dump().keys()
57
- and field not in self.get_field_aliases()
58
- ):
55
+ if field not in self.model_dump() and field not in self.get_field_aliases():
59
56
  message = f"{field} is not a valid field for {self.__repr_name__()}."
60
57
  Logger.log(
61
58
  {
@@ -92,9 +89,7 @@ class GenericObject(BaseModel):
92
89
  excess_fields: set[str] = set()
93
90
 
94
91
  pattern: re.Pattern[str] = re.compile(self._extra_field_pattern)
95
- excess_fields.update(
96
- key for key in self.model_extra.keys() if not pattern.match(key)
97
- )
92
+ excess_fields.update(key for key in self.model_extra if not pattern.match(key))
98
93
 
99
94
  for field in excess_fields:
100
95
  message = f"{field} is not a valid field for {self.__repr_name__()}."
@@ -239,7 +239,7 @@ class PathsObject(GenericObject):
239
239
  Special-case specification extensions, which are also allowed.
240
240
  """
241
241
 
242
- for field in data.keys():
242
+ for field in data:
243
243
  # Specification extensions
244
244
  if field.startswith("x-"):
245
245
  continue
@@ -700,7 +700,7 @@ class SchemaObject(GenericObject):
700
700
  if isinstance(type_val, str) and type_val != "null":
701
701
  schema_dict["type"] = [type_val, "null"]
702
702
  elif isinstance(type_val, list) and "null" not in type_val:
703
- schema_dict["type"] = type_val + ["null"]
703
+ schema_dict["type"] = [*type_val, ["null"]]
704
704
 
705
705
  # 2. Validate the schema structure using jsonschema's meta-schema
706
706
  # Get the right validator based on the declared $schema or default
@@ -748,7 +748,7 @@ class OAuthFlowObject(GenericObject):
748
748
  authorizationUrl: URI | None = None
749
749
  tokenUrl: URI | None = None
750
750
  refreshUrl: URI | None = None
751
- scopes: dict[str, str] = {}
751
+ scopes: dict[str, str] = Field(default={})
752
752
  _reference_uri: ClassVar[str] = (
753
753
  "https://spec.openapis.org/oas/v3.0.4.html#oauth-flow-object"
754
754
  )
@@ -956,7 +956,7 @@ class ComponentsObject(GenericObject):
956
956
  f"Invalid type for '{field_name}': expected dict, got {type(value)}"
957
957
  )
958
958
 
959
- for key in value.keys():
959
+ for key in value:
960
960
  if not re.match(pattern, key):
961
961
  raise ValueError(
962
962
  f"Invalid key '{key}' in '{field_name}': must match pattern {pattern}" # noqa: E501
@@ -104,7 +104,7 @@ class LicenceObject(GenericObject):
104
104
  except AmatiValueError:
105
105
  Logger.log(
106
106
  {
107
- "msg": f"{str(self.url)} is not a valid SPDX URL",
107
+ "msg": f"{self.url} is not a valid SPDX URL",
108
108
  "type": "warning",
109
109
  "loc": (self.__class__.__name__,),
110
110
  "input": self.url,
@@ -376,7 +376,7 @@ class SchemaObject(GenericObject):
376
376
  if isinstance(type_val, str) and type_val != "null":
377
377
  schema_dict["type"] = [type_val, "null"]
378
378
  elif isinstance(type_val, list) and "null" not in type_val:
379
- schema_dict["type"] = type_val + ["null"]
379
+ schema_dict["type"] = [*type_val, ["null"]]
380
380
 
381
381
  # 2. Validate the schema structure using jsonschema's meta-schema
382
382
  # Get the right validator based on the declared $schema or default
@@ -484,7 +484,7 @@ class ComponentsObject(GenericObject):
484
484
  f"Invalid type for '{field_name}': expected dict, got {type(value)}"
485
485
  )
486
486
 
487
- for key in value.keys():
487
+ for key in value:
488
488
  if not re.match(pattern, key):
489
489
  raise ValueError(
490
490
  f"Invalid key '{key}' in '{field_name}': must match pattern {pattern}" # noqa: E501
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "amati"
3
- version = "0.3.1"
3
+ version = "0.3.3"
4
4
  description = "Validates that a .yaml or .json file conforms to the OpenAPI Specifications 3.x."
5
5
  readme = "README.md"
6
6
  authors = [
7
7
  { name = "Ben", email = "2551337+ben-alexander@users.noreply.github.com" }
8
8
  ]
9
9
  requires-python = ">=3.14"
10
+ keywords = ["openapi", "openapi3", "openapi3.0", "openapi3.1", "swagger", "validator", "validation", "yaml", "json"]
10
11
  classifiers = [
11
12
  "Development Status :: 3 - Alpha",
12
13
  "Intended Audience :: Developers",
@@ -92,13 +93,30 @@ line-length = 88
92
93
 
93
94
  [tool.ruff.lint]
94
95
  select = [
96
+ "A", # flake8-builtins
97
+ "ARG", # flake8-arguments
98
+ "B", # flake8-bugbear
99
+ "C4", # flake8-comprehensions
100
+ "C90", # McCabe complexity
101
+ "DTZ", # flake8-datetimez
95
102
  "E", # pycodestyle errors
96
- "W", # pycodestyle warnings
103
+ "ERA", # eradicate (commented out code)
97
104
  "F", # pyflakes
98
105
  "I", # isort
99
- "B", # flake8-bugbear
100
- "C4", # flake8-comprehensions
106
+ "ISC", # flake8-implicit-str-concat
107
+ "PL", # Pylint
108
+ "PLC", # Pylint - Convention
109
+ "PLE", # Pylint - Error
110
+ "PTH", # flake8-pathlib
111
+ "PLR", # Pylint - Refactor
112
+ "PLW", # Pylint - warnings
113
+ "RUF", # ruff-specific rules
114
+ "S", # flake8-bandit
115
+ "SIM", # flake8-simplify
116
+ "T20", # flake8-print
101
117
  "UP", # pyupgrade
118
+ "W", # pycodestyle warnings
119
+ "YTT", # flake8-2020
102
120
  ]
103
121
  extend-fixable = ["UP015"]
104
122
 
@@ -106,3 +124,20 @@ extend-fixable = ["UP015"]
106
124
  known-first-party=["amati"]
107
125
  known-third-party=["pytest","pydantic","yaml","abnf", "rfc3987"]
108
126
  extra-standard-library=["typing_extensions", "annotationlib", "compression"]
127
+
128
+ [tool.ruff.lint.per-file-ignores]
129
+ # Disable certain checks in tests
130
+ "tests/**/*.py" = [
131
+ "S101", # Allow assert
132
+ "S311", # Allow random choice
133
+ "S603", # Allow subprocess calls
134
+ "S607", # Allow partially executable paths
135
+ "RUF001", # Allow ambiguous unicode characters
136
+ ]
137
+
138
+ # Disable certain checks in internal script
139
+ "scripts/setup_test_specs.py" = [
140
+ "S108", # Allow use of temp files
141
+ "S603", # Allow subprocess calls
142
+ "S607", # Allow partially executable paths
143
+ ]
@@ -19,7 +19,7 @@ def guard():
19
19
  of the top-level directory for amati
20
20
  """
21
21
 
22
- if Path("pyproject.toml") not in Path(".").iterdir():
22
+ if Path("pyproject.toml") not in Path().iterdir():
23
23
  raise ValueError("setup_test_specs.py must be run in the top-level directory")
24
24
 
25
25
 
@@ -30,7 +30,8 @@ def get_repos() -> dict[str, Any]:
30
30
 
31
31
  guard()
32
32
 
33
- with open("tests/data/.amati.tests.yaml", encoding="utf-8") as f:
33
+ config: Path = Path("tests/data/.amati.tests.yaml")
34
+ with config.open(encoding="utf-8") as f:
34
35
  content = yaml.safe_load(f)
35
36
 
36
37
  return content
@@ -49,7 +49,7 @@ def test_empty_object():
49
49
  st.integers(min_value=MIN),
50
50
  st.lists(st.integers(min_value=MIN), min_size=1),
51
51
  )
52
- def test_all_of_no_restrictions(name: str, age: int, music: list[int]):
52
+ def test_all_of_no_restrictions(name: str, age: int, music: list[int]): # noqa: PLR0915
53
53
  """Test when at least one field is not empty. Uses both None and falsy values."""
54
54
 
55
55
  # Tests with None
@@ -290,7 +290,7 @@ def test_multiple_iterable_consequences_valid():
290
290
  )
291
291
  assert not Logger.logs
292
292
  assert model.status == "active"
293
- assert model.level == 2
293
+ assert model.level == 2 # noqa: PLR2004
294
294
 
295
295
 
296
296
  def test_multiple_iterable_consequences_partial_invalid():
@@ -16,8 +16,8 @@ def get_test_data() -> dict[str, Any]:
16
16
  """
17
17
  Gathers the set of test data.
18
18
  """
19
-
20
- with open("tests/data/.amati.tests.yaml", encoding="utf-8") as f:
19
+ config: Path = Path("tests/data/.amati.tests.yaml")
20
+ with config.open(encoding="utf-8") as f:
21
21
  content = yaml.safe_load(f)
22
22
 
23
23
  return content
@@ -29,7 +29,7 @@ def get_errors(error_file: Path) -> list[dict[str, Any]]:
29
29
  with a given test specification.
30
30
  """
31
31
 
32
- with open(error_file, encoding="utf-8") as f:
32
+ with error_file.open(encoding="utf-8") as f:
33
33
  expected_errors = json.loads(f.read())
34
34
 
35
35
  return expected_errors
@@ -66,8 +66,6 @@ def test_specs():
66
66
  file: Path = Path(directory) / name / repo["spec"]
67
67
  files = determine_file_names(file)
68
68
 
69
- print(f"Testing {file}")
70
-
71
69
  consistency_check = run(
72
70
  file_path=file, consistency_check=True, local=True, html_report=True
73
71
  )
@@ -75,7 +73,7 @@ def test_specs():
75
73
  if errors := repo.get("error_file"):
76
74
  error_file = get_errors(Path(errors))
77
75
 
78
- with open(files["error_json"], encoding="utf-8") as f:
76
+ with Path(files["error_json"]).open(encoding="utf-8") as f:
79
77
  json_encoded = json.loads(f.read())
80
78
 
81
79
  assert json.dumps(json_encoded, sort_keys=True) == json.dumps(
@@ -33,7 +33,7 @@ class ModelExtraPattern(GenericObject):
33
33
  st.data(),
34
34
  )
35
35
  def test_invalid_generic_object(data: dict[str, str], data_strategy: st.DataObject):
36
- if "value" not in data.keys():
36
+ if "value" not in data:
37
37
  data["value"] = data_strategy.draw(st.text())
38
38
 
39
39
  with Logger.context():
@@ -55,7 +55,7 @@ def test_valid_generic_object(data: dict[str, str]):
55
55
  st.data(),
56
56
  )
57
57
  def test_allow_extra_fields(data: dict[str, str], data_strategy: st.DataObject):
58
- if "value" not in data.keys():
58
+ if "value" not in data:
59
59
  data["value"] = data_strategy.draw(st.text())
60
60
 
61
61
  with Logger.context():
@@ -78,7 +78,7 @@ def text_matching_pattern(draw: st.DrawFn) -> dict[str, str]:
78
78
  def test_allow_extra_fields_with_pattern(
79
79
  data: dict[str, str], data_strategy: st.DataObject
80
80
  ):
81
- if "value" not in data.keys():
81
+ if "value" not in data:
82
82
  data["value"] = data_strategy.draw(st.text())
83
83
 
84
84
  with Logger.context():
@@ -90,7 +90,7 @@ def test_allow_extra_fields_with_pattern(
90
90
  def test_allow_extra_fields_with_pattern_and_extra(
91
91
  data: dict[str, str], data_strategy: st.DataObject
92
92
  ):
93
- if "value" not in data.keys():
93
+ if "value" not in data:
94
94
  data["value"] = data_strategy.draw(st.text())
95
95
 
96
96
  # Add another field not begining with 'x-'
@@ -145,13 +145,12 @@ def test_case_4_id_url_match(name: str, identifier: str):
145
145
  @given(text_excluding_empty_string(), st.sampled_from(VALID_IDENTIFIERS_WITH_URLS))
146
146
  def test_case_4_id_url_match_no(name: str, identifier: str):
147
147
  url = unassociated_url(identifier)
148
- print(url)
148
+
149
149
  with Logger.context():
150
150
  LicenceObject(name=name, identifier=identifier, url=url) # type: ignore
151
151
  assert Logger.logs[0]["msg"]
152
152
  assert Logger.logs[0]["type"] == "value_error"
153
- for i in Logger.logs:
154
- print(i)
153
+
155
154
  assert (
156
155
  Logger.logs[1]["msg"]
157
156
  == f"{url} is not associated with the identifier {identifier}"
@@ -13,7 +13,7 @@ wheels = [
13
13
 
14
14
  [[package]]
15
15
  name = "amati"
16
- version = "0.3.1"
16
+ version = "0.3.3"
17
17
  source = { editable = "." }
18
18
  dependencies = [
19
19
  { name = "abnf" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes