openapi3-parser 1.1.22__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.
Files changed (65) hide show
  1. openapi3_parser-1.2.0/PKG-INFO +189 -0
  2. {openapi3_parser-1.1.22 → openapi3_parser-1.2.0}/license.txt +1 -1
  3. openapi3_parser-1.2.0/pyproject.toml +82 -0
  4. openapi3_parser-1.2.0/readme.md +149 -0
  5. openapi3_parser-1.2.0/src/openapi_parser/__init__.py +5 -0
  6. openapi3_parser-1.2.0/src/openapi_parser/builders/__init__.py +1 -0
  7. openapi3_parser-1.2.0/src/openapi_parser/builders/common.py +101 -0
  8. openapi3_parser-1.2.0/src/openapi_parser/builders/content.py +74 -0
  9. openapi3_parser-1.2.0/src/openapi_parser/builders/encoding.py +57 -0
  10. openapi3_parser-1.2.0/src/openapi_parser/builders/external_doc.py +37 -0
  11. openapi3_parser-1.2.0/src/openapi_parser/builders/header.py +55 -0
  12. openapi3_parser-1.2.0/src/openapi_parser/builders/info.py +68 -0
  13. openapi3_parser-1.2.0/src/openapi_parser/builders/link.py +51 -0
  14. openapi3_parser-1.2.0/src/openapi_parser/builders/oauth_flow.py +51 -0
  15. openapi3_parser-1.2.0/src/openapi_parser/builders/operation.py +92 -0
  16. openapi3_parser-1.2.0/src/openapi_parser/builders/parameter.py +105 -0
  17. openapi3_parser-1.2.0/src/openapi_parser/builders/path.py +74 -0
  18. openapi3_parser-1.2.0/src/openapi_parser/builders/request.py +41 -0
  19. openapi3_parser-1.2.0/src/openapi_parser/builders/response.py +68 -0
  20. {openapi3_parser-1.1.22 → openapi3_parser-1.2.0}/src/openapi_parser/builders/schema.py +130 -70
  21. openapi3_parser-1.2.0/src/openapi_parser/builders/schemas.py +31 -0
  22. openapi3_parser-1.2.0/src/openapi_parser/builders/security.py +62 -0
  23. openapi3_parser-1.2.0/src/openapi_parser/builders/server.py +45 -0
  24. openapi3_parser-1.2.0/src/openapi_parser/builders/tag.py +50 -0
  25. {openapi3_parser-1.1.22 → openapi3_parser-1.2.0}/src/openapi_parser/enumeration.py +32 -0
  26. openapi3_parser-1.2.0/src/openapi_parser/errors.py +10 -0
  27. {openapi3_parser-1.1.22 → openapi3_parser-1.2.0}/src/openapi_parser/loose_types.py +4 -0
  28. openapi3_parser-1.2.0/src/openapi_parser/parser.py +200 -0
  29. openapi3_parser-1.2.0/src/openapi_parser/resolver.py +53 -0
  30. openapi3_parser-1.2.0/src/openapi_parser/specification.py +376 -0
  31. openapi3_parser-1.1.22/PKG-INFO +0 -130
  32. openapi3_parser-1.1.22/pyproject.toml +0 -71
  33. openapi3_parser-1.1.22/readme.md +0 -89
  34. openapi3_parser-1.1.22/setup.cfg +0 -4
  35. openapi3_parser-1.1.22/src/openapi3_parser.egg-info/PKG-INFO +0 -130
  36. openapi3_parser-1.1.22/src/openapi3_parser.egg-info/SOURCES.txt +0 -37
  37. openapi3_parser-1.1.22/src/openapi3_parser.egg-info/dependency_links.txt +0 -1
  38. openapi3_parser-1.1.22/src/openapi3_parser.egg-info/requires.txt +0 -2
  39. openapi3_parser-1.1.22/src/openapi3_parser.egg-info/top_level.txt +0 -1
  40. openapi3_parser-1.1.22/src/openapi_parser/__init__.py +0 -3
  41. openapi3_parser-1.1.22/src/openapi_parser/builders/__init__.py +0 -0
  42. openapi3_parser-1.1.22/src/openapi_parser/builders/common.py +0 -80
  43. openapi3_parser-1.1.22/src/openapi_parser/builders/content.py +0 -39
  44. openapi3_parser-1.1.22/src/openapi_parser/builders/external_doc.py +0 -23
  45. openapi3_parser-1.1.22/src/openapi_parser/builders/header.py +0 -41
  46. openapi3_parser-1.1.22/src/openapi_parser/builders/info.py +0 -47
  47. openapi3_parser-1.1.22/src/openapi_parser/builders/oauth_flow.py +0 -39
  48. openapi3_parser-1.1.22/src/openapi_parser/builders/operation.py +0 -61
  49. openapi3_parser-1.1.22/src/openapi_parser/builders/parameter.py +0 -68
  50. openapi3_parser-1.1.22/src/openapi_parser/builders/path.py +0 -55
  51. openapi3_parser-1.1.22/src/openapi_parser/builders/request.py +0 -27
  52. openapi3_parser-1.1.22/src/openapi_parser/builders/response.py +0 -38
  53. openapi3_parser-1.1.22/src/openapi_parser/builders/schemas.py +0 -21
  54. openapi3_parser-1.1.22/src/openapi_parser/builders/security.py +0 -44
  55. openapi3_parser-1.1.22/src/openapi_parser/builders/server.py +0 -29
  56. openapi3_parser-1.1.22/src/openapi_parser/builders/tag.py +0 -30
  57. openapi3_parser-1.1.22/src/openapi_parser/errors.py +0 -6
  58. openapi3_parser-1.1.22/src/openapi_parser/parser.py +0 -146
  59. openapi3_parser-1.1.22/src/openapi_parser/resolver.py +0 -41
  60. openapi3_parser-1.1.22/src/openapi_parser/specification.py +0 -271
  61. openapi3_parser-1.1.22/tests/test_enumeration.py +0 -231
  62. openapi3_parser-1.1.22/tests/test_parser.py +0 -58
  63. openapi3_parser-1.1.22/tests/test_parser_options.py +0 -27
  64. openapi3_parser-1.1.22/tests/test_runner.py +0 -16
  65. {openapi3_parser-1.1.22 → 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
+ [![PyPI - Version](https://img.shields.io/pypi/v/openapi3-parser)](https://pypi.org/project/openapi3-parser/)
44
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/openapi3-parser)](https://clickpy.clickhouse.com/dashboard/openapi3-parser)
45
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/openapi3-parser)](https://pypi.org/project/openapi3-parser/)
46
+ [![PyPI - Format](https://img.shields.io/pypi/format/openapi3-parser)](https://pypi.org/project/openapi3-parser/)
47
+ [![PyPI - License](https://img.shields.io/pypi/l/openapi3-parser)](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
+ ```
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 Artyom Manchenkov
3
+ Copyright (c) 2020 Artem Manchenkov
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -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
+ [![PyPI - Version](https://img.shields.io/pypi/v/openapi3-parser)](https://pypi.org/project/openapi3-parser/)
4
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/openapi3-parser)](https://clickpy.clickhouse.com/dashboard/openapi3-parser)
5
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/openapi3-parser)](https://pypi.org/project/openapi3-parser/)
6
+ [![PyPI - Format](https://img.shields.io/pypi/format/openapi3-parser)](https://pypi.org/project/openapi3-parser/)
7
+ [![PyPI - License](https://img.shields.io/pypi/l/openapi3-parser)](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,5 @@
1
+ """OpenAPI v3 specification parser."""
2
+
3
+ from openapi_parser.parser import parse
4
+
5
+ __all__ = ["parse"]
@@ -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)