jentic-openapi-datamodels 1.0.0a17__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.
Files changed (73) hide show
  1. jentic/apitools/openapi/datamodels/low/extractors.py +3 -3
  2. jentic/apitools/openapi/datamodels/low/v30/__init__.py +76 -0
  3. jentic/apitools/openapi/datamodels/low/v30/builders/__init__.py +312 -0
  4. jentic/apitools/openapi/datamodels/low/v30/callback.py +131 -0
  5. jentic/apitools/openapi/datamodels/low/v30/components.py +236 -0
  6. jentic/apitools/openapi/datamodels/low/v30/contact.py +4 -10
  7. jentic/apitools/openapi/datamodels/low/v30/discriminator.py +4 -9
  8. jentic/apitools/openapi/datamodels/low/v30/encoding.py +81 -0
  9. jentic/apitools/openapi/datamodels/low/v30/example.py +91 -0
  10. jentic/apitools/openapi/datamodels/low/v30/external_documentation.py +4 -10
  11. jentic/apitools/openapi/datamodels/low/v30/header.py +120 -0
  12. jentic/apitools/openapi/datamodels/low/v30/info.py +14 -23
  13. jentic/apitools/openapi/datamodels/low/v30/license.py +4 -10
  14. jentic/apitools/openapi/datamodels/low/v30/link.py +141 -0
  15. jentic/apitools/openapi/datamodels/low/v30/media_type.py +110 -0
  16. jentic/apitools/openapi/datamodels/low/v30/oauth_flow.py +4 -10
  17. jentic/apitools/openapi/datamodels/low/v30/oauth_flows.py +7 -15
  18. jentic/apitools/openapi/datamodels/low/v30/openapi.py +149 -0
  19. jentic/apitools/openapi/datamodels/low/v30/operation.py +134 -0
  20. jentic/apitools/openapi/datamodels/low/v30/parameter.py +123 -0
  21. jentic/apitools/openapi/datamodels/low/v30/path_item.py +125 -0
  22. jentic/apitools/openapi/datamodels/low/v30/paths.py +108 -0
  23. jentic/apitools/openapi/datamodels/low/v30/reference.py +5 -9
  24. jentic/apitools/openapi/datamodels/low/v30/request_body.py +108 -0
  25. jentic/apitools/openapi/datamodels/low/v30/response.py +104 -0
  26. jentic/apitools/openapi/datamodels/low/v30/responses.py +109 -0
  27. jentic/apitools/openapi/datamodels/low/v30/schema.py +81 -97
  28. jentic/apitools/openapi/datamodels/low/v30/security_requirement.py +14 -9
  29. jentic/apitools/openapi/datamodels/low/v30/security_scheme.py +42 -22
  30. jentic/apitools/openapi/datamodels/low/v30/server.py +111 -0
  31. jentic/apitools/openapi/datamodels/low/v30/server_variable.py +4 -10
  32. jentic/apitools/openapi/datamodels/low/v30/tag.py +8 -46
  33. jentic/apitools/openapi/datamodels/low/v30/xml.py +4 -10
  34. jentic/apitools/openapi/datamodels/low/v31/__init__.py +77 -0
  35. jentic/apitools/openapi/datamodels/low/v31/builders/__init__.py +347 -0
  36. jentic/apitools/openapi/datamodels/low/v31/callback.py +131 -0
  37. jentic/apitools/openapi/datamodels/low/v31/components.py +240 -0
  38. jentic/apitools/openapi/datamodels/low/v31/contact.py +61 -0
  39. jentic/apitools/openapi/datamodels/low/v31/discriminator.py +62 -0
  40. jentic/apitools/openapi/datamodels/low/v31/encoding.py +81 -0
  41. jentic/apitools/openapi/datamodels/low/v31/example.py +91 -0
  42. jentic/apitools/openapi/datamodels/low/v31/external_documentation.py +59 -0
  43. jentic/apitools/openapi/datamodels/low/v31/header.py +120 -0
  44. jentic/apitools/openapi/datamodels/low/v31/info.py +116 -0
  45. jentic/apitools/openapi/datamodels/low/v31/license.py +61 -0
  46. jentic/apitools/openapi/datamodels/low/v31/link.py +141 -0
  47. jentic/apitools/openapi/datamodels/low/v31/media_type.py +110 -0
  48. jentic/apitools/openapi/datamodels/low/v31/oauth_flow.py +65 -0
  49. jentic/apitools/openapi/datamodels/low/v31/oauth_flows.py +108 -0
  50. jentic/apitools/openapi/datamodels/low/v31/openapi.py +168 -0
  51. jentic/apitools/openapi/datamodels/low/v31/operation.py +133 -0
  52. jentic/apitools/openapi/datamodels/low/v31/parameter.py +123 -0
  53. jentic/apitools/openapi/datamodels/low/v31/path_item.py +125 -0
  54. jentic/apitools/openapi/datamodels/low/v31/paths.py +108 -0
  55. jentic/apitools/openapi/datamodels/low/v31/reference.py +65 -0
  56. jentic/apitools/openapi/datamodels/low/v31/request_body.py +108 -0
  57. jentic/apitools/openapi/datamodels/low/v31/response.py +104 -0
  58. jentic/apitools/openapi/datamodels/low/v31/responses.py +109 -0
  59. jentic/apitools/openapi/datamodels/low/v31/schema.py +498 -0
  60. jentic/apitools/openapi/datamodels/low/v31/security_requirement.py +106 -0
  61. jentic/apitools/openapi/datamodels/low/v31/security_scheme.py +129 -0
  62. jentic/apitools/openapi/datamodels/low/v31/server.py +111 -0
  63. jentic/apitools/openapi/datamodels/low/v31/server_variable.py +70 -0
  64. jentic/apitools/openapi/datamodels/low/v31/tag.py +63 -0
  65. jentic/apitools/openapi/datamodels/low/v31/xml.py +54 -0
  66. jentic_openapi_datamodels-1.0.0a19.dist-info/METADATA +372 -0
  67. jentic_openapi_datamodels-1.0.0a19.dist-info/RECORD +75 -0
  68. jentic/apitools/openapi/datamodels/low/model_builder.py +0 -129
  69. jentic_openapi_datamodels-1.0.0a17.dist-info/METADATA +0 -211
  70. jentic_openapi_datamodels-1.0.0a17.dist-info/RECORD +0 -27
  71. {jentic_openapi_datamodels-1.0.0a17.dist-info → jentic_openapi_datamodels-1.0.0a19.dist-info}/WHEEL +0 -0
  72. {jentic_openapi_datamodels-1.0.0a17.dist-info → jentic_openapi_datamodels-1.0.0a19.dist-info}/licenses/LICENSE +0 -0
  73. {jentic_openapi_datamodels-1.0.0a17.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 jentic.apitools.openapi.datamodels.low.context import Context
4
- from jentic.apitools.openapi.datamodels.low.fields import fixed_fields
5
- from jentic.apitools.openapi.datamodels.low.sources import KeySource, ValueSource, YAMLValue
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)