amati 0.2.29__tar.gz → 0.3.2__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 (99) hide show
  1. {amati-0.2.29 → amati-0.3.2}/.github/workflows/data-refresh.yaml +2 -2
  2. {amati-0.2.29 → amati-0.3.2}/PKG-INFO +21 -27
  3. {amati-0.2.29 → amati-0.3.2}/README.md +18 -24
  4. {amati-0.2.29 → amati-0.3.2}/amati/_data/files/spdx-licences.json +33 -0
  5. {amati-0.2.29 → amati-0.3.2}/amati/_logging.py +4 -5
  6. {amati-0.2.29 → amati-0.3.2}/amati/amati.py +54 -113
  7. {amati-0.2.29 → amati-0.3.2}/bin/checks.sh +1 -1
  8. {amati-0.2.29 → amati-0.3.2}/pyproject.toml +3 -3
  9. amati-0.3.2/tests/data/invalid-openapi.yaml +26 -0
  10. amati-0.3.2/tests/test_amati.py +87 -0
  11. {amati-0.2.29 → amati-0.3.2}/uv.lock +26 -26
  12. amati-0.2.29/tests/test_amati.py +0 -51
  13. {amati-0.2.29 → amati-0.3.2}/.dockerignore +0 -0
  14. {amati-0.2.29 → amati-0.3.2}/.github/actions/setup/action.yaml +0 -0
  15. {amati-0.2.29 → amati-0.3.2}/.github/dependabot.yml +0 -0
  16. {amati-0.2.29 → amati-0.3.2}/.github/workflows/checks.yaml +0 -0
  17. {amati-0.2.29 → amati-0.3.2}/.github/workflows/codeql.yml +0 -0
  18. {amati-0.2.29 → amati-0.3.2}/.github/workflows/coverage.yaml +0 -0
  19. {amati-0.2.29 → amati-0.3.2}/.github/workflows/dependency-review.yml +0 -0
  20. {amati-0.2.29 → amati-0.3.2}/.github/workflows/publish.yaml +0 -0
  21. {amati-0.2.29 → amati-0.3.2}/.github/workflows/scorecards.yml +0 -0
  22. {amati-0.2.29 → amati-0.3.2}/.github/workflows/tag-and-create-release.yaml +0 -0
  23. {amati-0.2.29 → amati-0.3.2}/.gitignore +0 -0
  24. {amati-0.2.29 → amati-0.3.2}/.pre-commit-config.yaml +0 -0
  25. {amati-0.2.29 → amati-0.3.2}/.python-version +0 -0
  26. {amati-0.2.29 → amati-0.3.2}/Dockerfile +0 -0
  27. {amati-0.2.29 → amati-0.3.2}/LICENSE +0 -0
  28. {amati-0.2.29 → amati-0.3.2}/SECURITY.md +0 -0
  29. {amati-0.2.29 → amati-0.3.2}/TEMPLATE.html +0 -0
  30. {amati-0.2.29 → amati-0.3.2}/amati/__init__.py +0 -0
  31. {amati-0.2.29 → amati-0.3.2}/amati/_data/files/http-status-codes.json +0 -0
  32. {amati-0.2.29 → amati-0.3.2}/amati/_data/files/iso9110.json +0 -0
  33. {amati-0.2.29 → amati-0.3.2}/amati/_data/files/media-types.json +0 -0
  34. {amati-0.2.29 → amati-0.3.2}/amati/_data/files/schemes.json +0 -0
  35. {amati-0.2.29 → amati-0.3.2}/amati/_data/files/tlds.json +0 -0
  36. {amati-0.2.29 → amati-0.3.2}/amati/_data/http_status_code.py +0 -0
  37. {amati-0.2.29 → amati-0.3.2}/amati/_data/iso9110.py +0 -0
  38. {amati-0.2.29 → amati-0.3.2}/amati/_data/media_types.py +0 -0
  39. {amati-0.2.29 → amati-0.3.2}/amati/_data/refresh.py +0 -0
  40. {amati-0.2.29 → amati-0.3.2}/amati/_data/schemes.py +0 -0
  41. {amati-0.2.29 → amati-0.3.2}/amati/_data/spdx_licences.py +0 -0
  42. {amati-0.2.29 → amati-0.3.2}/amati/_data/tlds.py +0 -0
  43. {amati-0.2.29 → amati-0.3.2}/amati/_error_handler.py +0 -0
  44. {amati-0.2.29 → amati-0.3.2}/amati/_resolve_forward_references.py +0 -0
  45. {amati-0.2.29 → amati-0.3.2}/amati/exceptions.py +0 -0
  46. {amati-0.2.29 → amati-0.3.2}/amati/fields/__init__.py +0 -0
  47. {amati-0.2.29 → amati-0.3.2}/amati/fields/_custom_types.py +0 -0
  48. {amati-0.2.29 → amati-0.3.2}/amati/fields/commonmark.py +0 -0
  49. {amati-0.2.29 → amati-0.3.2}/amati/fields/email.py +0 -0
  50. {amati-0.2.29 → amati-0.3.2}/amati/fields/http_status_codes.py +0 -0
  51. {amati-0.2.29 → amati-0.3.2}/amati/fields/iso9110.py +0 -0
  52. {amati-0.2.29 → amati-0.3.2}/amati/fields/json.py +0 -0
  53. {amati-0.2.29 → amati-0.3.2}/amati/fields/media.py +0 -0
  54. {amati-0.2.29 → amati-0.3.2}/amati/fields/oas.py +0 -0
  55. {amati-0.2.29 → amati-0.3.2}/amati/fields/spdx_licences.py +0 -0
  56. {amati-0.2.29 → amati-0.3.2}/amati/fields/uri.py +0 -0
  57. {amati-0.2.29 → amati-0.3.2}/amati/file_handler.py +0 -0
  58. {amati-0.2.29 → amati-0.3.2}/amati/grammars/oas.py +0 -0
  59. {amati-0.2.29 → amati-0.3.2}/amati/grammars/rfc6901.py +0 -0
  60. {amati-0.2.29 → amati-0.3.2}/amati/grammars/rfc7159.py +0 -0
  61. {amati-0.2.29 → amati-0.3.2}/amati/model_validators.py +0 -0
  62. {amati-0.2.29 → amati-0.3.2}/amati/py.typed +0 -0
  63. {amati-0.2.29 → amati-0.3.2}/amati/validators/__init__.py +0 -0
  64. {amati-0.2.29 → amati-0.3.2}/amati/validators/generic.py +0 -0
  65. {amati-0.2.29 → amati-0.3.2}/amati/validators/oas304.py +0 -0
  66. {amati-0.2.29 → amati-0.3.2}/amati/validators/oas311.py +0 -0
  67. {amati-0.2.29 → amati-0.3.2}/bin/startup.sh +0 -0
  68. {amati-0.2.29 → amati-0.3.2}/bin/upgrade-python.sh +0 -0
  69. {amati-0.2.29 → amati-0.3.2}/bin/uv-upgrade-from-main.sh +0 -0
  70. {amati-0.2.29 → amati-0.3.2}/scripts/setup_test_specs.py +0 -0
  71. {amati-0.2.29 → amati-0.3.2}/tests/__init__.py +0 -0
  72. {amati-0.2.29 → amati-0.3.2}/tests/data/.amati.tests.yaml +0 -0
  73. {amati-0.2.29 → amati-0.3.2}/tests/data/DigitalOcean-public.v2.errors.json +0 -0
  74. {amati-0.2.29 → amati-0.3.2}/tests/data/api.github.com.yaml.errors.json +0 -0
  75. {amati-0.2.29 → amati-0.3.2}/tests/data/discourse.yml.errors.json +0 -0
  76. {amati-0.2.29 → amati-0.3.2}/tests/data/next-api.github.com.yaml.errors.json +0 -0
  77. {amati-0.2.29 → amati-0.3.2}/tests/data/openapi.yaml +0 -0
  78. {amati-0.2.29 → amati-0.3.2}/tests/data/openapi.yaml.gz +0 -0
  79. {amati-0.2.29 → amati-0.3.2}/tests/data/redocly.openapi.yaml.errors.json +0 -0
  80. {amati-0.2.29 → amati-0.3.2}/tests/fields/__init__.py +0 -0
  81. {amati-0.2.29 → amati-0.3.2}/tests/fields/test_email.py +0 -0
  82. {amati-0.2.29 → amati-0.3.2}/tests/fields/test_http_status_codes.py +0 -0
  83. {amati-0.2.29 → amati-0.3.2}/tests/fields/test_iso9110.py +0 -0
  84. {amati-0.2.29 → amati-0.3.2}/tests/fields/test_media.py +0 -0
  85. {amati-0.2.29 → amati-0.3.2}/tests/fields/test_oas.py +0 -0
  86. {amati-0.2.29 → amati-0.3.2}/tests/fields/test_spdx_licences.py +0 -0
  87. {amati-0.2.29 → amati-0.3.2}/tests/fields/test_uri.py +0 -0
  88. {amati-0.2.29 → amati-0.3.2}/tests/helpers.py +0 -0
  89. {amati-0.2.29 → amati-0.3.2}/tests/model_validators/test_all_of.py +0 -0
  90. {amati-0.2.29 → amati-0.3.2}/tests/model_validators/test_at_least_one.py +0 -0
  91. {amati-0.2.29 → amati-0.3.2}/tests/model_validators/test_if_then.py +0 -0
  92. {amati-0.2.29 → amati-0.3.2}/tests/model_validators/test_only_one.py +0 -0
  93. {amati-0.2.29 → amati-0.3.2}/tests/test_external_specs.py +0 -0
  94. {amati-0.2.29 → amati-0.3.2}/tests/test_logging.py +0 -0
  95. {amati-0.2.29 → amati-0.3.2}/tests/validators/__init__.py +0 -0
  96. {amati-0.2.29 → amati-0.3.2}/tests/validators/test_generic.py +0 -0
  97. {amati-0.2.29 → amati-0.3.2}/tests/validators/test_licence_object.py +0 -0
  98. {amati-0.2.29 → amati-0.3.2}/tests/validators/test_security_scheme_object.py +0 -0
  99. {amati-0.2.29 → amati-0.3.2}/tests/validators/test_server_variable_object.py +0 -0
