amati 0.2.29__py3-none-any.whl → 0.3.1__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/_logging.py +4 -5
- amati/amati.py +54 -113
- {amati-0.2.29.dist-info → amati-0.3.1.dist-info}/METADATA +21 -27
- {amati-0.2.29.dist-info → amati-0.3.1.dist-info}/RECORD +7 -7
- {amati-0.2.29.dist-info → amati-0.3.1.dist-info}/WHEEL +0 -0
- {amati-0.2.29.dist-info → amati-0.3.1.dist-info}/entry_points.txt +0 -0
- {amati-0.2.29.dist-info → amati-0.3.1.dist-info}/licenses/LICENSE +0 -0
amati/_logging.py
CHANGED
|
@@ -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
|
|
27
|
-
|
|
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]] = []
|
amati/amati.py
CHANGED
|
@@ -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.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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.
|
|
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=
|
|
242
|
-
help="The specification to be
|
|
194
|
+
required=True,
|
|
195
|
+
help="The specification to be validated",
|
|
243
196
|
)
|
|
244
197
|
|
|
245
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
275
|
-
" is used",
|
|
218
|
+
" alongside <filename>.errors.json",
|
|
276
219
|
)
|
|
277
220
|
|
|
278
|
-
|
|
279
|
-
|
|
221
|
+
refreshment: argparse.ArgumentParser = subparsers.add_parser("refresh")
|
|
222
|
+
|
|
223
|
+
refreshment.add_argument(
|
|
224
|
+
"--type",
|
|
280
225
|
required=False,
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
logger.info(f"
|
|
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.")
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amati
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Validates that a .yaml or .json file conforms to the OpenAPI Specifications 3.x.
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
6
|
-
Project-URL: Issues, https://github.com/
|
|
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]
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
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
|
|
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
|
-
>>>
|
|
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
|
|
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/
|
|
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
|
|
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
|
[](https://scorecard.dev/viewer/?uri=github.com/gwyli/amati)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
amati/__init__.py,sha256=IXbWlVtFGTIdvxaafs8Qy8zeD3KjTDgK0omllFq8Jio,461
|
|
2
2
|
amati/_error_handler.py,sha256=_zs0hWqNf6NlXPk9-2MWyk6t5QGttDxNqatZ5YnCm6U,1245
|
|
3
|
-
amati/_logging.py,sha256=
|
|
3
|
+
amati/_logging.py,sha256=1q5mPlnZDSgAHl56lB20ZN6kV6OS-ew-8fvPeXF0uKI,1357
|
|
4
4
|
amati/_resolve_forward_references.py,sha256=-9MORpUhgusGJdvnH502p8-dc9Q6zqaB5XkPZvp4o7E,6509
|
|
5
|
-
amati/amati.py,sha256=
|
|
5
|
+
amati/amati.py,sha256=_oFo_Z9cuS7NdN7WZTynXlTDMzndXuT4ERznGWaNpdw,8155
|
|
6
6
|
amati/exceptions.py,sha256=MUzW7FsE0_4BJC99hStokMItKk7OvNBiqO6Zr6d_8cY,577
|
|
7
7
|
amati/file_handler.py,sha256=59kAsyVHE8mddRrNgyEowgZLEsgLcsZsFq6GtH7ZYBo,8065
|
|
8
8
|
amati/model_validators.py,sha256=py9QWEa68bE_tv9JrfnB2nJz16f4_Z2gLQAhH4O-CCs,14887
|
|
@@ -38,8 +38,8 @@ amati/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
|
38
38
|
amati/validators/generic.py,sha256=NM6ZkuGdM8SDvSUBbMcVz2KIYBWFH2lYTFMVpS1I5QM,5270
|
|
39
39
|
amati/validators/oas304.py,sha256=suDonNEMIzOmX_JfJFT1kYTT0dFFcSHsk8qOpf_x80A,31967
|
|
40
40
|
amati/validators/oas311.py,sha256=ey_j-6YR0rkYd6arVkeK1fDLsGBBoyWjjYBUy7Wj2xM,17325
|
|
41
|
-
amati-0.
|
|
42
|
-
amati-0.
|
|
43
|
-
amati-0.
|
|
44
|
-
amati-0.
|
|
45
|
-
amati-0.
|
|
41
|
+
amati-0.3.1.dist-info/METADATA,sha256=B5EM86jqsymAgnVDUMT-bPPrt6IKGeQbk75iQA1wxbI,6918
|
|
42
|
+
amati-0.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
43
|
+
amati-0.3.1.dist-info/entry_points.txt,sha256=sacBb6g0f0ZJtNjNYx93_Xe4y5xzawvklCFVXup9ru0,37
|
|
44
|
+
amati-0.3.1.dist-info/licenses/LICENSE,sha256=WAA01ZXeNs1bwpNWKR6aVucjtYjYm_iQIUYkCAENjqM,1070
|
|
45
|
+
amati-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|