jentic-openapi-datamodels 1.0.0a18__py3-none-any.whl → 1.0.0a19__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.
- jentic/apitools/openapi/datamodels/low/extractors.py +3 -3
- jentic/apitools/openapi/datamodels/low/v30/__init__.py +76 -0
- jentic/apitools/openapi/datamodels/low/v30/builders/__init__.py +312 -0
- jentic/apitools/openapi/datamodels/low/v30/callback.py +131 -0
- jentic/apitools/openapi/datamodels/low/v30/components.py +236 -0
- jentic/apitools/openapi/datamodels/low/v30/contact.py +4 -10
- jentic/apitools/openapi/datamodels/low/v30/discriminator.py +4 -9
- jentic/apitools/openapi/datamodels/low/v30/encoding.py +81 -0
- jentic/apitools/openapi/datamodels/low/v30/example.py +91 -0
- jentic/apitools/openapi/datamodels/low/v30/external_documentation.py +4 -10
- jentic/apitools/openapi/datamodels/low/v30/header.py +120 -0
- jentic/apitools/openapi/datamodels/low/v30/info.py +14 -23
- jentic/apitools/openapi/datamodels/low/v30/license.py +4 -10
- jentic/apitools/openapi/datamodels/low/v30/link.py +141 -0
- jentic/apitools/openapi/datamodels/low/v30/media_type.py +110 -0
- jentic/apitools/openapi/datamodels/low/v30/oauth_flow.py +4 -10
- jentic/apitools/openapi/datamodels/low/v30/oauth_flows.py +7 -15
- jentic/apitools/openapi/datamodels/low/v30/openapi.py +149 -0
- jentic/apitools/openapi/datamodels/low/v30/operation.py +134 -0
- jentic/apitools/openapi/datamodels/low/v30/parameter.py +123 -0
- jentic/apitools/openapi/datamodels/low/v30/path_item.py +125 -0
- jentic/apitools/openapi/datamodels/low/v30/paths.py +108 -0
- jentic/apitools/openapi/datamodels/low/v30/reference.py +5 -9
- jentic/apitools/openapi/datamodels/low/v30/request_body.py +108 -0
- jentic/apitools/openapi/datamodels/low/v30/response.py +104 -0
- jentic/apitools/openapi/datamodels/low/v30/responses.py +109 -0
- jentic/apitools/openapi/datamodels/low/v30/schema.py +81 -97
- jentic/apitools/openapi/datamodels/low/v30/security_requirement.py +14 -9
- jentic/apitools/openapi/datamodels/low/v30/security_scheme.py +42 -22
- jentic/apitools/openapi/datamodels/low/v30/server.py +111 -0
- jentic/apitools/openapi/datamodels/low/v30/server_variable.py +4 -10
- jentic/apitools/openapi/datamodels/low/v30/tag.py +8 -46
- jentic/apitools/openapi/datamodels/low/v30/xml.py +4 -10
- jentic/apitools/openapi/datamodels/low/v31/__init__.py +77 -0
- jentic/apitools/openapi/datamodels/low/v31/builders/__init__.py +347 -0
- jentic/apitools/openapi/datamodels/low/v31/callback.py +131 -0
- jentic/apitools/openapi/datamodels/low/v31/components.py +240 -0
- jentic/apitools/openapi/datamodels/low/v31/contact.py +61 -0
- jentic/apitools/openapi/datamodels/low/v31/discriminator.py +62 -0
- jentic/apitools/openapi/datamodels/low/v31/encoding.py +81 -0
- jentic/apitools/openapi/datamodels/low/v31/example.py +91 -0
- jentic/apitools/openapi/datamodels/low/v31/external_documentation.py +59 -0
- jentic/apitools/openapi/datamodels/low/v31/header.py +120 -0
- jentic/apitools/openapi/datamodels/low/v31/info.py +116 -0
- jentic/apitools/openapi/datamodels/low/v31/license.py +61 -0
- jentic/apitools/openapi/datamodels/low/v31/link.py +141 -0
- jentic/apitools/openapi/datamodels/low/v31/media_type.py +110 -0
- jentic/apitools/openapi/datamodels/low/v31/oauth_flow.py +65 -0
- jentic/apitools/openapi/datamodels/low/v31/oauth_flows.py +108 -0
- jentic/apitools/openapi/datamodels/low/v31/openapi.py +168 -0
- jentic/apitools/openapi/datamodels/low/v31/operation.py +133 -0
- jentic/apitools/openapi/datamodels/low/v31/parameter.py +123 -0
- jentic/apitools/openapi/datamodels/low/v31/path_item.py +125 -0
- jentic/apitools/openapi/datamodels/low/v31/paths.py +108 -0
- jentic/apitools/openapi/datamodels/low/v31/reference.py +65 -0
- jentic/apitools/openapi/datamodels/low/v31/request_body.py +108 -0
- jentic/apitools/openapi/datamodels/low/v31/response.py +104 -0
- jentic/apitools/openapi/datamodels/low/v31/responses.py +109 -0
- jentic/apitools/openapi/datamodels/low/v31/schema.py +498 -0
- jentic/apitools/openapi/datamodels/low/v31/security_requirement.py +106 -0
- jentic/apitools/openapi/datamodels/low/v31/security_scheme.py +129 -0
- jentic/apitools/openapi/datamodels/low/v31/server.py +111 -0
- jentic/apitools/openapi/datamodels/low/v31/server_variable.py +70 -0
- jentic/apitools/openapi/datamodels/low/v31/tag.py +63 -0
- jentic/apitools/openapi/datamodels/low/v31/xml.py +54 -0
- jentic_openapi_datamodels-1.0.0a19.dist-info/METADATA +372 -0
- jentic_openapi_datamodels-1.0.0a19.dist-info/RECORD +75 -0
- jentic/apitools/openapi/datamodels/low/model_builder.py +0 -129
- jentic_openapi_datamodels-1.0.0a18.dist-info/METADATA +0 -211
- jentic_openapi_datamodels-1.0.0a18.dist-info/RECORD +0 -27
- {jentic_openapi_datamodels-1.0.0a18.dist-info → jentic_openapi_datamodels-1.0.0a19.dist-info}/WHEEL +0 -0
- {jentic_openapi_datamodels-1.0.0a18.dist-info → jentic_openapi_datamodels-1.0.0a19.dist-info}/licenses/LICENSE +0 -0
- {jentic_openapi_datamodels-1.0.0a18.dist-info → jentic_openapi_datamodels-1.0.0a19.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from ruamel import yaml
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
3
|
+
from .context import Context
|
|
4
|
+
from .fields import fixed_fields
|
|
5
|
+
from .sources import KeySource, ValueSource, YAMLValue
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
__all__ = ["extract_extension_fields", "extract_unknown_fields"]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""OpenAPI 3.0.x Low-Level Data Models.
|
|
2
|
+
|
|
3
|
+
This module provides low-level/plumbing data models for OpenAPI 3.0 specification objects.
|
|
4
|
+
These models preserve complete source fidelity for inspection and validation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Main entry point for parsing OpenAPI documents
|
|
8
|
+
# All dataclasses for type hints and isinstance checks
|
|
9
|
+
from .callback import Callback
|
|
10
|
+
from .components import Components
|
|
11
|
+
from .contact import Contact
|
|
12
|
+
from .discriminator import Discriminator
|
|
13
|
+
from .encoding import Encoding
|
|
14
|
+
from .example import Example
|
|
15
|
+
from .external_documentation import ExternalDocumentation
|
|
16
|
+
from .header import Header
|
|
17
|
+
from .info import Info
|
|
18
|
+
from .license import License
|
|
19
|
+
from .link import Link
|
|
20
|
+
from .media_type import MediaType
|
|
21
|
+
from .oauth_flow import OAuthFlow
|
|
22
|
+
from .oauth_flows import OAuthFlows
|
|
23
|
+
from .openapi import OpenAPI30, build
|
|
24
|
+
from .operation import Operation
|
|
25
|
+
from .parameter import Parameter
|
|
26
|
+
from .path_item import PathItem
|
|
27
|
+
from .paths import Paths
|
|
28
|
+
from .reference import Reference
|
|
29
|
+
from .request_body import RequestBody
|
|
30
|
+
from .response import Response
|
|
31
|
+
from .responses import Responses
|
|
32
|
+
from .schema import Schema
|
|
33
|
+
from .security_requirement import SecurityRequirement
|
|
34
|
+
from .security_scheme import SecurityScheme
|
|
35
|
+
from .server import Server
|
|
36
|
+
from .server_variable import ServerVariable
|
|
37
|
+
from .tag import Tag
|
|
38
|
+
from .xml import XML
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
# Main entry point
|
|
43
|
+
"build",
|
|
44
|
+
# Root object
|
|
45
|
+
"OpenAPI30",
|
|
46
|
+
# All dataclasses (alphabetically sorted)
|
|
47
|
+
"Callback",
|
|
48
|
+
"Components",
|
|
49
|
+
"Contact",
|
|
50
|
+
"Discriminator",
|
|
51
|
+
"Encoding",
|
|
52
|
+
"Example",
|
|
53
|
+
"ExternalDocumentation",
|
|
54
|
+
"Header",
|
|
55
|
+
"Info",
|
|
56
|
+
"License",
|
|
57
|
+
"Link",
|
|
58
|
+
"MediaType",
|
|
59
|
+
"OAuthFlow",
|
|
60
|
+
"OAuthFlows",
|
|
61
|
+
"Operation",
|
|
62
|
+
"Parameter",
|
|
63
|
+
"PathItem",
|
|
64
|
+
"Paths",
|
|
65
|
+
"Reference",
|
|
66
|
+
"RequestBody",
|
|
67
|
+
"Response",
|
|
68
|
+
"Responses",
|
|
69
|
+
"Schema",
|
|
70
|
+
"SecurityRequirement",
|
|
71
|
+
"SecurityScheme",
|
|
72
|
+
"Server",
|
|
73
|
+
"ServerVariable",
|
|
74
|
+
"Tag",
|
|
75
|
+
"XML",
|
|
76
|
+
]
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""Builders for OpenAPI 3.0.x specification objects."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import fields
|
|
4
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast, get_args
|
|
5
|
+
|
|
6
|
+
from ruamel import yaml
|
|
7
|
+
|
|
8
|
+
from ...context import Context
|
|
9
|
+
from ...extractors import extract_extension_fields
|
|
10
|
+
from ...fields import fixed_fields
|
|
11
|
+
from ...sources import FieldSource, KeySource, ValueSource, YAMLInvalidValue, YAMLValue
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ..callback import Callback # noqa: F401
|
|
16
|
+
from ..example import Example # noqa: F401
|
|
17
|
+
from ..external_documentation import ExternalDocumentation # noqa: F401
|
|
18
|
+
from ..header import Header # noqa: F401
|
|
19
|
+
from ..link import Link # noqa: F401
|
|
20
|
+
from ..media_type import MediaType # noqa: F401
|
|
21
|
+
from ..parameter import Parameter # noqa: F401
|
|
22
|
+
from ..reference import Reference # noqa: F401
|
|
23
|
+
from ..schema import Schema # noqa: F401
|
|
24
|
+
from ..security_requirement import SecurityRequirement # noqa: F401
|
|
25
|
+
from ..server import Server # noqa: F401
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
__all__ = ["build_model", "build_field_source"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
T = TypeVar("T")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_model(
|
|
35
|
+
root: yaml.Node, dataclass_type: type[T], *, context: Context | None = None
|
|
36
|
+
) -> T | ValueSource[YAMLInvalidValue]:
|
|
37
|
+
"""
|
|
38
|
+
Generic builder for OpenAPI 3.0 low model.
|
|
39
|
+
|
|
40
|
+
Builds any dataclass that follows the pattern:
|
|
41
|
+
- Has a required `root_node: yaml.Node` field
|
|
42
|
+
- Has an optional `extensions: dict[...]` field
|
|
43
|
+
- Has spec fields marked with `fixed_field()`
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
47
|
+
dataclass_type: The dataclass type to build
|
|
48
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
An instance of dataclass_type if the node is valid, or a ValueSource containing
|
|
52
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
53
|
+
and its source location for validation).
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
xml = build_model(root_node, XML, context=context)
|
|
57
|
+
"""
|
|
58
|
+
# Initialize context once at the beginning
|
|
59
|
+
if context is None:
|
|
60
|
+
context = Context()
|
|
61
|
+
|
|
62
|
+
if not isinstance(root, yaml.MappingNode):
|
|
63
|
+
# Preserve invalid root data instead of returning None
|
|
64
|
+
value = context.yaml_constructor.construct_object(root, deep=True)
|
|
65
|
+
return ValueSource(value=value, value_node=root)
|
|
66
|
+
|
|
67
|
+
# Get fixed specification fields for this dataclass type
|
|
68
|
+
_fixed_fields = fixed_fields(dataclass_type)
|
|
69
|
+
|
|
70
|
+
# Build YAML name to Python field name mapping
|
|
71
|
+
yaml_to_field = {
|
|
72
|
+
field.metadata.get("yaml_name", fname): fname for fname, field in _fixed_fields.items()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Extract field values in a single pass (non-recursive, single layer only)
|
|
76
|
+
field_values: dict[str, FieldSource[Any]] = {}
|
|
77
|
+
for key_node, value_node in root.value:
|
|
78
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
79
|
+
|
|
80
|
+
# Map YAML key to Python field name
|
|
81
|
+
field_name = yaml_to_field.get(key)
|
|
82
|
+
if field_name:
|
|
83
|
+
field = _fixed_fields[field_name]
|
|
84
|
+
field_type_args = set(get_args(field.type))
|
|
85
|
+
|
|
86
|
+
if field_type_args & {
|
|
87
|
+
FieldSource[str],
|
|
88
|
+
FieldSource[bool],
|
|
89
|
+
FieldSource[int],
|
|
90
|
+
FieldSource[YAMLValue],
|
|
91
|
+
}:
|
|
92
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
93
|
+
elif field_type_args & {FieldSource[dict[KeySource[str], ValueSource[str]]]}:
|
|
94
|
+
# Handle dict with KeySource/ValueSource wrapping
|
|
95
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
96
|
+
mapping_dict: dict[KeySource[str], ValueSource[str]] = {}
|
|
97
|
+
for map_key_node, map_value_node in value_node.value:
|
|
98
|
+
map_key = context.yaml_constructor.construct_yaml_str(map_key_node)
|
|
99
|
+
map_value = context.yaml_constructor.construct_object(
|
|
100
|
+
map_value_node, deep=True
|
|
101
|
+
)
|
|
102
|
+
mapping_dict[KeySource(value=map_key, key_node=map_key_node)] = ValueSource(
|
|
103
|
+
value=map_value, value_node=map_value_node
|
|
104
|
+
)
|
|
105
|
+
field_values[field_name] = FieldSource(
|
|
106
|
+
value=mapping_dict, key_node=key_node, value_node=value_node
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
# Not a mapping - preserve as-is for validation
|
|
110
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
111
|
+
elif field_type_args & {FieldSource[list[ValueSource[str]]]}:
|
|
112
|
+
# Handle list with ValueSource wrapping for each item
|
|
113
|
+
if isinstance(value_node, yaml.SequenceNode):
|
|
114
|
+
value_list: list[ValueSource[str]] = []
|
|
115
|
+
for item_node in value_node.value:
|
|
116
|
+
item_value = context.yaml_constructor.construct_object(item_node, deep=True)
|
|
117
|
+
value_list.append(ValueSource(value=item_value, value_node=item_node))
|
|
118
|
+
field_values[field_name] = FieldSource(
|
|
119
|
+
value=value_list, key_node=key_node, value_node=value_node
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
# Not a sequence - preserve as-is for validation
|
|
123
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
124
|
+
elif field_type_args & {FieldSource[list["Server"]]}:
|
|
125
|
+
# Handle list[Server] with lazy import
|
|
126
|
+
from ..server import build as build_server
|
|
127
|
+
|
|
128
|
+
if isinstance(value_node, yaml.SequenceNode):
|
|
129
|
+
servers_list = []
|
|
130
|
+
for item_node in value_node.value:
|
|
131
|
+
server_obj = build_server(item_node, context)
|
|
132
|
+
servers_list.append(server_obj)
|
|
133
|
+
field_values[field_name] = FieldSource(
|
|
134
|
+
value=servers_list, key_node=key_node, value_node=value_node
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
# Not a sequence - preserve as-is for validation
|
|
138
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
139
|
+
elif field_type_args & {FieldSource[list["SecurityRequirement"]]}:
|
|
140
|
+
# Handle list[SecurityRequirement] with lazy import
|
|
141
|
+
from ..security_requirement import build as build_security_requirement
|
|
142
|
+
|
|
143
|
+
if isinstance(value_node, yaml.SequenceNode):
|
|
144
|
+
security_list = []
|
|
145
|
+
for item_node in value_node.value:
|
|
146
|
+
security_req = build_security_requirement(item_node, context)
|
|
147
|
+
security_list.append(security_req)
|
|
148
|
+
field_values[field_name] = FieldSource(
|
|
149
|
+
value=security_list, key_node=key_node, value_node=value_node
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
# Not a sequence - preserve as-is for validation
|
|
153
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
154
|
+
elif field_type_args & {FieldSource[list["Parameter | Reference"]]}:
|
|
155
|
+
# Handle list[Parameter | Reference] with lazy import
|
|
156
|
+
from ..parameter import build_parameter_or_reference
|
|
157
|
+
|
|
158
|
+
if isinstance(value_node, yaml.SequenceNode):
|
|
159
|
+
parameters_list = []
|
|
160
|
+
for item_node in value_node.value:
|
|
161
|
+
parameter_or_reference = build_parameter_or_reference(item_node, context)
|
|
162
|
+
parameters_list.append(parameter_or_reference)
|
|
163
|
+
field_values[field_name] = FieldSource(
|
|
164
|
+
value=parameters_list, key_node=key_node, value_node=value_node
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
# Not a sequence - preserve as-is for validation
|
|
168
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
169
|
+
elif field_type_args & {FieldSource[dict[KeySource[str], "Example | Reference"]]}:
|
|
170
|
+
# Handle dict[KeySource[str], Example | Reference] with lazy import
|
|
171
|
+
from ..example import build_example_or_reference
|
|
172
|
+
|
|
173
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
174
|
+
examples_dict = {}
|
|
175
|
+
for map_key_node, map_value_node in value_node.value:
|
|
176
|
+
map_key = context.yaml_constructor.construct_yaml_str(map_key_node)
|
|
177
|
+
example_or_reference = build_example_or_reference(map_value_node, context)
|
|
178
|
+
examples_dict[KeySource(value=map_key, key_node=map_key_node)] = (
|
|
179
|
+
example_or_reference
|
|
180
|
+
)
|
|
181
|
+
field_values[field_name] = FieldSource(
|
|
182
|
+
value=examples_dict, key_node=key_node, value_node=value_node
|
|
183
|
+
)
|
|
184
|
+
else:
|
|
185
|
+
# Not a mapping - preserve as-is for validation
|
|
186
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
187
|
+
elif field_type_args & {FieldSource[dict[KeySource[str], "MediaType"]]}:
|
|
188
|
+
# Handle dict[KeySource[str], MediaType] with lazy import
|
|
189
|
+
from ..media_type import build as build_media_type
|
|
190
|
+
|
|
191
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
192
|
+
content_dict = {}
|
|
193
|
+
for map_key_node, map_value_node in value_node.value:
|
|
194
|
+
map_key = context.yaml_constructor.construct_yaml_str(map_key_node)
|
|
195
|
+
media_type_obj = build_media_type(map_value_node, context)
|
|
196
|
+
content_dict[KeySource(value=map_key, key_node=map_key_node)] = (
|
|
197
|
+
media_type_obj
|
|
198
|
+
)
|
|
199
|
+
field_values[field_name] = FieldSource(
|
|
200
|
+
value=content_dict, key_node=key_node, value_node=value_node
|
|
201
|
+
)
|
|
202
|
+
else:
|
|
203
|
+
# Not a mapping - preserve as-is for validation
|
|
204
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
205
|
+
elif field_type_args & {FieldSource[dict[KeySource[str], "Header | Reference"]]}:
|
|
206
|
+
# Handle dict[KeySource[str], Header | Reference] with lazy import
|
|
207
|
+
from ..header import build_header_or_reference
|
|
208
|
+
|
|
209
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
210
|
+
headers_dict = {}
|
|
211
|
+
for map_key_node, map_value_node in value_node.value:
|
|
212
|
+
map_key = context.yaml_constructor.construct_yaml_str(map_key_node)
|
|
213
|
+
header_or_reference = build_header_or_reference(map_value_node, context)
|
|
214
|
+
headers_dict[KeySource(value=map_key, key_node=map_key_node)] = (
|
|
215
|
+
header_or_reference
|
|
216
|
+
)
|
|
217
|
+
field_values[field_name] = FieldSource(
|
|
218
|
+
value=headers_dict, key_node=key_node, value_node=value_node
|
|
219
|
+
)
|
|
220
|
+
else:
|
|
221
|
+
# Not a mapping - preserve as-is for validation
|
|
222
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
223
|
+
elif field_type_args & {FieldSource[dict[KeySource[str], "Callback | Reference"]]}:
|
|
224
|
+
# Handle dict[KeySource[str], Callback | Reference] with lazy import
|
|
225
|
+
from ..callback import build_callback_or_reference
|
|
226
|
+
|
|
227
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
228
|
+
callbacks_dict = {}
|
|
229
|
+
for map_key_node, map_value_node in value_node.value:
|
|
230
|
+
map_key = context.yaml_constructor.construct_yaml_str(map_key_node)
|
|
231
|
+
callback_or_reference = build_callback_or_reference(map_value_node, context)
|
|
232
|
+
callbacks_dict[KeySource(value=map_key, key_node=map_key_node)] = (
|
|
233
|
+
callback_or_reference
|
|
234
|
+
)
|
|
235
|
+
field_values[field_name] = FieldSource(
|
|
236
|
+
value=callbacks_dict, key_node=key_node, value_node=value_node
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
# Not a mapping - preserve as-is for validation
|
|
240
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
241
|
+
elif field_type_args & {FieldSource[dict[KeySource[str], "Link | Reference"]]}:
|
|
242
|
+
# Handle dict[KeySource[str], Link | Reference] with lazy import
|
|
243
|
+
from ..link import build_link_or_reference
|
|
244
|
+
|
|
245
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
246
|
+
links_dict = {}
|
|
247
|
+
for map_key_node, map_value_node in value_node.value:
|
|
248
|
+
map_key = context.yaml_constructor.construct_yaml_str(map_key_node)
|
|
249
|
+
link_or_reference = build_link_or_reference(map_value_node, context)
|
|
250
|
+
links_dict[KeySource(value=map_key, key_node=map_key_node)] = (
|
|
251
|
+
link_or_reference
|
|
252
|
+
)
|
|
253
|
+
field_values[field_name] = FieldSource(
|
|
254
|
+
value=links_dict, key_node=key_node, value_node=value_node
|
|
255
|
+
)
|
|
256
|
+
else:
|
|
257
|
+
# Not a mapping - preserve as-is for validation
|
|
258
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
259
|
+
elif field_type_args & {FieldSource["ExternalDocumentation"]}:
|
|
260
|
+
# Handle ExternalDocumentation with lazy import
|
|
261
|
+
from ..external_documentation import build as build_external_docs
|
|
262
|
+
|
|
263
|
+
field_values[field_name] = FieldSource(
|
|
264
|
+
value=build_external_docs(value_node, context),
|
|
265
|
+
key_node=key_node,
|
|
266
|
+
value_node=value_node,
|
|
267
|
+
)
|
|
268
|
+
elif field_type_args & {FieldSource["Schema | Reference"]}:
|
|
269
|
+
# Handle Schema | Reference union with lazy import
|
|
270
|
+
from ..schema import build_schema_or_reference
|
|
271
|
+
|
|
272
|
+
field_values[field_name] = FieldSource(
|
|
273
|
+
value=build_schema_or_reference(value_node, context),
|
|
274
|
+
key_node=key_node,
|
|
275
|
+
value_node=value_node,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Build and return the dataclass instance
|
|
279
|
+
# Conditionally include extensions field if dataclass supports it
|
|
280
|
+
# Cast to Any to work around generic type constraints
|
|
281
|
+
has_extensions = any(f.name == "extensions" for f in fields(cast(Any, dataclass_type)))
|
|
282
|
+
return cast(
|
|
283
|
+
T,
|
|
284
|
+
dataclass_type(
|
|
285
|
+
root_node=root, # type: ignore[call-arg]
|
|
286
|
+
**field_values,
|
|
287
|
+
**({"extensions": extract_extension_fields(root, context)} if has_extensions else {}),
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def build_field_source(
|
|
293
|
+
key_node: yaml.Node,
|
|
294
|
+
value_node: yaml.Node,
|
|
295
|
+
context: Context,
|
|
296
|
+
) -> FieldSource[Any]:
|
|
297
|
+
"""
|
|
298
|
+
Build a FieldSource from a YAML node.
|
|
299
|
+
|
|
300
|
+
Constructs the Python value from the YAML node and wraps it in a FieldSource
|
|
301
|
+
that preserves the original node locations for error reporting.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
key_node: The YAML node for the field key
|
|
305
|
+
value_node: The YAML node for the field value
|
|
306
|
+
context: Parsing context
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
A FieldSource containing the constructed value and node references
|
|
310
|
+
"""
|
|
311
|
+
value = context.yaml_constructor.construct_object(value_node, deep=True)
|
|
312
|
+
return FieldSource(value=value, key_node=key_node, value_node=value_node)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
from ruamel import yaml
|
|
4
|
+
|
|
5
|
+
from ..context import Context
|
|
6
|
+
from ..extractors import extract_extension_fields
|
|
7
|
+
from ..sources import KeySource, ValueSource, YAMLInvalidValue, YAMLValue
|
|
8
|
+
from .path_item import PathItem
|
|
9
|
+
from .path_item import build as build_path_item
|
|
10
|
+
from .reference import Reference
|
|
11
|
+
from .reference import build as build_reference
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ["Callback", "build", "build_callback_or_reference"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class Callback:
|
|
19
|
+
"""
|
|
20
|
+
Callback Object representation for OpenAPI 3.0.
|
|
21
|
+
|
|
22
|
+
A map of possible out-of-band callbacks related to the parent operation. Each value in the map
|
|
23
|
+
is a Path Item Object that describes a set of requests that may be initiated by the API provider
|
|
24
|
+
and the expected responses.
|
|
25
|
+
|
|
26
|
+
The key is a runtime expression that identifies a URL to use for the callback operation.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
root_node: The top-level node representing the entire Callback object in the original source file
|
|
30
|
+
path_items: Map of expression keys to Path Item Objects. Each key is a runtime expression
|
|
31
|
+
that will be evaluated to determine the callback URL.
|
|
32
|
+
extensions: Specification extensions (x-* fields)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
root_node: yaml.Node
|
|
36
|
+
path_items: dict[KeySource[str], PathItem] = field(default_factory=dict)
|
|
37
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def build(
|
|
41
|
+
root: yaml.Node, context: Context | None = None
|
|
42
|
+
) -> Callback | ValueSource[YAMLInvalidValue]:
|
|
43
|
+
"""
|
|
44
|
+
Build a Callback object from a YAML node.
|
|
45
|
+
|
|
46
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
47
|
+
model that provides complete source fidelity for inspection and validation.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
51
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A Callback object if the node is valid, or a ValueSource containing
|
|
55
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
56
|
+
and its source location for validation).
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
from ruamel.yaml import YAML
|
|
60
|
+
yaml = YAML()
|
|
61
|
+
root = yaml.compose('''
|
|
62
|
+
'{$request.body#/callbackUrl}':
|
|
63
|
+
post:
|
|
64
|
+
requestBody:
|
|
65
|
+
required: true
|
|
66
|
+
content:
|
|
67
|
+
application/json:
|
|
68
|
+
schema:
|
|
69
|
+
type: object
|
|
70
|
+
responses:
|
|
71
|
+
'200':
|
|
72
|
+
description: callback successfully processed
|
|
73
|
+
''')
|
|
74
|
+
callback = build(root)
|
|
75
|
+
assert len(callback.path_items) > 0
|
|
76
|
+
"""
|
|
77
|
+
context = context or Context()
|
|
78
|
+
|
|
79
|
+
# Check if root is a MappingNode, if not return ValueSource with invalid data
|
|
80
|
+
if not isinstance(root, yaml.MappingNode):
|
|
81
|
+
value = context.yaml_constructor.construct_object(root, deep=True)
|
|
82
|
+
return ValueSource(value=value, value_node=root)
|
|
83
|
+
|
|
84
|
+
# Extract extensions first
|
|
85
|
+
extensions = extract_extension_fields(root, context)
|
|
86
|
+
extension_properties = {k.value for k in extensions.keys()}
|
|
87
|
+
|
|
88
|
+
# Process each field to determine if it's an expression (not an extension)
|
|
89
|
+
path_items: dict[KeySource[str], PathItem | ValueSource[YAMLInvalidValue]] = {}
|
|
90
|
+
|
|
91
|
+
for key_node, value_node in root.value:
|
|
92
|
+
expression = context.yaml_constructor.construct_yaml_str(key_node)
|
|
93
|
+
|
|
94
|
+
if expression not in extension_properties:
|
|
95
|
+
# Expression field (any key that's not an extension) - build as Path Item
|
|
96
|
+
path_item_obj = build_path_item(value_node, context)
|
|
97
|
+
path_items[KeySource(value=expression, key_node=key_node)] = path_item_obj
|
|
98
|
+
|
|
99
|
+
# Create and return the Callback object with collected data
|
|
100
|
+
return Callback(
|
|
101
|
+
root_node=root,
|
|
102
|
+
path_items=path_items, # type: ignore[arg-type]
|
|
103
|
+
extensions=extensions,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_callback_or_reference(
|
|
108
|
+
node: yaml.Node, context: Context
|
|
109
|
+
) -> Callback | Reference | ValueSource[YAMLInvalidValue]:
|
|
110
|
+
"""
|
|
111
|
+
Build either a Callback or Reference from a YAML node.
|
|
112
|
+
|
|
113
|
+
This helper handles the polymorphic nature of OpenAPI where many fields
|
|
114
|
+
can contain either a Callback object or a Reference object ($ref).
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
node: The YAML node to parse
|
|
118
|
+
context: Parsing context
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
A Callback, Reference, or ValueSource if the node is invalid
|
|
122
|
+
"""
|
|
123
|
+
# Check if it's a reference (has $ref key)
|
|
124
|
+
if isinstance(node, yaml.MappingNode):
|
|
125
|
+
for key_node, _ in node.value:
|
|
126
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
127
|
+
if key == "$ref":
|
|
128
|
+
return build_reference(node, context)
|
|
129
|
+
|
|
130
|
+
# Otherwise, try to build as Callback
|
|
131
|
+
return build(node, context)
|