@@ -51,7 +51,7 @@ jobs:
51
51
  run: |
52
52
  # Capture both stdout and exit code
53
53
  set +e
54
- OUTPUT=$(uv run python amati/amati.py --refresh-data 2>&1)
54
+ OUTPUT=$(uv run python amati/amati.py refresh 2>&1)
55
55
  EXIT_CODE=$?
56
56
  set -e
57
57
 
@@ -119,7 +119,7 @@ jobs:
119
119
  This PR contains automated data refresh changes generated by the weekly data refresh workflow.
120
120
 
121
121
  ### Changes
122
- - Data refreshed using \`python amati/amati.py --refresh-data\`
122
+ - Data refreshed using \`python amati/amati.py refresh\`
123
123
  - Files updated in \`amati/_data/files/\` directory
124
124
 
125
125
  ### Details
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amati
3
- Version: 0.2.29
3
+ Version: 0.3.2
4
4
  Summary: Validates that a .yaml or .json file conforms to the OpenAPI Specifications 3.x.
5
- Project-URL: Homepage, https://github.com/gwyil/amati
6
- Project-URL: Issues, https://github.com/gwyil/amati/issues
5
+ Project-URL: Homepage, https://github.com/gwyli/amati
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
9
  Classifier: Development Status :: 3 - Alpha
@@ -42,42 +42,33 @@ amati is designed to validate that a file conforms to the [OpenAPI Specification
42
42
  ## Usage
43
43
 
44
44
  ```sh
45
- python amati/amati.py --help
46
- usage: amati [-h] [-s SPEC] [--consistency-check] [-d DISCOVER] [--local] [--html-report]
47
-
48
- Tests whether a OpenAPI specification is valid. Will look an openapi.json or openapi.yaml file in the directory that
49
- amati is called from. If --discover is set will search the directory tree. If the specification does not follow the
50
- naming recommendation the --spec switch should be used. Creates a file <filename>.errors.json alongside the original
51
- specification containing a JSON representation of all the errors.
45
+ python amati/amati.py validate --help
46
+ usage: amati validate [-h] -s SPEC [--consistency-check] [--local] [--html-report]
52
47
 
53
48
  options:
54
49
  -h, --help show this help message and exit
