openapi3-parser 1.1.21__tar.gz → 1.2.0__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.
- openapi3_parser-1.2.0/PKG-INFO +189 -0
- {openapi3_parser-1.1.21 → openapi3_parser-1.2.0}/license.txt +1 -1
- openapi3_parser-1.2.0/pyproject.toml +82 -0
- openapi3_parser-1.2.0/readme.md +149 -0
- openapi3_parser-1.2.0/src/openapi_parser/__init__.py +5 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/__init__.py +1 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/common.py +101 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/content.py +74 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/encoding.py +57 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/external_doc.py +37 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/header.py +55 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/info.py +68 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/link.py +51 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/oauth_flow.py +51 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/operation.py +92 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/parameter.py +105 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/path.py +74 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/request.py +41 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/response.py +68 -0
- {openapi3_parser-1.1.21 → openapi3_parser-1.2.0}/src/openapi_parser/builders/schema.py +130 -70
- openapi3_parser-1.2.0/src/openapi_parser/builders/schemas.py +31 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/security.py +62 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/server.py +45 -0
- openapi3_parser-1.2.0/src/openapi_parser/builders/tag.py +50 -0
- openapi3_parser-1.2.0/src/openapi_parser/enumeration.py +181 -0
- openapi3_parser-1.2.0/src/openapi_parser/errors.py +10 -0
- {openapi3_parser-1.1.21 → openapi3_parser-1.2.0}/src/openapi_parser/loose_types.py +4 -0
- openapi3_parser-1.2.0/src/openapi_parser/parser.py +200 -0
- openapi3_parser-1.2.0/src/openapi_parser/resolver.py +53 -0
- openapi3_parser-1.2.0/src/openapi_parser/specification.py +376 -0
- openapi3_parser-1.1.21/PKG-INFO +0 -30
- openapi3_parser-1.1.21/setup.cfg +0 -29
- openapi3_parser-1.1.21/setup.py +0 -34
- openapi3_parser-1.1.21/src/openapi3_parser.egg-info/PKG-INFO +0 -30
- openapi3_parser-1.1.21/src/openapi3_parser.egg-info/SOURCES.txt +0 -37
- openapi3_parser-1.1.21/src/openapi3_parser.egg-info/dependency_links.txt +0 -1
- openapi3_parser-1.1.21/src/openapi3_parser.egg-info/requires.txt +0 -2
- openapi3_parser-1.1.21/src/openapi3_parser.egg-info/top_level.txt +0 -1
- openapi3_parser-1.1.21/src/openapi_parser/__init__.py +0 -11
- openapi3_parser-1.1.21/src/openapi_parser/builders/__init__.py +0 -0
- openapi3_parser-1.1.21/src/openapi_parser/builders/common.py +0 -80
- openapi3_parser-1.1.21/src/openapi_parser/builders/content.py +0 -39
- openapi3_parser-1.1.21/src/openapi_parser/builders/external_doc.py +0 -23
- openapi3_parser-1.1.21/src/openapi_parser/builders/header.py +0 -41
- openapi3_parser-1.1.21/src/openapi_parser/builders/info.py +0 -47
- openapi3_parser-1.1.21/src/openapi_parser/builders/oauth_flow.py +0 -39
- openapi3_parser-1.1.21/src/openapi_parser/builders/operation.py +0 -61
- openapi3_parser-1.1.21/src/openapi_parser/builders/parameter.py +0 -68
- openapi3_parser-1.1.21/src/openapi_parser/builders/path.py +0 -55
- openapi3_parser-1.1.21/src/openapi_parser/builders/request.py +0 -27
- openapi3_parser-1.1.21/src/openapi_parser/builders/response.py +0 -38
- openapi3_parser-1.1.21/src/openapi_parser/builders/schemas.py +0 -21
- openapi3_parser-1.1.21/src/openapi_parser/builders/security.py +0 -44
- openapi3_parser-1.1.21/src/openapi_parser/builders/server.py +0 -29
- openapi3_parser-1.1.21/src/openapi_parser/builders/tag.py +0 -30
- openapi3_parser-1.1.21/src/openapi_parser/enumeration.py +0 -148
- openapi3_parser-1.1.21/src/openapi_parser/errors.py +0 -6
- openapi3_parser-1.1.21/src/openapi_parser/parser.py +0 -146
- openapi3_parser-1.1.21/src/openapi_parser/resolver.py +0 -41
- openapi3_parser-1.1.21/src/openapi_parser/specification.py +0 -271
- openapi3_parser-1.1.21/tests/test_enumeration.py +0 -230
- openapi3_parser-1.1.21/tests/test_parser.py +0 -58
- openapi3_parser-1.1.21/tests/test_parser_options.py +0 -27
- openapi3_parser-1.1.21/tests/test_runner.py +0 -16
- {openapi3_parser-1.1.21 → openapi3_parser-1.2.0}/src/openapi_parser/py.typed +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: openapi3-parser
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: OpenAPI v3 parser
|
|
5
|
+
Keywords: swagger,python,swagger-parser,openapi3-parser,parser,openapi3,swagger-api
|
|
6
|
+
Author: Artem Manchenkov
|
|
7
|
+
Author-email: Artem Manchenkov <artem@manchenkoff.me>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2020 Artem Manchenkov
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
30
|
+
Classifier: Operating System :: OS Independent
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
33
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
34
|
+
Requires-Dist: openapi-spec-validator>=0.8.5
|
|
35
|
+
Requires-Dist: prance>=25.4.8.0
|
|
36
|
+
Requires-Python: >=3.10
|
|
37
|
+
Project-URL: homepage, https://github.com/manchenkoff/openapi3-parser
|
|
38
|
+
Project-URL: source, https://github.com/manchenkoff/openapi3-parser
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# OpenAPI Parser
|
|
42
|
+
|
|
43
|
+
[](https://pypi.org/project/openapi3-parser/)
|
|
44
|
+
[](https://clickpy.clickhouse.com/dashboard/openapi3-parser)
|
|
45
|
+
[](https://pypi.org/project/openapi3-parser/)
|
|
46
|
+
[](https://pypi.org/project/openapi3-parser/)
|
|
47
|
+
[](license.txt)
|
|
48
|
+
|
|
49
|
+
Parse OpenAPI 3 documents into fully typed Python dataclass objects.
|
|
50
|
+
Navigate your API specification programmatically — servers, paths,
|
|
51
|
+
operations, parameters, schemas, security schemes, and more.
|
|
52
|
+
|
|
53
|
+
| Version | Status |
|
|
54
|
+
| ------- | -------------- |
|
|
55
|
+
| 2.0 | Deprecated |
|
|
56
|
+
| 3.0 | **Supported** |
|
|
57
|
+
| 3.1 | In development |
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install openapi3-parser
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from openapi_parser import parse
|
|
69
|
+
|
|
70
|
+
specification = parse("swagger.yml")
|
|
71
|
+
print(specification.info.title) # e.g. "User example service"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Use Cases
|
|
75
|
+
|
|
76
|
+
### Parse from different sources
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
# From file path
|
|
80
|
+
spec = parse("specs/openapi.yml")
|
|
81
|
+
|
|
82
|
+
# From URL
|
|
83
|
+
spec = parse("https://example.com/openapi.json")
|
|
84
|
+
|
|
85
|
+
# From raw string
|
|
86
|
+
spec = parse(spec_string="""
|
|
87
|
+
openapi: "3.0.0"
|
|
88
|
+
info:
|
|
89
|
+
title: My API
|
|
90
|
+
version: "1.0.0"
|
|
91
|
+
paths: {}
|
|
92
|
+
""")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Navigate servers, paths, and operations
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
specification = parse("swagger.yml")
|
|
99
|
+
|
|
100
|
+
# List all servers
|
|
101
|
+
for server in specification.servers:
|
|
102
|
+
print(f"{server.description} - {server.url}")
|
|
103
|
+
|
|
104
|
+
# List all paths and their HTTP methods
|
|
105
|
+
for path in specification.paths:
|
|
106
|
+
methods = ", ".join(op.method.value for op in path.operations)
|
|
107
|
+
print(f"{path.url}: [{methods}]")
|
|
108
|
+
|
|
109
|
+
# Inspect operation details
|
|
110
|
+
for path in specification.paths:
|
|
111
|
+
for op in path.operations:
|
|
112
|
+
print(f"[{op.method.value}] {path.url}: {op.summary}")
|
|
113
|
+
if op.deprecated:
|
|
114
|
+
print(" (deprecated)")
|
|
115
|
+
if op.operation_id:
|
|
116
|
+
print(f" operationId: {op.operation_id}")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Enum strictness
|
|
120
|
+
|
|
121
|
+
By default, content types, string formats, and other enum fields are validated
|
|
122
|
+
against predefined enums. For specs that use custom values, pass
|
|
123
|
+
`strict_enum=False`:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
# Accepts non-standard content types like "application/vnd.api+json"
|
|
127
|
+
spec = parse("swagger.yml", strict_enum=False)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
When strict mode is off, unrecognized values are wrapped in a `LooseEnum`
|
|
131
|
+
object instead of raising an error.
|
|
132
|
+
|
|
133
|
+
### Error Handling
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from openapi_parser.errors import ParserError
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
spec = parse("invalid.yml")
|
|
140
|
+
except ParserError as e:
|
|
141
|
+
print(f"Parsing failed: {e}")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Data Model
|
|
145
|
+
|
|
146
|
+
Parsed documents return a `Specification` object composed of fully typed
|
|
147
|
+
dataclasses:
|
|
148
|
+
|
|
149
|
+
| Model | Description |
|
|
150
|
+
| --------------- | ----------- |
|
|
151
|
+
| `Specification` | Root document — version, info, servers, paths, schemas, security |
|
|
152
|
+
| `Info` | API metadata — title, version, description, contact, license |
|
|
153
|
+
| `Server` | Server definition — url, description, variables |
|
|
154
|
+
| `Path` | URL path — operations, parameters |
|
|
155
|
+
| `Operation` | HTTP method — responses, parameters, request body, security |
|
|
156
|
+
| `Parameter` | Path/query/header/cookie param — schema, style, required |
|
|
157
|
+
| `Response` | Status code, description, content, headers |
|
|
158
|
+
| `RequestBody` | Content, description, required |
|
|
159
|
+
| `Content` | Media type, schema, example |
|
|
160
|
+
| `Schema` | Base type — Integer, Number, String, Boolean, Array, Object, Null |
|
|
161
|
+
| `Property` | Object property — name, schema |
|
|
162
|
+
| `OneOf`/`AnyOf` | Composition schemas with discriminator support |
|
|
163
|
+
| `Security` | Security scheme — apiKey, http, oauth2, openIdConnect |
|
|
164
|
+
| `OAuthFlow` | OAuth flow — authorization, token, scopes |
|
|
165
|
+
| `Header` | Response header — name, schema, description |
|
|
166
|
+
| `Tag` | Tag with optional external docs |
|
|
167
|
+
| `ExternalDoc` | External documentation reference |
|
|
168
|
+
| `Discriminator` | Polymorphism discriminator — property name, mapping |
|
|
169
|
+
|
|
170
|
+
See the [specification module](src/openapi_parser/specification.py) for
|
|
171
|
+
all available fields and types.
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Install with dev dependencies
|
|
177
|
+
uv sync --dev
|
|
178
|
+
|
|
179
|
+
# Lint
|
|
180
|
+
uv run ruff check .
|
|
181
|
+
uv run mypy .
|
|
182
|
+
uv run ty check .
|
|
183
|
+
|
|
184
|
+
# Test
|
|
185
|
+
uv run pytest
|
|
186
|
+
|
|
187
|
+
# Format
|
|
188
|
+
uv run ruff format .
|
|
189
|
+
```
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "openapi3-parser"
|
|
3
|
+
version = "1.2.0"
|
|
4
|
+
description = "OpenAPI v3 parser"
|
|
5
|
+
readme = "readme.md"
|
|
6
|
+
license = { file = "license.txt" }
|
|
7
|
+
authors = [{ name = "Artem Manchenkov", email = "artem@manchenkoff.me" }]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
keywords = [
|
|
10
|
+
"swagger",
|
|
11
|
+
"python",
|
|
12
|
+
"swagger-parser",
|
|
13
|
+
"openapi3-parser",
|
|
14
|
+
"parser",
|
|
15
|
+
"openapi3",
|
|
16
|
+
"swagger-api",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Topic :: Software Development :: Libraries",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"openapi-spec-validator>=0.8.5",
|
|
27
|
+
"prance>=25.4.8.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
homepage = "https://github.com/manchenkoff/openapi3-parser"
|
|
32
|
+
source = "https://github.com/manchenkoff/openapi3-parser"
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = ["mypy>=2.1.0", "pytest>=9.0.3", "pytest-cov>=6.1.0", "ruff>=0.15.13", "ty>=0.0.37"]
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["uv_build>=0.11.12,<0.12"]
|
|
39
|
+
build-backend = "uv_build"
|
|
40
|
+
|
|
41
|
+
[tool.uv.build-backend]
|
|
42
|
+
module-name = "openapi_parser"
|
|
43
|
+
|
|
44
|
+
[[tool.uv.index]]
|
|
45
|
+
name = "testpypi"
|
|
46
|
+
url = "https://test.pypi.org/simple/"
|
|
47
|
+
publish-url = "https://test.pypi.org/legacy/"
|
|
48
|
+
explicit = true
|
|
49
|
+
|
|
50
|
+
[tool.mypy]
|
|
51
|
+
python_version = "3.10"
|
|
52
|
+
strict = true
|
|
53
|
+
mypy_path = ["src"]
|
|
54
|
+
|
|
55
|
+
[[tool.mypy.overrides]]
|
|
56
|
+
module = "tests.*"
|
|
57
|
+
|
|
58
|
+
[[tool.mypy.overrides]]
|
|
59
|
+
module = "prance"
|
|
60
|
+
ignore_missing_imports = true
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint]
|
|
63
|
+
extend-select = [
|
|
64
|
+
"D", # pydocstyle
|
|
65
|
+
"UP", # pyupgrade
|
|
66
|
+
"B", # flake8-bugbear
|
|
67
|
+
"SIM", # flake8-simplify
|
|
68
|
+
"I", # isort
|
|
69
|
+
"ARG", # unused arguments
|
|
70
|
+
"RUF100", # unused noqa
|
|
71
|
+
"TID252", # ban relative imports
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint.pydocstyle]
|
|
75
|
+
convention = "google"
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint.per-file-ignores]
|
|
78
|
+
"tests/**" = ["D", "ARG"]
|
|
79
|
+
|
|
80
|
+
[tool.coverage.run]
|
|
81
|
+
source = ["openapi_parser"]
|
|
82
|
+
omit = ["tests/*"]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# OpenAPI Parser
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/openapi3-parser/)
|
|
4
|
+
[](https://clickpy.clickhouse.com/dashboard/openapi3-parser)
|
|
5
|
+
[](https://pypi.org/project/openapi3-parser/)
|
|
6
|
+
[](https://pypi.org/project/openapi3-parser/)
|
|
7
|
+
[](license.txt)
|
|
8
|
+
|
|
9
|
+
Parse OpenAPI 3 documents into fully typed Python dataclass objects.
|
|
10
|
+
Navigate your API specification programmatically — servers, paths,
|
|
11
|
+
operations, parameters, schemas, security schemes, and more.
|
|
12
|
+
|
|
13
|
+
| Version | Status |
|
|
14
|
+
| ------- | -------------- |
|
|
15
|
+
| 2.0 | Deprecated |
|
|
16
|
+
| 3.0 | **Supported** |
|
|
17
|
+
| 3.1 | In development |
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install openapi3-parser
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from openapi_parser import parse
|
|
29
|
+
|
|
30
|
+
specification = parse("swagger.yml")
|
|
31
|
+
print(specification.info.title) # e.g. "User example service"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Use Cases
|
|
35
|
+
|
|
36
|
+
### Parse from different sources
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# From file path
|
|
40
|
+
spec = parse("specs/openapi.yml")
|
|
41
|
+
|
|
42
|
+
# From URL
|
|
43
|
+
spec = parse("https://example.com/openapi.json")
|
|
44
|
+
|
|
45
|
+
# From raw string
|
|
46
|
+
spec = parse(spec_string="""
|
|
47
|
+
openapi: "3.0.0"
|
|
48
|
+
info:
|
|
49
|
+
title: My API
|
|
50
|
+
version: "1.0.0"
|
|
51
|
+
paths: {}
|
|
52
|
+
""")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Navigate servers, paths, and operations
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
specification = parse("swagger.yml")
|
|
59
|
+
|
|
60
|
+
# List all servers
|
|
61
|
+
for server in specification.servers:
|
|
62
|
+
print(f"{server.description} - {server.url}")
|
|
63
|
+
|
|
64
|
+
# List all paths and their HTTP methods
|
|
65
|
+
for path in specification.paths:
|
|
66
|
+
methods = ", ".join(op.method.value for op in path.operations)
|
|
67
|
+
print(f"{path.url}: [{methods}]")
|
|
68
|
+
|
|
69
|
+
# Inspect operation details
|
|
70
|
+
for path in specification.paths:
|
|
71
|
+
for op in path.operations:
|
|
72
|
+
print(f"[{op.method.value}] {path.url}: {op.summary}")
|
|
73
|
+
if op.deprecated:
|
|
74
|
+
print(" (deprecated)")
|
|
75
|
+
if op.operation_id:
|
|
76
|
+
print(f" operationId: {op.operation_id}")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Enum strictness
|
|
80
|
+
|
|
81
|
+
By default, content types, string formats, and other enum fields are validated
|
|
82
|
+
against predefined enums. For specs that use custom values, pass
|
|
83
|
+
`strict_enum=False`:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# Accepts non-standard content types like "application/vnd.api+json"
|
|
87
|
+
spec = parse("swagger.yml", strict_enum=False)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
When strict mode is off, unrecognized values are wrapped in a `LooseEnum`
|
|
91
|
+
object instead of raising an error.
|
|
92
|
+
|
|
93
|
+
### Error Handling
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from openapi_parser.errors import ParserError
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
spec = parse("invalid.yml")
|
|
100
|
+
except ParserError as e:
|
|
101
|
+
print(f"Parsing failed: {e}")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Data Model
|
|
105
|
+
|
|
106
|
+
Parsed documents return a `Specification` object composed of fully typed
|
|
107
|
+
dataclasses:
|
|
108
|
+
|
|
109
|
+
| Model | Description |
|
|
110
|
+
| --------------- | ----------- |
|
|
111
|
+
| `Specification` | Root document — version, info, servers, paths, schemas, security |
|
|
112
|
+
| `Info` | API metadata — title, version, description, contact, license |
|
|
113
|
+
| `Server` | Server definition — url, description, variables |
|
|
114
|
+
| `Path` | URL path — operations, parameters |
|
|
115
|
+
| `Operation` | HTTP method — responses, parameters, request body, security |
|
|
116
|
+
| `Parameter` | Path/query/header/cookie param — schema, style, required |
|
|
117
|
+
| `Response` | Status code, description, content, headers |
|
|
118
|
+
| `RequestBody` | Content, description, required |
|
|
119
|
+
| `Content` | Media type, schema, example |
|
|
120
|
+
| `Schema` | Base type — Integer, Number, String, Boolean, Array, Object, Null |
|
|
121
|
+
| `Property` | Object property — name, schema |
|
|
122
|
+
| `OneOf`/`AnyOf` | Composition schemas with discriminator support |
|
|
123
|
+
| `Security` | Security scheme — apiKey, http, oauth2, openIdConnect |
|
|
124
|
+
| `OAuthFlow` | OAuth flow — authorization, token, scopes |
|
|
125
|
+
| `Header` | Response header — name, schema, description |
|
|
126
|
+
| `Tag` | Tag with optional external docs |
|
|
127
|
+
| `ExternalDoc` | External documentation reference |
|
|
128
|
+
| `Discriminator` | Polymorphism discriminator — property name, mapping |
|
|
129
|
+
|
|
130
|
+
See the [specification module](src/openapi_parser/specification.py) for
|
|
131
|
+
all available fields and types.
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Install with dev dependencies
|
|
137
|
+
uv sync --dev
|
|
138
|
+
|
|
139
|
+
# Lint
|
|
140
|
+
uv run ruff check .
|
|
141
|
+
uv run mypy .
|
|
142
|
+
uv run ty check .
|
|
143
|
+
|
|
144
|
+
# Test
|
|
145
|
+
uv run pytest
|
|
146
|
+
|
|
147
|
+
# Format
|
|
148
|
+
uv run ruff format .
|
|
149
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Builders for converting raw OpenAPI dicts into typed specification objects."""
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Shared builder utilities and type helpers."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from openapi_parser.errors import ParserError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class PropertyMeta:
|
|
12
|
+
"""Property metadata for type-casting extraction."""
|
|
13
|
+
|
|
14
|
+
name: str
|
|
15
|
+
cast: Callable[..., Any] | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def extract_typed_props(
|
|
19
|
+
data: dict[str, Any],
|
|
20
|
+
attrs_map: dict[str, PropertyMeta],
|
|
21
|
+
) -> dict[str, Any]:
|
|
22
|
+
"""Extract properties from the dictionary with type-casting using passed mapping.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
data (dict): Original dictionary to process
|
|
26
|
+
attrs_map (Dict[str, PropertyMeta]): Type-casting mapping
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Dict[str, Any]: Dictionary with type-casted values
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def cast_value(
|
|
33
|
+
name: str,
|
|
34
|
+
value: Any,
|
|
35
|
+
type_cast_func: Callable[..., Any] | None,
|
|
36
|
+
) -> Any:
|
|
37
|
+
try:
|
|
38
|
+
return type_cast_func(value) if type_cast_func is not None else value
|
|
39
|
+
except ValueError:
|
|
40
|
+
raise ParserError(
|
|
41
|
+
f"Invalid value for '{name}' property, got '{value}'"
|
|
42
|
+
) from None
|
|
43
|
+
|
|
44
|
+
custom_attrs = {
|
|
45
|
+
attr_name: cast_value(attr_info.name, data[attr_info.name], attr_info.cast)
|
|
46
|
+
for attr_name, attr_info in attrs_map.items()
|
|
47
|
+
if data.get(attr_info.name) is not None
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return custom_attrs
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def merge_schema(original: dict[str, Any], other: dict[str, Any]) -> dict[str, Any]:
|
|
54
|
+
"""Merge two schema dictionaries into single dict.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
original (dict): Source schema dictionary
|
|
58
|
+
other (dict): Schema dictionary to append to the source
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
dict: Dictionary value of new merged schema
|
|
62
|
+
"""
|
|
63
|
+
source = original.copy()
|
|
64
|
+
|
|
65
|
+
for key, value in other.items():
|
|
66
|
+
if key not in source:
|
|
67
|
+
source[key] = value
|
|
68
|
+
elif isinstance(value, list):
|
|
69
|
+
if isinstance(source[key], list):
|
|
70
|
+
source[key].extend(value)
|
|
71
|
+
else:
|
|
72
|
+
source[key] = value
|
|
73
|
+
elif isinstance(value, dict):
|
|
74
|
+
if isinstance(source[key], dict):
|
|
75
|
+
source[key] = merge_schema(source[key], value)
|
|
76
|
+
else:
|
|
77
|
+
source[key] = value
|
|
78
|
+
else:
|
|
79
|
+
source[key] = value
|
|
80
|
+
|
|
81
|
+
return source
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def extract_extension_attributes(schema: dict[str, Any]) -> dict[str, Any]:
|
|
85
|
+
"""Extract custom 'x-*' attributes from schema dictionary.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
schema (dict): Schema dictionary
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
dict: Dictionary with parsed attributes w/o 'x-' prefix
|
|
92
|
+
"""
|
|
93
|
+
extension_key_format = "x-"
|
|
94
|
+
|
|
95
|
+
extensions_dict: dict[str, Any] = {
|
|
96
|
+
key.replace(extension_key_format, "").replace("-", "_"): value
|
|
97
|
+
for key, value in schema.items()
|
|
98
|
+
if key.startswith(extension_key_format)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return extensions_dict
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Content builder for OpenAPI request/response bodies."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from openapi_parser.builders.encoding import EncodingBuilder
|
|
7
|
+
from openapi_parser.builders.schema import SchemaFactory
|
|
8
|
+
from openapi_parser.enumeration import ContentType
|
|
9
|
+
from openapi_parser.loose_types import LooseContentType
|
|
10
|
+
from openapi_parser.specification import Content
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
ContentTypeType = type[ContentType] | type[LooseContentType]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ContentBuilder:
|
|
18
|
+
"""Builds content objects for request/response bodies."""
|
|
19
|
+
|
|
20
|
+
_schema_factory: SchemaFactory
|
|
21
|
+
_encoding_builder: EncodingBuilder
|
|
22
|
+
_strict_enum: bool
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
schema_factory: SchemaFactory,
|
|
27
|
+
encoding_builder: EncodingBuilder,
|
|
28
|
+
strict_enum: bool = True,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initialize content builder.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
schema_factory: Factory for creating schema objects
|
|
34
|
+
encoding_builder: Builder for encoding objects
|
|
35
|
+
strict_enum: Whether to validate enums strictly
|
|
36
|
+
"""
|
|
37
|
+
self._schema_factory = schema_factory
|
|
38
|
+
self._encoding_builder = encoding_builder
|
|
39
|
+
self._strict_enum = strict_enum
|
|
40
|
+
|
|
41
|
+
def build_list(self, data: dict[str, Any]) -> list[Content]:
|
|
42
|
+
"""Build a list of content objects from a dict of media types."""
|
|
43
|
+
return [
|
|
44
|
+
self._create_content(
|
|
45
|
+
content_type,
|
|
46
|
+
content_value,
|
|
47
|
+
)
|
|
48
|
+
for content_type, content_value in data.items()
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
def _create_content(
|
|
52
|
+
self,
|
|
53
|
+
content_type: str,
|
|
54
|
+
content_value: dict[str, Any],
|
|
55
|
+
) -> Content:
|
|
56
|
+
logger.debug(f"Content building [type={content_type}]")
|
|
57
|
+
|
|
58
|
+
ContentTypeCls: ContentTypeType = (
|
|
59
|
+
ContentType if self._strict_enum else LooseContentType
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
encoding = (
|
|
63
|
+
self._encoding_builder.build_dict(content_value["encoding"])
|
|
64
|
+
if content_value.get("encoding")
|
|
65
|
+
else None
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return Content(
|
|
69
|
+
type=ContentTypeCls(content_type),
|
|
70
|
+
schema=self._schema_factory.create(content_value.get("schema", {})),
|
|
71
|
+
example=content_value.get("example"),
|
|
72
|
+
examples=content_value.get("examples", {}),
|
|
73
|
+
encoding=encoding,
|
|
74
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Encoding builder for request body property encodings."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from openapi_parser.builders.common import (
|
|
7
|
+
PropertyMeta,
|
|
8
|
+
extract_extension_attributes,
|
|
9
|
+
extract_typed_props,
|
|
10
|
+
)
|
|
11
|
+
from openapi_parser.builders.header import HeaderBuilder
|
|
12
|
+
from openapi_parser.specification import Encoding
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EncodingBuilder:
|
|
18
|
+
"""Builds encoding objects from raw specification data."""
|
|
19
|
+
|
|
20
|
+
_header_builder: HeaderBuilder
|
|
21
|
+
|
|
22
|
+
def __init__(self, header_builder: HeaderBuilder) -> None:
|
|
23
|
+
"""Initialize encoding builder.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
header_builder: Builder for header objects
|
|
27
|
+
"""
|
|
28
|
+
self._header_builder = header_builder
|
|
29
|
+
|
|
30
|
+
def build_dict(self, data: dict[str, dict[str, Any]]) -> dict[str, Encoding]:
|
|
31
|
+
"""Build a dict of encodings from a dict of raw encoding definitions."""
|
|
32
|
+
return {
|
|
33
|
+
property_name: self._build(encoding_data)
|
|
34
|
+
for property_name, encoding_data in data.items()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def _build(self, data: dict[str, Any]) -> Encoding:
|
|
38
|
+
logger.debug("Encoding building")
|
|
39
|
+
|
|
40
|
+
attrs_map = {
|
|
41
|
+
"content_type": PropertyMeta(name="contentType", cast=str),
|
|
42
|
+
"headers": PropertyMeta(
|
|
43
|
+
name="headers",
|
|
44
|
+
cast=self._header_builder.build_list,
|
|
45
|
+
),
|
|
46
|
+
"style": PropertyMeta(name="style", cast=str),
|
|
47
|
+
"explode": PropertyMeta(name="explode", cast=bool),
|
|
48
|
+
"allow_reserved": PropertyMeta(name="allowReserved", cast=bool),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
attrs = extract_typed_props(data, attrs_map)
|
|
52
|
+
attrs["extensions"] = extract_extension_attributes(data)
|
|
53
|
+
|
|
54
|
+
if attrs["extensions"]:
|
|
55
|
+
logger.debug(f"Extracted custom properties [{attrs['extensions'].keys()}]")
|
|
56
|
+
|
|
57
|
+
return Encoding(**attrs)
|