jentic-openapi-datamodels 1.0.0a18__py3-none-any.whl → 1.0.0a20__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.0a20.dist-info/METADATA +379 -0
- jentic_openapi_datamodels-1.0.0a20.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.0a20.dist-info}/WHEEL +0 -0
- {jentic_openapi_datamodels-1.0.0a18.dist-info → jentic_openapi_datamodels-1.0.0a20.dist-info}/licenses/LICENSE +0 -0
- {jentic_openapi_datamodels-1.0.0a18.dist-info → jentic_openapi_datamodels-1.0.0a20.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, replace
|
|
2
|
+
|
|
3
|
+
from ruamel import yaml
|
|
4
|
+
|
|
5
|
+
from ..context import Context
|
|
6
|
+
from ..fields import fixed_field
|
|
7
|
+
from ..sources import FieldSource, KeySource, ValueSource, YAMLInvalidValue, YAMLValue
|
|
8
|
+
from .builders import build_field_source, build_model
|
|
9
|
+
from .reference import Reference
|
|
10
|
+
from .reference import build as build_reference
|
|
11
|
+
from .server import Server
|
|
12
|
+
from .server import build as build_server
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = ["Link", "build", "build_link_or_reference"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class Link:
|
|
20
|
+
"""
|
|
21
|
+
Link Object representation for OpenAPI 3.0.
|
|
22
|
+
|
|
23
|
+
The Link Object represents a possible design-time link for a response.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
root_node: The top-level node representing the entire Link object in the original source file
|
|
27
|
+
operation_ref: A relative or absolute URI reference to an OAS operation.
|
|
28
|
+
operation_id: The name of an existing, resolvable OAS operation.
|
|
29
|
+
parameters: A map representing parameters to pass to an operation as specified with operationId
|
|
30
|
+
or identified via operationRef.
|
|
31
|
+
request_body: A literal value or {expression} to use as a request body when calling the target operation.
|
|
32
|
+
description: A description of the link. CommonMark syntax MAY be used for rich text representation.
|
|
33
|
+
server: A server object to be used by the target operation.
|
|
34
|
+
extensions: Specification extensions (x-* fields)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
root_node: yaml.Node
|
|
38
|
+
operation_ref: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "operationRef"})
|
|
39
|
+
operation_id: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "operationId"})
|
|
40
|
+
parameters: FieldSource[dict[KeySource[str], ValueSource[YAMLValue]]] | None = fixed_field()
|
|
41
|
+
request_body: FieldSource[YAMLValue] | None = fixed_field(metadata={"yaml_name": "requestBody"})
|
|
42
|
+
description: FieldSource[str] | None = fixed_field()
|
|
43
|
+
server: FieldSource[Server] | None = fixed_field()
|
|
44
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build(root: yaml.Node, context: Context | None = None) -> Link | ValueSource[YAMLInvalidValue]:
|
|
48
|
+
"""
|
|
49
|
+
Build a Link object from a YAML node.
|
|
50
|
+
|
|
51
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
52
|
+
model that provides complete source fidelity for inspection and validation.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
56
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
A Link object if the node is valid, or a ValueSource containing
|
|
60
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
61
|
+
and its source location for validation).
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
from ruamel.yaml import YAML
|
|
65
|
+
yaml = YAML()
|
|
66
|
+
root = yaml.compose("operationId: getUserById\\nparameters:\\n userId: $response.body#/id")
|
|
67
|
+
link = build(root)
|
|
68
|
+
assert link.operation_id.value == 'getUserById'
|
|
69
|
+
"""
|
|
70
|
+
context = context or Context()
|
|
71
|
+
|
|
72
|
+
# Use build_model for initial construction
|
|
73
|
+
link = build_model(root, Link, context=context)
|
|
74
|
+
|
|
75
|
+
# If build_model returned ValueSource (invalid node), return it immediately
|
|
76
|
+
if not isinstance(link, Link):
|
|
77
|
+
return link
|
|
78
|
+
|
|
79
|
+
# Manually handle nested server object and parameters dict
|
|
80
|
+
replacements = {}
|
|
81
|
+
for key_node, value_node in root.value:
|
|
82
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
83
|
+
|
|
84
|
+
if key == "server":
|
|
85
|
+
# Handle nested Server object - child builder handles invalid nodes
|
|
86
|
+
replacements["server"] = FieldSource(
|
|
87
|
+
value=build_server(value_node, context=context),
|
|
88
|
+
key_node=key_node,
|
|
89
|
+
value_node=value_node,
|
|
90
|
+
)
|
|
91
|
+
elif key == "parameters":
|
|
92
|
+
# Handle parameters map with KeySource/ValueSource wrapping
|
|
93
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
94
|
+
params_dict: dict[KeySource[str], ValueSource[YAMLValue]] = {}
|
|
95
|
+
for param_key_node, param_value_node in value_node.value:
|
|
96
|
+
param_key = context.yaml_constructor.construct_yaml_str(param_key_node)
|
|
97
|
+
param_value = context.yaml_constructor.construct_object(
|
|
98
|
+
param_value_node, deep=True
|
|
99
|
+
)
|
|
100
|
+
params_dict[KeySource(value=param_key, key_node=param_key_node)] = ValueSource(
|
|
101
|
+
value=param_value, value_node=param_value_node
|
|
102
|
+
)
|
|
103
|
+
replacements["parameters"] = FieldSource(
|
|
104
|
+
value=params_dict, key_node=key_node, value_node=value_node
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
# Not a mapping - preserve as-is for validation
|
|
108
|
+
replacements["parameters"] = build_field_source(key_node, value_node, context)
|
|
109
|
+
|
|
110
|
+
# Apply all replacements at once
|
|
111
|
+
if replacements:
|
|
112
|
+
link = replace(link, **replacements)
|
|
113
|
+
|
|
114
|
+
return link
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def build_link_or_reference(
|
|
118
|
+
node: yaml.Node, context: Context
|
|
119
|
+
) -> Link | Reference | ValueSource[YAMLInvalidValue]:
|
|
120
|
+
"""
|
|
121
|
+
Build either a Link or Reference from a YAML node.
|
|
122
|
+
|
|
123
|
+
This helper handles the polymorphic nature of OpenAPI where many fields
|
|
124
|
+
can contain either a Link object or a Reference object ($ref).
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
node: The YAML node to parse
|
|
128
|
+
context: Parsing context
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
A Link, Reference, or ValueSource if the node is invalid
|
|
132
|
+
"""
|
|
133
|
+
# Check if it's a reference (has $ref key)
|
|
134
|
+
if isinstance(node, yaml.MappingNode):
|
|
135
|
+
for key_node, _ in node.value:
|
|
136
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
137
|
+
if key == "$ref":
|
|
138
|
+
return build_reference(node, context)
|
|
139
|
+
|
|
140
|
+
# Otherwise, try to build as Link
|
|
141
|
+
return build(node, context)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, replace
|
|
2
|
+
|
|
3
|
+
from ruamel import yaml
|
|
4
|
+
|
|
5
|
+
from ..context import Context
|
|
6
|
+
from ..fields import fixed_field
|
|
7
|
+
from ..sources import FieldSource, KeySource, ValueSource, YAMLInvalidValue, YAMLValue
|
|
8
|
+
from .builders import build_field_source, build_model
|
|
9
|
+
from .encoding import Encoding
|
|
10
|
+
from .encoding import build as build_encoding
|
|
11
|
+
from .example import Example
|
|
12
|
+
from .reference import Reference
|
|
13
|
+
from .schema import Schema
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ["MediaType", "build"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class MediaType:
|
|
21
|
+
"""
|
|
22
|
+
Media Type Object representation for OpenAPI 3.0.
|
|
23
|
+
|
|
24
|
+
Each Media Type Object provides schema and examples for the media type identified by its key.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
root_node: The top-level node representing the entire Media Type object in the original source file
|
|
28
|
+
schema: The schema defining the content of the request, response, or parameter.
|
|
29
|
+
example: Example of the media type. The example SHOULD match the specified schema and encoding properties if present.
|
|
30
|
+
examples: Examples of the media type. Each example SHOULD contain a value in the correct format as specified in the parameter encoding.
|
|
31
|
+
encoding: A map between a property name and its encoding information. The key, being the property name,
|
|
32
|
+
MUST exist in the schema as a property. The encoding object SHALL only apply to requestBody objects
|
|
33
|
+
when the media type is multipart or application/x-www-form-urlencoded.
|
|
34
|
+
extensions: Specification extensions (x-* fields)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
root_node: yaml.Node
|
|
38
|
+
schema: FieldSource["Schema | Reference"] | None = fixed_field()
|
|
39
|
+
example: FieldSource[YAMLValue] | None = fixed_field()
|
|
40
|
+
examples: FieldSource[dict[KeySource[str], "Example | Reference"]] | None = fixed_field()
|
|
41
|
+
encoding: FieldSource[dict[KeySource[str], Encoding]] | None = fixed_field()
|
|
42
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def build(
|
|
46
|
+
root: yaml.Node, context: Context | None = None
|
|
47
|
+
) -> MediaType | ValueSource[YAMLInvalidValue]:
|
|
48
|
+
"""
|
|
49
|
+
Build a MediaType object from a YAML node.
|
|
50
|
+
|
|
51
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
52
|
+
model that provides complete source fidelity for inspection and validation.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
56
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
A MediaType object if the node is valid, or a ValueSource containing
|
|
60
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
61
|
+
and its source location for validation).
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
from ruamel.yaml import YAML
|
|
65
|
+
yaml = YAML()
|
|
66
|
+
root = yaml.compose('''
|
|
67
|
+
schema:
|
|
68
|
+
type: string
|
|
69
|
+
examples:
|
|
70
|
+
user:
|
|
71
|
+
value: John Doe
|
|
72
|
+
''')
|
|
73
|
+
media_type = build(root)
|
|
74
|
+
assert media_type.schema.value.type.value == 'string'
|
|
75
|
+
"""
|
|
76
|
+
context = context or Context()
|
|
77
|
+
|
|
78
|
+
# Use build_model for initial construction
|
|
79
|
+
media_type = build_model(root, MediaType, context=context)
|
|
80
|
+
|
|
81
|
+
# If build_model returned ValueSource (invalid node), return it immediately
|
|
82
|
+
if not isinstance(media_type, MediaType):
|
|
83
|
+
return media_type
|
|
84
|
+
|
|
85
|
+
# Manually handle nested complex fields
|
|
86
|
+
for key_node, value_node in root.value:
|
|
87
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
88
|
+
|
|
89
|
+
if key == "encoding":
|
|
90
|
+
# Handle encoding field - map of Encoding objects
|
|
91
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
92
|
+
encoding_dict: dict[KeySource[str], Encoding | ValueSource[YAMLInvalidValue]] = {}
|
|
93
|
+
for encoding_key_node, encoding_value_node in value_node.value:
|
|
94
|
+
encoding_key = context.yaml_constructor.construct_yaml_str(encoding_key_node)
|
|
95
|
+
# Build Encoding - child builder handles invalid nodes
|
|
96
|
+
encoding_obj = build_encoding(encoding_value_node, context)
|
|
97
|
+
encoding_dict[KeySource(value=encoding_key, key_node=encoding_key_node)] = (
|
|
98
|
+
encoding_obj
|
|
99
|
+
)
|
|
100
|
+
encoding = FieldSource(
|
|
101
|
+
value=encoding_dict, key_node=key_node, value_node=value_node
|
|
102
|
+
)
|
|
103
|
+
media_type = replace(media_type, encoding=encoding)
|
|
104
|
+
else:
|
|
105
|
+
# Not a mapping - preserve as-is for validation
|
|
106
|
+
encoding = build_field_source(key_node, value_node, context)
|
|
107
|
+
media_type = replace(media_type, encoding=encoding)
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
return media_type
|
|
@@ -2,16 +2,10 @@ from dataclasses import dataclass, field
|
|
|
2
2
|
|
|
3
3
|
from ruamel import yaml
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
FieldSource,
|
|
10
|
-
KeySource,
|
|
11
|
-
ValueSource,
|
|
12
|
-
YAMLInvalidValue,
|
|
13
|
-
YAMLValue,
|
|
14
|
-
)
|
|
5
|
+
from ..context import Context
|
|
6
|
+
from ..fields import fixed_field
|
|
7
|
+
from ..sources import FieldSource, KeySource, ValueSource, YAMLInvalidValue, YAMLValue
|
|
8
|
+
from .builders import build_model
|
|
15
9
|
|
|
16
10
|
|
|
17
11
|
__all__ = ["OAuthFlow", "build"]
|
|
@@ -3,18 +3,12 @@ from typing import Any
|
|
|
3
3
|
|
|
4
4
|
from ruamel import yaml
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
ValueSource,
|
|
13
|
-
YAMLInvalidValue,
|
|
14
|
-
YAMLValue,
|
|
15
|
-
)
|
|
16
|
-
from jentic.apitools.openapi.datamodels.low.v30.oauth_flow import OAuthFlow
|
|
17
|
-
from jentic.apitools.openapi.datamodels.low.v30.oauth_flow import build as build_oauth_flow
|
|
6
|
+
from ..context import Context
|
|
7
|
+
from ..extractors import extract_extension_fields
|
|
8
|
+
from ..fields import fixed_field, fixed_fields
|
|
9
|
+
from ..sources import FieldSource, KeySource, ValueSource, YAMLInvalidValue, YAMLValue
|
|
10
|
+
from .oauth_flow import OAuthFlow
|
|
11
|
+
from .oauth_flow import build as build_oauth_flow
|
|
18
12
|
|
|
19
13
|
|
|
20
14
|
__all__ = ["OAuthFlows", "build"]
|
|
@@ -74,9 +68,7 @@ def build(
|
|
|
74
68
|
flows = build(root)
|
|
75
69
|
assert flows.implicit.value.authorization_url.value == 'https://example.com/auth'
|
|
76
70
|
"""
|
|
77
|
-
|
|
78
|
-
if context is None:
|
|
79
|
-
context = Context()
|
|
71
|
+
context = context or Context()
|
|
80
72
|
|
|
81
73
|
if not isinstance(root, yaml.MappingNode):
|
|
82
74
|
# Preserve invalid root data instead of returning None
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, replace
|
|
2
|
+
|
|
3
|
+
from ruamel import yaml
|
|
4
|
+
|
|
5
|
+
from ..context import Context
|
|
6
|
+
from ..fields import fixed_field
|
|
7
|
+
from ..sources import FieldSource, KeySource, ValueSource, YAMLInvalidValue, YAMLValue
|
|
8
|
+
from .builders import build_field_source, build_model
|
|
9
|
+
from .components import Components
|
|
10
|
+
from .components import build as build_components
|
|
11
|
+
from .external_documentation import ExternalDocumentation
|
|
12
|
+
from .info import Info
|
|
13
|
+
from .info import build as build_info
|
|
14
|
+
from .paths import Paths
|
|
15
|
+
from .paths import build as build_paths
|
|
16
|
+
from .security_requirement import SecurityRequirement
|
|
17
|
+
from .server import Server
|
|
18
|
+
from .tag import Tag
|
|
19
|
+
from .tag import build as build_tag
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = ["OpenAPI30", "build"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class OpenAPI30:
|
|
27
|
+
"""
|
|
28
|
+
OpenAPI Object representation for OpenAPI 3.0.
|
|
29
|
+
|
|
30
|
+
This is the root document object of the OpenAPI document.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
root_node: The top-level node representing the entire OpenAPI object in the original source file
|
|
34
|
+
openapi: REQUIRED. The version number of the OpenAPI Specification that the document uses.
|
|
35
|
+
Must match the specification version (e.g., "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.0.4").
|
|
36
|
+
info: REQUIRED. Provides metadata about the API (title, version, description, etc.)
|
|
37
|
+
paths: REQUIRED. The available paths and operations for the API
|
|
38
|
+
servers: An array of Server Objects providing connectivity information to target servers
|
|
39
|
+
components: An element to hold reusable objects for different aspects of the OAS
|
|
40
|
+
security: A declaration of which security mechanisms can be used across the API
|
|
41
|
+
tags: A list of tags used by the specification with additional metadata
|
|
42
|
+
external_docs: Additional external documentation
|
|
43
|
+
extensions: Specification extensions (x-* fields)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
root_node: yaml.Node
|
|
47
|
+
openapi: FieldSource[str] | None = fixed_field()
|
|
48
|
+
info: FieldSource[Info] | None = fixed_field()
|
|
49
|
+
servers: FieldSource[list["Server"]] | None = fixed_field()
|
|
50
|
+
paths: FieldSource[Paths] | None = fixed_field()
|
|
51
|
+
components: FieldSource[Components] | None = fixed_field()
|
|
52
|
+
security: FieldSource[list["SecurityRequirement"]] | None = fixed_field()
|
|
53
|
+
tags: FieldSource[list[Tag]] | None = fixed_field()
|
|
54
|
+
external_docs: FieldSource["ExternalDocumentation"] | None = fixed_field(
|
|
55
|
+
metadata={"yaml_name": "externalDocs"}
|
|
56
|
+
)
|
|
57
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build(
|
|
61
|
+
root: yaml.Node, context: Context | None = None
|
|
62
|
+
) -> OpenAPI30 | ValueSource[YAMLInvalidValue]:
|
|
63
|
+
"""
|
|
64
|
+
Build an OpenAPI object from a YAML node.
|
|
65
|
+
|
|
66
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
67
|
+
model that provides complete source fidelity for inspection and validation.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
71
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
An OpenAPI object if the node is valid, or a ValueSource containing
|
|
75
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
76
|
+
and its source location for validation).
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
from ruamel.yaml import YAML
|
|
80
|
+
yaml = YAML()
|
|
81
|
+
root = yaml.compose('''
|
|
82
|
+
openapi: 3.0.4
|
|
83
|
+
info:
|
|
84
|
+
title: Sample API
|
|
85
|
+
version: 1.0.0
|
|
86
|
+
paths:
|
|
87
|
+
/users:
|
|
88
|
+
get:
|
|
89
|
+
responses:
|
|
90
|
+
'200':
|
|
91
|
+
description: Success
|
|
92
|
+
''')
|
|
93
|
+
openapi_doc = build(root)
|
|
94
|
+
assert openapi_doc.openapi.value == '3.0.4'
|
|
95
|
+
assert openapi_doc.info.value.title.value == 'Sample API'
|
|
96
|
+
"""
|
|
97
|
+
context = context or Context()
|
|
98
|
+
|
|
99
|
+
# Use build_model for initial construction
|
|
100
|
+
openapi_obj = build_model(root, OpenAPI30, context=context)
|
|
101
|
+
|
|
102
|
+
# If build_model returned ValueSource (invalid node), return it immediately
|
|
103
|
+
if not isinstance(openapi_obj, OpenAPI30):
|
|
104
|
+
return openapi_obj
|
|
105
|
+
|
|
106
|
+
# Manually handle nested complex fields and list fields with custom builders
|
|
107
|
+
replacements = {}
|
|
108
|
+
for key_node, value_node in root.value:
|
|
109
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
110
|
+
|
|
111
|
+
if key == "info":
|
|
112
|
+
# Handle info field - Info object
|
|
113
|
+
replacements["info"] = FieldSource(
|
|
114
|
+
value=build_info(value_node, context), key_node=key_node, value_node=value_node
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
elif key == "paths":
|
|
118
|
+
# Handle paths field - Paths object
|
|
119
|
+
replacements["paths"] = FieldSource(
|
|
120
|
+
value=build_paths(value_node, context), key_node=key_node, value_node=value_node
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
elif key == "components":
|
|
124
|
+
# Handle components field - Components object
|
|
125
|
+
replacements["components"] = FieldSource(
|
|
126
|
+
value=build_components(value_node, context),
|
|
127
|
+
key_node=key_node,
|
|
128
|
+
value_node=value_node,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
elif key == "tags":
|
|
132
|
+
# Handle tags field - array of Tag objects
|
|
133
|
+
if isinstance(value_node, yaml.SequenceNode):
|
|
134
|
+
tags_list = []
|
|
135
|
+
for tag_node in value_node.value:
|
|
136
|
+
tag_obj = build_tag(tag_node, context)
|
|
137
|
+
tags_list.append(tag_obj)
|
|
138
|
+
replacements["tags"] = FieldSource(
|
|
139
|
+
value=tags_list, key_node=key_node, value_node=value_node
|
|
140
|
+
)
|
|
141
|
+
else:
|
|
142
|
+
# Not a sequence - preserve as-is for validation
|
|
143
|
+
replacements["tags"] = build_field_source(key_node, value_node, context)
|
|
144
|
+
|
|
145
|
+
# Apply all replacements at once
|
|
146
|
+
if replacements:
|
|
147
|
+
openapi_obj = replace(openapi_obj, **replacements)
|
|
148
|
+
|
|
149
|
+
return openapi_obj
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, replace
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from ruamel import yaml
|
|
5
|
+
|
|
6
|
+
from ..context import Context
|
|
7
|
+
from ..fields import fixed_field
|
|
8
|
+
from ..sources import FieldSource, KeySource, ValueSource, YAMLInvalidValue, YAMLValue
|
|
9
|
+
from .builders import build_model
|
|
10
|
+
from .external_documentation import ExternalDocumentation
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .callback import Callback
|
|
15
|
+
|
|
16
|
+
from .parameter import Parameter
|
|
17
|
+
from .reference import Reference
|
|
18
|
+
from .request_body import RequestBody, build_request_body_or_reference
|
|
19
|
+
from .responses import Responses
|
|
20
|
+
from .responses import build as build_responses
|
|
21
|
+
from .security_requirement import SecurityRequirement
|
|
22
|
+
from .server import Server
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["Operation", "build"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True, slots=True)
|
|
29
|
+
class Operation:
|
|
30
|
+
"""
|
|
31
|
+
Operation Object representation for OpenAPI 3.0.
|
|
32
|
+
|
|
33
|
+
Describes a single API operation on a path.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
root_node: The top-level node representing the entire Operation object in the original source file
|
|
37
|
+
tags: List of tags for API documentation control
|
|
38
|
+
summary: Short summary of what the operation does
|
|
39
|
+
description: Verbose explanation of the operation behavior (may contain CommonMark syntax)
|
|
40
|
+
external_docs: Additional external documentation for this operation
|
|
41
|
+
operation_id: Unique string used to identify the operation
|
|
42
|
+
parameters: List of parameters that are applicable for this operation
|
|
43
|
+
request_body: Request body applicable for this operation
|
|
44
|
+
responses: REQUIRED. The list of possible responses as they are returned from executing this operation
|
|
45
|
+
callbacks: Map of possible out-of-band callbacks related to the parent operation
|
|
46
|
+
deprecated: Declares this operation to be deprecated
|
|
47
|
+
security: Declaration of which security mechanisms can be used for this operation
|
|
48
|
+
servers: Alternative server array to service this operation
|
|
49
|
+
extensions: Specification extensions (x-* fields)
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
root_node: yaml.Node
|
|
53
|
+
tags: FieldSource[list[ValueSource[str]]] | None = fixed_field()
|
|
54
|
+
summary: FieldSource[str] | None = fixed_field()
|
|
55
|
+
description: FieldSource[str] | None = fixed_field()
|
|
56
|
+
external_docs: FieldSource["ExternalDocumentation"] | None = fixed_field(
|
|
57
|
+
metadata={"yaml_name": "externalDocs"}
|
|
58
|
+
)
|
|
59
|
+
operation_id: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "operationId"})
|
|
60
|
+
parameters: FieldSource[list["Parameter | Reference"]] | None = fixed_field()
|
|
61
|
+
request_body: FieldSource[RequestBody | Reference] | None = fixed_field(
|
|
62
|
+
metadata={"yaml_name": "requestBody"}
|
|
63
|
+
)
|
|
64
|
+
responses: FieldSource[Responses] | None = fixed_field()
|
|
65
|
+
callbacks: FieldSource[dict[KeySource[str], "Callback | Reference"]] | None = fixed_field()
|
|
66
|
+
deprecated: FieldSource[bool] | None = fixed_field()
|
|
67
|
+
security: FieldSource[list["SecurityRequirement"]] | None = fixed_field()
|
|
68
|
+
servers: FieldSource[list["Server"]] | None = fixed_field()
|
|
69
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def build(
|
|
73
|
+
root: yaml.Node, context: Context | None = None
|
|
74
|
+
) -> Operation | ValueSource[YAMLInvalidValue]:
|
|
75
|
+
"""
|
|
76
|
+
Build an Operation object from a YAML node.
|
|
77
|
+
|
|
78
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
79
|
+
model that provides complete source fidelity for inspection and validation.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
83
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
An Operation object if the node is valid, or a ValueSource containing
|
|
87
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
88
|
+
and its source location for validation).
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
from ruamel.yaml import YAML
|
|
92
|
+
yaml = YAML()
|
|
93
|
+
root = yaml.compose('''
|
|
94
|
+
summary: List users
|
|
95
|
+
operationId: listUsers
|
|
96
|
+
responses:
|
|
97
|
+
'200':
|
|
98
|
+
description: successful operation
|
|
99
|
+
''')
|
|
100
|
+
operation = build(root)
|
|
101
|
+
assert operation.summary.value == 'List users'
|
|
102
|
+
"""
|
|
103
|
+
context = context or Context()
|
|
104
|
+
|
|
105
|
+
# Use build_model for initial construction
|
|
106
|
+
operation = build_model(root, Operation, context=context)
|
|
107
|
+
|
|
108
|
+
# If build_model returned ValueSource (invalid node), return it immediately
|
|
109
|
+
if not isinstance(operation, Operation):
|
|
110
|
+
return operation
|
|
111
|
+
|
|
112
|
+
# Manually handle nested complex fields
|
|
113
|
+
replacements = {}
|
|
114
|
+
for key_node, value_node in root.value:
|
|
115
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
116
|
+
|
|
117
|
+
if key == "requestBody":
|
|
118
|
+
# Handle requestBody field - RequestBody or Reference
|
|
119
|
+
request_body_or_reference = build_request_body_or_reference(value_node, context)
|
|
120
|
+
replacements["request_body"] = FieldSource(
|
|
121
|
+
value=request_body_or_reference, key_node=key_node, value_node=value_node
|
|
122
|
+
)
|
|
123
|
+
elif key == "responses":
|
|
124
|
+
# Handle responses field - Responses object
|
|
125
|
+
responses_obj = build_responses(value_node, context)
|
|
126
|
+
replacements["responses"] = FieldSource(
|
|
127
|
+
value=responses_obj, key_node=key_node, value_node=value_node
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Apply all replacements at once
|
|
131
|
+
if replacements:
|
|
132
|
+
operation = replace(operation, **replacements)
|
|
133
|
+
|
|
134
|
+
return operation
|