55
- -s, --spec SPEC The specification to be parsed
50
+ -s, --spec SPEC The specification to be validated
56
51
  --consistency-check Runs a consistency check between the input specification and the parsed specification
57
- -d, --discover DISCOVER
58
- Searches the specified directory tree for openapi.yaml or openapi.json.
59
- --local Store errors local to the caller in a file called <file-name>.errors.json; a .amati/ directory
60
- will be created.
61
- --html-report Creates an HTML report of the errors, called <file-name>.errors.html, alongside the original
62
- file or in a .amati/ directory if the --local switch is used
52
+ --local Store errors local to the caller in .amati/<file-name>.errors.json
53
+ --html-report Creates an HTML report of the errors, called <file-name>.errors.html, alongside <filename>.errors.json
63
54
  ```
64
55
 
65
56
  ### Docker
66
57
 
67
58
  A Dockerfile is available on [DockerHub](https://hub.docker.com/r/benale/amati/tags) or `docker pull benale/amati:alpha`.
68
59
 
69
- Whilst an alpha build only the image tagged `alpha` will be maintained. If there are breaking API changes these will be detailed in releases going forward. Releases can be separately watched using the custom option when watching this repository.
60
+ Whilst an alpha build, only the image tagged `alpha` will be maintained. If there are breaking API changes these will be detailed in releases. Releases can be separately watched using the custom option when watching this repository.
70
61
 
71
62
  To run against a specific specification the location of the specification needs to be mounted in the container.
72
63
 
73
64
  ```sh
74
- docker run -v "<path-to-specification>:/<mount-name> benale/amati:alpha <options>
65
+ docker run -v "<path-to-specification>:/<mount-name> benale/amati:alpha validate --spec <path-to-spec> <options>
75
66
  ```
76
67
 
77
68
  e.g. where you have a specification located in `/Users/myuser/myrepo/myspec.yaml` and create a mount `/data`:
78
69
 
79
70
  ```sh
80
- docker run -v /Users/myuser/myrepo:/data benale/amati:alpha --spec /data/myspec.yaml --html-report
71
+ docker run -v /Users/myuser/myrepo:/data benale/amati:alpha validate --spec /data/myspec.yaml --html-report
81
72
  ```
82
73
 
83
74
  ### PyPI
@@ -86,14 +77,13 @@ amati is [available on PyPI](https://pypi.org/project/amati/), to run everything
86
77
 
87
78
  ```py
88
79
  >>> from amati import amati
89
- >>> check = amati.run('tests/data/openapi.yaml', consistency_check=True, local=True, html_report=True)
90
- >>> check
80
+ >>> amati.run('tests/data/openapi.yaml', consistency_check=True, local=True, html_report=True)
91
81
  True
92
82
  ```
93
83
 
94
84
  ## Architecture
95
85
 
96
- amati uses Pydantic, especially the validation, and Typing to construct the entire OAS as a single data type. Passing a dictionary to the top-level data type runs all the validation in the Pydantic models constructing a single set of inherited classes and datatypes that validate that the API specification is accurate. To the extent that Pydantic is functional, amati has a [functional core and an imperative shell](https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell).
86
+ amati uses [Pydantic](https://docs.pydantic.dev/latest/), especially the validation, and [typing](https://docs.python.org/3/library/typing.html) to construct the entire OAS as a single data type. Passing a dictionary to the top-level data type runs all the validation in the Pydantic models constructing a single set of inherited classes and datatypes that validate that the API specification is accurate. To the extent that Pydantic is functional, amati has a [functional core and an imperative shell](https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell).
97
87
 
98
88
  Where the specification conforms, but relies on implementation-defined behavior (e.g. [data type formats](https://spec.openapis.org/oas/v3.1.1.html#data-type-format)), a warning will be raised.
99
89
 
@@ -130,7 +120,7 @@ It's expected that there are no errors and 100% of the code is reached and execu
130
120
  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>`.
131
121
 
132
122
  ```sh
133
- python scripts/tests/setup_test_specs.py
123
+ python scripts/setup_test_specs.py
134
124
  ```
135
125
 
136
126
  If there are some issues with the specification a JSON file detailing those should be placed into `tests/data/` and the name of that file noted in `tests/data/.amati.tests.yaml` for the test suite to pick it up and check that the errors are expected. Any specifications that close the coverage gap are gratefully received.
@@ -152,18 +142,22 @@ docker build -t amati -f Dockerfile .
152
142
  to run against a specific specification the location of the specification needs to be mounted in the container.
153
143
 
154
144
  ```sh
155
- docker run -v "<path-to-specification>:/<mount-name> amati <options>
145
+ docker run -v "<path-to-specification>:/<mount-name> amati validate -s <path-to-spec> <options>
156
146
  ```
157
147
 
158
148
  This can be tested against a provided specification, from the root directory
159
149
 
160
150
  ```sh
161
- docker run --detach -v "$(pwd):/data" amati <options>
151
+ docker run --detach -v "$(pwd):/data" amati validate -s <path-to-spec> <options>
162
152
  ```
163
153
 
164
154
 
165
155
  ### Data
166
156
 
167
- There are some scripts to create the data needed by the project, for example, all the registered TLDs. To refresh the data, run the contents of `/scripts/data`.
157
+ There are some scripts to create the data needed by the project, for example, all the registered TLDs. To refresh the data, run:
158
+
159
+ ```py
160
+ python amati/amati.py refresh
161
+ ```
168
162
 
169
163
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/gwyli/amati/badge)](https://scorecard.dev/viewer/?uri=github.com/gwyli/amati)
@@ -9,42 +9,33 @@ amati is designed to validate that a file conforms to the [OpenAPI Specification
9
9
  ## Usage
10
10
 
11
11
  ```sh
12
- python amati/amati.py --help
13
- usage: amati [-h] [-s SPEC] [--consistency-check] [-d DISCOVER] [--local] [--html-report]
14
-
15
- Tests whether a OpenAPI specification is valid. Will look an openapi.json or openapi.yaml file in the directory that
16
- amati is called from. If --discover is set will search the directory tree. If the specification does not follow the
17
- naming recommendation the --spec switch should be used. Creates a file <filename>.errors.json alongside the original
18
- specification containing a JSON representation of all the errors.
12
+ python amati/amati.py validate --help
13
+ usage: amati validate [-h] -s SPEC [--consistency-check] [--local] [--html-report]
19
14
 
20
15
  options:
21
16
  -h, --help show this help message and exit
22
- -s, --spec SPEC The specification to be parsed
17
+ -s, --spec SPEC The specification to be validated
23
18
  --consistency-check Runs a consistency check between the input specification and the parsed specification
24
- -d, --discover DISCOVER
25
- Searches the specified directory tree for openapi.yaml or openapi.json.
26
- --local Store errors local to the caller in a file called <file-name>.errors.json; a .amati/ directory
27
- will be created.
28
- --html-report Creates an HTML report of the errors, called <file-name>.errors.html, alongside the original
29
- file or in a .amati/ directory if the --local switch is used
19
+ --local Store errors local to the caller in .amati/<file-name>.errors.json
20
+ --html-report Creates an HTML report of the errors, called <file-name>.errors.html, alongside <filename>.errors.json
30
21
  ```
31
22
 
32
23
  ### Docker
33
24
 
34
25
  A Dockerfile is available on [DockerHub](https://hub.docker.com/r/benale/amati/tags) or `docker pull benale/amati:alpha`.
35
26
 
36
- Whilst an alpha build only the image tagged `alpha` will be maintained. If there are breaking API changes these will be detailed in releases going forward. Releases can be separately watched using the custom option when watching this repository.
27
+ Whilst an alpha build, only the image tagged `alpha` will be maintained. If there are breaking API changes these will be detailed in releases. Releases can be separately watched using the custom option when watching this repository.
37
28
 
38
29
  To run against a specific specification the location of the specification needs to be mounted in the container.
39
30
 
40
31
  ```sh
41
- docker run -v "<path-to-specification>:/<mount-name> benale/amati:alpha <options>
32
+ docker run -v "<path-to-specification>:/<mount-name> benale/amati:alpha validate --spec <path-to-spec> <options>
42
33
  ```
43
34
 
44
35
  e.g. where you have a specification located in `/Users/myuser/myrepo/myspec.yaml` and create a mount `/data`:
45
36
 
46
37
  ```sh
47
- docker run -v /Users/myuser/myrepo:/data benale/amati:alpha --spec /data/myspec.yaml --html-report
38
+ docker run -v /Users/myuser/myrepo:/data benale/amati:alpha validate --spec /data/myspec.yaml --html-report
48
39
  ```
49
40
 
50
41
  ### PyPI
@@ -53,14 +44,13 @@ amati is [available on PyPI](https://pypi.org/project/amati/), to run everything
53
44
 
54
45
  ```py
55
46
  >>> from amati import amati
56
- >>> check = amati.run('tests/data/openapi.yaml', consistency_check=True, local=True, html_report=True)
57
- >>> check
47
+ >>> amati.run('tests/data/openapi.yaml', consistency_check=True, local=True, html_report=True)
58
48
  True
59
49
  ```
60
50
 
61
51
  ## Architecture
62
52
 
63
- amati uses Pydantic, especially the validation, and Typing to construct the entire OAS as a single data type. Passing a dictionary to the top-level data type runs all the validation in the Pydantic models constructing a single set of inherited classes and datatypes that validate that the API specification is accurate. To the extent that Pydantic is functional, amati has a [functional core and an imperative shell](https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell).
53
+ amati uses [Pydantic](https://docs.pydantic.dev/latest/), especially the validation, and [typing](https://docs.python.org/3/library/typing.html) to construct the entire OAS as a single data type. Passing a dictionary to the top-level data type runs all the validation in the Pydantic models constructing a single set of inherited classes and datatypes that validate that the API specification is accurate. To the extent that Pydantic is functional, amati has a [functional core and an imperative shell](https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell).
64
54
 
65
55
  Where the specification conforms, but relies on implementation-defined behavior (e.g. [data type formats](https://spec.openapis.org/oas/v3.1.1.html#data-type-format)), a warning will be raised.
66
56
 
@@ -97,7 +87,7 @@ It's expected that there are no errors and 100% of the code is reached and execu
97
87
  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>`.
98
88
 
99
89
  ```sh
100
- python scripts/tests/setup_test_specs.py
90
+ python scripts/setup_test_specs.py
101
91
  ```
102
92
 
103
93
  If there are some issues with the specification a JSON file detailing those should be placed into `tests/data/` and the name of that file noted in `tests/data/.amati.tests.yaml` for the test suite to pick it up and check that the errors are expected. Any specifications that close the coverage gap are gratefully received.
@@ -119,18 +109,22 @@ docker build -t amati -f Dockerfile .
119
109
  to run against a specific specification the location of the specification needs to be mounted in the container.
120
110
 
121
111
  ```sh
122
- docker run -v "<path-to-specification>:/<mount-name> amati <options>
112
+ docker run -v "<path-to-specification>:/<mount-name> amati validate -s <path-to-spec> <options>
123
113
  ```
124
114
 
125
115
  This can be tested against a provided specification, from the root directory
126
116
 
127
117
  ```sh
128
- docker run --detach -v "$(pwd):/data" amati <options>
118
+ docker run --detach -v "$(pwd):/data" amati validate -s <path-to-spec> <options>
129
119
  ```
130
120
 
131
121
 
132
122
  ### Data
133
123
 
134
- There are some scripts to create the data needed by the project, for example, all the registered TLDs. To refresh the data, run the contents of `/scripts/data`.
124
+ There are some scripts to create the data needed by the project, for example, all the registered TLDs. To refresh the data, run:
125
+
126
+ ```py
127
+ python amati/amati.py refresh
128
+ ```
135
129
 
136
130
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/gwyli/amati/badge)](https://scorecard.dev/viewer/?uri=github.com/gwyli/amati)
@@ -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,
@@ -10,7 +10,7 @@ from typing import Any, ClassVar, NotRequired, TypedDict
10
10
  type LogType = Exception | Warning
11
11
 
12
12
 
13
- @dataclass
13
+ @dataclass(frozen=True)
14
14
  class Log(TypedDict):
15
15
  type: str
16
16
  loc: NotRequired[tuple[int | str, ...]]
@@ -20,11 +20,10 @@ class Log(TypedDict):
20
20
 
21
21
 
22
22
  class Logger:
23
- """
24
- A mixin class that provides logging functionality.
23
+ """A simple class-level logger for collecting Log objects.
25
24
 
26
- This class maintains a list of Log messages that are added.
27
- It is NOT thread-safe. State is maintained at a global level.
25
+ This class provides methods for appending logs and managing
26
+ a logging context that automatically clears the logs.
28
27
  """
29
28
 
30
29
  logs: ClassVar[list[Log]] = []
@@ -163,86 +163,39 @@ def run(
163
163
  return True
164
164
 
165
165
 
166
- def discover(spec: str, discover_dir: str = ".") -> list[Path]:
167
- """
168
- Finds OpenAPI Specification files to validate
169
-
170
- Args:
171
- spec: The path to a specific OpenAPI specification file.
172
- discover_dir: The directory to search through.
173
- Returns:
174
- A list of specifications to validate.
175
- """
176
-
177
- specs: list[Path] = []
178
-
179
- # If a spec is provided, check if it exists and erorr if not
180
- if spec:
181
- spec_path = Path(spec)
182
-
183
- if not spec_path.exists():
184
- raise FileNotFoundError(f"File {spec} does not exist.")
185
-
186
- if not spec_path.is_file():
187
- raise IsADirectoryError(f"{spec} is a directory, not a file.")
188
-
189
- specs.append(spec_path)
190
-
191
- # End early if we're not also trying to find files
192
- if not discover_dir:
193
- return specs
194
-
195
- if Path("openapi.json").exists():
196
- specs.append(Path("openapi.json"))
197
-
198
- if Path("openapi.yaml").exists():
199
- specs.append(Path("openapi.yaml"))
200
-
201
- if specs:
202
- return specs
203
-
204
- if discover_dir == ".":
205
- raise FileNotFoundError(
206
- "openapi.json or openapi.yaml can't be found, use --discover or --spec."
207
- )
208
-
209
- specs = specs + list(Path(discover_dir).glob("**/openapi.json"))
210
- specs = specs + list(Path(discover_dir).glob("**/openapi.yaml"))
211
-
212
- if not specs:
213
- raise FileNotFoundError(
214
- "openapi.json or openapi.yaml can't be found, use --spec."
215
- )
216
-
217
- return specs
218
-
219
-
220
166
  if __name__ == "__main__":
167
+ logger.remove() # Remove the default logger
168
+ # Add a new logger that outputs to stderr with a specific format
169
+ logger.add(sys.stderr, format="{time} | {level} | {message}")
170
+
221
171
  import argparse
222
172
 
223
173
  parser: argparse.ArgumentParser = argparse.ArgumentParser(
224
174
  prog="amati",
225
175
  description="""
226
- Tests whether a OpenAPI specification is valid. Will look an openapi.json
227
- or openapi.yaml file in the directory that amati is called from. If
228
- --discover is set will search the directory tree. If the specification
229
- does not follow the naming recommendation the --spec switch should be
230
- used.
231
-
232
- Creates a file <filename>.errors.json alongside the original specification
233
- containing a JSON representation of all the errors.
176
+ Tests whether a OpenAPI specification is valid. Creates a file
177
+ <filename>.errors.json alongside the original specification containing
178
+ a JSON representation of all the errors.
179
+
180
+ Optionally creates an HTML report of the errors, and performs an internal
181
+ consistency check to verify that the output of the validation is identical
182
+ to the input.
234
183
  """,
235
184
  suggest_on_error=True,
236
185
  )
237
186
 
238
- parser.add_argument(
187
+ subparsers: argparse.Action = parser.add_subparsers(required=True, dest="command")
188
+
189
+ validation: argparse.ArgumentParser = subparsers.add_parser("validate")
190
+
191
+ validation.add_argument(
239
192
  "-s",
240
193
  "--spec",
241
- required=False,
242
- help="The specification to be parsed",
194
+ required=True,
195
+ help="The specification to be validated",
243
196
  )
244
197
 
245
- parser.add_argument(
198
+ validation.add_argument(
246
199
  "--consistency-check",
247
200
  required=False,
248
201
  action="store_true",
@@ -250,48 +203,44 @@ if __name__ == "__main__":
250
203
  " parsed specification",
251
204
  )
252
205
 
253
- parser.add_argument(
254
- "-d",
255
- "--discover",
256
- required=False,
257
- default=".",
258
- help="Searches the specified directory tree for openapi.yaml or openapi.json.",
259
- )
260
-
261
- parser.add_argument(
206
+ validation.add_argument(
262
207
  "--local",
263
208
  required=False,
264
209
  action="store_true",
265
- help="Store errors local to the caller in a file called <file-name>.errors.json"
266
- "; a .amati/ directory will be created.",
210
+ help="Store errors local to the caller in .amati/<file-name>.errors.json",
267
211
  )
268
212
 
269
- parser.add_argument(
213
+ validation.add_argument(
270
214
  "--html-report",
271
215
  required=False,
272
216
  action="store_true",
273
217
  help="Creates an HTML report of the errors, called <file-name>.errors.html,"
274
- " alongside the original file or in a .amati/ directory if the --local switch"
275
- " is used",
218
+ " alongside <filename>.errors.json",
276
219
  )
277
220
 
278
- parser.add_argument(
279
- "--refresh-data",
221
+ refreshment: argparse.ArgumentParser = subparsers.add_parser("refresh")
222
+
223
+ refreshment.add_argument(
224
+ "--type",
280
225
  required=False,
281
- action="store_true",
282
- help="Refreshes the local data files used by amati, such as HTTP status codes "
283
- "or , media types from IANA",
226
+ default="all",
227
+ choices=[
228
+ "all",
229
+ "http_status_code",
230
+ "iso9110",
231
+ "media_types",
232
+ "schemes",
233
+ "spdx_licences",
234
+ "tlds",
235
+ ],
236
+ help="The type of data to refresh. Defaults to all.",
284
237
  )
285
238
 
286
239
  args: argparse.Namespace = parser.parse_args()
287
240
 
288
- logger.remove() # Remove the default logger
289
- # Add a new logger that outputs to stderr with a specific format
290
- logger.add(sys.stderr, format="{time} | {level} | {message}")
291
-
292
241
  logger.info("Starting amati")
293
242
 
294
- if args.refresh_data:
243
+ if args.command == "refresh":
295
244
  logger.info("Refreshing data.")
296
245
  try:
297
246
  refresh("all")
@@ -301,32 +250,24 @@ if __name__ == "__main__":
301
250
  logger.error(f"Error refreshing data: {str(e)}")
302
251
  sys.exit(1)
303
252
 
253
+ specification: Path = Path(args.spec)
254
+ logger.info(f"Processing specification {specification}")
255
+
256
+ # Top-level try/except to ensure one failed spec doesn't stop the rest
257
+ # from being processed.
258
+ e: Exception
304
259
  try:
305
- specifications: list[Path] = discover(args.spec, args.discover)
260
+ successful_check: bool = run(
261
+ specification, args.consistency_check, args.local, args.html_report
262
+ )
263
+ logger.info(f"Specification {specification} processed successfully.")
306
264
  except Exception as e:
307
- logger.error(str(e))
265
+ logger.error(f"Error processing {specification}, {str(e)}")
308
266
  sys.exit(1)
309
267
 
310
- specification: Path
311
- for specification in specifications:
312
- successful_check: bool = False
313
- logger.info(f"Processing specification {specification}")
314
-
315
- # Top-level try/except to ensure one failed spec doesn't stop the rest
316
- # from being processed.
317
- e: Exception
318
- try:
319
- successful_check = run(
320
- specification, args.consistency_check, args.local, args.html_report
321
- )
322
- logger.info(f"Specification {specification} processed successfully.")
323
- except Exception as e:
324
- logger.error(f"Error processing {specification}, {str(e)}")
325
- sys.exit(1)
326
-
327
- if args.consistency_check and successful_check:
328
- logger.info(f"Consistency check successful for {specification}")
329
- elif args.consistency_check:
330
- logger.info(f"Consistency check failed for {specification}")
268
+ if args.consistency_check and successful_check:
269
+ logger.info(f"Consistency check successful for {specification}")
270
+ elif args.consistency_check:
271
+ logger.info(f"Consistency check failed for {specification}")
331
272
 
332
273
  logger.info("Stopping amati.")
@@ -7,5 +7,5 @@ pytest --doctest-modules amati/
7
7
  pyright --verifytypes amati --ignoreexternal
8
8
  docker build -t amati -f Dockerfile .
9
9
  cd .amati/test-specs/swagger/src/main/resources/ || exit
10
- docker run -v "$(pwd):/data" amati -d /data --consistency-check
10
+ docker run -v "$(pwd):/data" amati validate -s /data/openapi.yaml --consistency-check
11
11
  cd - || exit
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "amati"
3
- version = "0.2.29"
3
+ version = "0.3.2"
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 = [
@@ -37,8 +37,8 @@ dependencies = [
37
37
  ]
38
38
 
39
39
  [project.urls]
40
- Homepage = "https://github.com/gwyil/amati"
41
- Issues = "https://github.com/gwyil/amati/issues"
40
+ Homepage = "https://github.com/gwyli/amati"
41
+ Issues = "https://github.com/gwyli/amati/issues"
42
42
 
43
43
  [project.scripts]
44
44
  amati = "amati:main"
@@ -0,0 +1,26 @@
1
+ # Minimal example of an OpenAPI spec for use in tests/test_amati.py
2
+ openapi: 3.1.1
3
+ info:
4
+ title: Example API
5
+ termsOfService: https://example.com/terms
6
+ contact:
7
+ email: support@example.com
8
+ license:
9
+ name: MIT
10
+ identifier: MIT2
11
+ version: 1.0.0
12
+ servers:
13
+ - url: https://api.example.com/v1
14
+ description: Production server
15
+ - url: https://api.{environment}.example.com/v1/users/{userId}
16
+ description: Test servers
17
+ variables:
18
+ environment:
19
+ default: staging
20
+ enum:
21
+ - staging
22
+ - development
23
+ description: The environment to use
24
+ userId:
25
+ default: "1"
26
+ description: The user ID
@@ -0,0 +1,87 @@
1
+ """
2
+ Tests amati/amati.py, especially the args.
3
+ """
4
+
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+
9
+ def test_specifc_spec():
10
+ subprocess.run(
11
+ [
12
+ "python",
13
+ "amati/amati.py",
14
+ "validate",
15
+ "-s",
16
+ "tests/data/openapi.yaml",
17
+ "--consistency-check",
18
+ ],
19
+ check=True,
20
+ )
21
+
22
+
23
+ def test_gzip():
24
+ subprocess.run(
25
+ [
26
+ "python",
27
+ "amati/amati.py",
28
+ "validate",
29
+ "-s",
30
+ "tests/data/openapi.yaml.gz",
31
+ "--consistency-check",
32
+ ],
33
+ check=True,
34
+ )
35
+
36
+
37
+ def test_errors_created_local():
38
+ error_file: Path = Path(".amati/invalid-openapi.yaml.errors.json")
39
+ html_file: Path = Path(".amati/invalid-openapi.yaml.errors.html")
40
+
41
+ if error_file.exists():
42
+ error_file.unlink()
43
+ if html_file.exists():
44
+ html_file.unlink()
45
+
46
+ subprocess.run(
47
+ [
48
+ "python",
49
+ "amati/amati.py",
50
+ "validate",
51
+ "-s",
52
+ "tests/data/invalid-openapi.yaml",
53
+ "--local",
54
+ "--html-report",
55
+ ],
56
+ check=True,
57
+ )
58
+ assert error_file.exists()
59
+ assert html_file.exists()
60
+ error_file.unlink()
61
+ html_file.unlink()
62
+
63
+
64
+ def test_errors_created():
65
+ error_file: Path = Path("tests/data/invalid-openapi.yaml.errors.json")
66
+ html_file: Path = Path("tests/data/invalid-openapi.yaml.errors.html")
67
+
68
+ if error_file.exists():
69
+ error_file.unlink()
70
+ if html_file.exists():
71
+ html_file.unlink()
72
+
73
+ subprocess.run(
74
+ [
75
+ "python",
76
+ "amati/amati.py",
77
+ "validate",
78
+ "-s",
79
+ "tests/data/invalid-openapi.yaml",
80
+ "--html-report",
81
+ ],
82
+ check=True,
83
+ )
84
+ assert error_file.exists()
85
+ assert html_file.exists()
86
+ error_file.unlink()
87
+ html_file.unlink()
@@ -13,7 +13,7 @@ wheels = [
13
13
 
14
14
  [[package]]
15
15
  name = "amati"
16
- version = "0.2.29"
16
+ version = "0.3.2"
17
17
  source = { editable = "." }
18
18
  dependencies = [
19
19
  { name = "abnf" },
@@ -362,7 +362,7 @@ wheels = [
362
362
 
363
363
  [[package]]
364
364
  name = "pydantic"
365
- version = "2.12.1"
365
+ version = "2.12.2"
366
366
  source = { registry = "https://pypi.org/simple/" }
367
367
  dependencies = [
368
368
  { name = "annotated-types" },
@@ -370,39 +370,39 @@ dependencies = [
370
370
  { name = "typing-extensions" },
371
371
  { name = "typing-inspection" },
372
372
  ]
373
- sdist = { url = "https://files.pythonhosted.org/packages/3c/a7/d0d7b3c128948ece6676a6a21b9036e3ca53765d35052dbcc8c303886a44/pydantic-2.12.1.tar.gz", hash = "sha256:0af849d00e1879199babd468ec9db13b956f6608e9250500c1a9d69b6a62824e", size = 815997, upload-time = "2025-10-13T21:00:41.219Z" }
373
+ sdist = { url = "https://files.pythonhosted.org/packages/8d/35/d319ed522433215526689bad428a94058b6dd12190ce7ddd78618ac14b28/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd", size = 816358, upload-time = "2025-10-14T15:02:21.842Z" }
374
374
  wheels = [
375
- { url = "https://files.pythonhosted.org/packages/f5/69/ce4e60e5e67aa0c339a5dc3391a02b4036545efb6308c54dc4aa9425386f/pydantic-2.12.1-py3-none-any.whl", hash = "sha256:665931f5b4ab40c411439e66f99060d631d1acc58c3d481957b9123343d674d1", size = 460511, upload-time = "2025-10-13T21:00:38.935Z" },
375
+ { url = "https://files.pythonhosted.org/packages/6c/98/468cb649f208a6f1279448e6e5247b37ae79cf5e4041186f1e2ef3d16345/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae", size = 460628, upload-time = "2025-10-14T15:02:19.623Z" },
376
376
  ]
377
377
 
378
378
  [[package]]
379
379
  name = "pydantic-core"
380
- version = "2.41.3"
380
+ version = "2.41.4"
381
381
  source = { registry = "https://pypi.org/simple/" }
382
382
  dependencies = [
383
383
  { name = "typing-extensions" },
384
384
  ]
385
- sdist = { url = "https://files.pythonhosted.org/packages/00/e9/3916abb671bffb00845408c604ff03480dc8dc273310d8268547a37be0fb/pydantic_core-2.41.3.tar.gz", hash = "sha256:cdebb34b36ad05e8d77b4e797ad38a2a775c2a07a8fa386d4f6943b7778dcd39", size = 457489, upload-time = "2025-10-13T19:34:51.666Z" }
386
- wheels = [
387
- { url = "https://files.pythonhosted.org/packages/f1/d8/db32fbced75853c1d8e7ada8cb2b837ade99b2f281de569908de3e29f0bf/pydantic_core-2.41.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0c77e8e72344e34052ea26905fa7551ecb75fc12795ca1a8e44f816918f4c718", size = 2103383, upload-time = "2025-10-13T19:32:37.522Z" },
388
- { url = "https://files.pythonhosted.org/packages/de/28/5bcb3327b3777994633f4cb459c5dc34a9cbe6cf0ac449d3e8f1e74bdaaa/pydantic_core-2.41.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32be442a017e82a6c496a52ef5db5f5ac9abf31c3064f5240ee15a1d27cc599e", size = 1904974, upload-time = "2025-10-13T19:32:39.513Z" },
389
- { url = "https://files.pythonhosted.org/packages/71/8d/c9d8cad7c02d63869079fb6fb61b8ab27adbeeda0bf130c684fe43daa126/pydantic_core-2.41.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af10c78f0e9086d2d883ddd5a6482a613ad435eb5739cf1467b1f86169e63d91", size = 1956879, upload-time = "2025-10-13T19:32:41.849Z" },
390
- { url = "https://files.pythonhosted.org/packages/15/b1/8a84b55631a45375a467df288d8f905bec0abadb1e75bce3b32402b49733/pydantic_core-2.41.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6212874118704e27d177acee5b90b83556b14b2eb88aae01bae51cd9efe27019", size = 2051787, upload-time = "2025-10-13T19:32:43.86Z" },
391
- { url = "https://files.pythonhosted.org/packages/c3/97/a84ea9cb7ba4dbfd43865e5dd536b22c78ee763d82d501c6f6a553403c00/pydantic_core-2.41.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6a24c82674a3a8e7f7306e57e98219e5c1cdfc0f57bc70986930dda136230b2", size = 2217830, upload-time = "2025-10-13T19:32:46.053Z" },
392
- { url = "https://files.pythonhosted.org/packages/1a/2c/64233c77410e314dbb7f2e8112be7f56de57cf64198a32d8ab3f7b74adf4/pydantic_core-2.41.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e0c81dc047c18059410c959a437540abcefea6a882d6e43b9bf45c291eaacd9", size = 2341131, upload-time = "2025-10-13T19:32:48.402Z" },
393
- { url = "https://files.pythonhosted.org/packages/23/3d/915b90eb0de93bd522b293fd1a986289f5d576c72e640f3bb426b496d095/pydantic_core-2.41.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0d7e1a9f80f00a8180b9194ecef66958eb03f3c3ae2d77195c9d665ac0a61e", size = 2063797, upload-time = "2025-10-13T19:32:50.458Z" },
394
- { url = "https://files.pythonhosted.org/packages/4d/25/a65665caa86e496e19feef48e6bd9263c1a46f222e8f9b0818f67bd98dc3/pydantic_core-2.41.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2868fabfc35ec0738539ce0d79aab37aeffdcb9682b9b91f0ac4b0ba31abb1eb", size = 2193041, upload-time = "2025-10-13T19:32:52.686Z" },
395
- { url = "https://files.pythonhosted.org/packages/cd/46/a7f7e17f99ee691a7d93a53aa41bf7d1b1d425945b6e9bc8020498a413e1/pydantic_core-2.41.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:cb4f40c93307e1c50996e4edcddf338e1f3f1fb86fb69b654111c6050ae3b081", size = 2136119, upload-time = "2025-10-13T19:32:54.737Z" },
396
- { url = "https://files.pythonhosted.org/packages/5f/92/c27c1f3edd06e04af71358aa8f4d244c8bc6726e3fb47e00157d3dffe66f/pydantic_core-2.41.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:287cbcd3407a875eaf0b1efa2e5288493d5b79bfd3629459cf0b329ad8a9071a", size = 2317223, upload-time = "2025-10-13T19:32:56.927Z" },
397
- { url = "https://files.pythonhosted.org/packages/51/6c/20aabe3c32888fb13d4726e405716fed14b1d4d1d4292d585862c1458b7b/pydantic_core-2.41.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:5253835aa145049205a67056884555a936f9b3fea7c3ce860bff62be6a1ae4d1", size = 2320425, upload-time = "2025-10-13T19:32:59.454Z" },
398
- { url = "https://files.pythonhosted.org/packages/67/d2/476d4bc6b3070e151ae920167f27f26415e12f8fcc6cf5a47a613aba7267/pydantic_core-2.41.3-cp314-cp314-win32.whl", hash = "sha256:69297795efe5349156d18eebea818b75d29a1d3d1d5f26a250f22ab4220aacd6", size = 1994216, upload-time = "2025-10-13T19:33:01.484Z" },
399
- { url = "https://files.pythonhosted.org/packages/16/ca/2cd8515584b3d665ca3c4d946364c2a9932d0d5648694c2a10d273cde81c/pydantic_core-2.41.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1c133e3447c2f6d95e47ede58fff0053370758112a1d39117d0af8c93584049", size = 2026522, upload-time = "2025-10-13T19:33:03.546Z" },
400
- { url = "https://files.pythonhosted.org/packages/77/61/c9f2791d7188594f0abdc1b7fe8ec3efc123ee2d9c553fd3b6da2d9fd53d/pydantic_core-2.41.3-cp314-cp314-win_arm64.whl", hash = "sha256:54534eecbb7a331521f832e15fc307296f491ee1918dacfd4d5b900da6ee3332", size = 1969070, upload-time = "2025-10-13T19:33:05.604Z" },
401
- { url = "https://files.pythonhosted.org/packages/b5/eb/45f9a91f8c09f4cfb62f78dce909b20b6047ce4fd8d89310fcac5ad62e54/pydantic_core-2.41.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6b4be10152098b43c093a4b5e9e9da1ac7a1c954c1934d4438d07ba7b7bcf293", size = 1876593, upload-time = "2025-10-13T19:33:07.814Z" },
402
- { url = "https://files.pythonhosted.org/packages/99/f8/5c9d0959e0e1f260eea297a5ecc1dc29a14e03ee6a533e805407e8403c1a/pydantic_core-2.41.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe4ebd676c158a7994253161151b476dbbef2acbd2f547cfcfdf332cf67cc29", size = 1882977, upload-time = "2025-10-13T19:33:10.109Z" },
403
- { url = "https://files.pythonhosted.org/packages/8b/f4/7ab918e35f55e7beee471ba8c67dfc4c9c19a8904e4867bfda7f9c76a72e/pydantic_core-2.41.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:984ca0113b39dda1d7c358d6db03dd6539ef244d0558351806c1327239e035bf", size = 2041033, upload-time = "2025-10-13T19:33:12.216Z" },
404
- { url = "https://files.pythonhosted.org/packages/a6/c8/5b12e5a36410ebcd0082ae5b0258150d72762e306f298cc3fe731b5574ec/pydantic_core-2.41.3-cp314-cp314t-win_amd64.whl", hash = "sha256:2a7dd8a6f5a9a2f8c7f36e4fc0982a985dbc4ac7176ee3df9f63179b7295b626", size = 1994462, upload-time = "2025-10-13T19:33:14.421Z" },
405
- { url = "https://files.pythonhosted.org/packages/6b/f6/c6f3b7244a2a0524f4a04052e3d590d3be0ba82eb1a2f0fe5d068237701e/pydantic_core-2.41.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b387f08b378924fa82bd86e03c9d61d6daca1a73ffb3947bdcfe12ea14c41f68", size = 1973551, upload-time = "2025-10-13T19:33:16.87Z" },
385
+ sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" }
386
+ wheels = [
387
+ { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" },
388
+ { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" },
389
+ { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" },
390
+ { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" },
391
+ { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" },
392
+ { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" },
393
+ { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" },
394
+ { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" },
395
+ { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" },
396
+ { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" },
397
+ { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" },
398
+ { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" },
399
+ { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" },
400
+ { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" },
401
+ { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" },
402
+ { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" },
403
+ { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" },
404
+ { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" },
405
+ { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" },
406
406
  ]
407
407
 
408
408
  [[package]]
@@ -1,51 +0,0 @@
1
- """
2
- Tests amati/amati.py, especially the args.
3
- """
4
-
5
- import os
6
- import subprocess
7
-
8
- import pytest
9
-
10
-
11
- def test_specifc_spec():
12
- subprocess.run(
13
- [
14
- "python",
15
- "amati/amati.py",
16
- "-s",
17
- "tests/data/openapi.yaml",
18
- "--consistency-check",
19
- ],
20
- check=True,
21
- )
22
-
23
-
24
- def test_gzip():
25
- subprocess.run(
26
- [
27
- "python",
28
- "amati/amati.py",
29
- "-s",
30
- "tests/data/openapi.yaml.gz",
31
- "--consistency-check",
32
- ],
33
- check=True,
34
- )
35
-
36
-
37
- def test_discover_without_directory_failure():
38
- with pytest.raises(subprocess.CalledProcessError):
39
- subprocess.run(["python", "amati/amati.py", "--consistency-check"], check=True)
40
-
41
-
42
- def test_discover_without_directory_success():
43
- os.chdir("tests/data")
44
- subprocess.run(
45
- ["python", "../../amati/amati.py", "--consistency-check"], check=True
46
- )
47
- os.chdir("../../")
48
-
49
-
50
- def test_discover_with_directory():
51
- subprocess.run(["python", "amati/amati.py", "-d", "tests/data/"], check=True)
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
File without changes
File without changes