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,108 @@
|
|
|
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
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = ["Paths", "build"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class Paths:
|
|
17
|
+
"""
|
|
18
|
+
Paths Object representation for OpenAPI 3.1.
|
|
19
|
+
|
|
20
|
+
Holds the relative paths to the individual endpoints and their operations.
|
|
21
|
+
The paths are appended to the server URL to construct the full URL.
|
|
22
|
+
|
|
23
|
+
Path field names MUST begin with a forward slash (/). Path templating is supported
|
|
24
|
+
(e.g., /users/{id}). When matching URLs, concrete (non-templated) paths are matched
|
|
25
|
+
before templated paths. Templated paths with the same hierarchy but different templated
|
|
26
|
+
names are not allowed.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
root_node: The top-level node representing the entire Paths object in the original source file
|
|
30
|
+
paths: Map of path strings to Path Item Objects. Each key is a relative path that MUST begin
|
|
31
|
+
with a forward slash (/). Supports path templating with curly braces.
|
|
32
|
+
extensions: Specification extensions (x-* fields)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
root_node: yaml.Node
|
|
36
|
+
paths: dict[KeySource[str], PathItem] = field(default_factory=dict)
|
|
37
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def build(root: yaml.Node, context: Context | None = None) -> Paths | ValueSource[YAMLInvalidValue]:
|
|
41
|
+
"""
|
|
42
|
+
Build a Paths object from a YAML node.
|
|
43
|
+
|
|
44
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
45
|
+
model that provides complete source fidelity for inspection and validation.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
49
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
A Paths object if the node is valid, or a ValueSource containing
|
|
53
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
54
|
+
and its source location for validation).
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
from ruamel.yaml import YAML
|
|
58
|
+
yaml = YAML()
|
|
59
|
+
root = yaml.compose('''
|
|
60
|
+
/users:
|
|
61
|
+
get:
|
|
62
|
+
summary: Get all users
|
|
63
|
+
responses:
|
|
64
|
+
'200':
|
|
65
|
+
description: Success
|
|
66
|
+
/users/{id}:
|
|
67
|
+
get:
|
|
68
|
+
summary: Get user by ID
|
|
69
|
+
parameters:
|
|
70
|
+
- name: id
|
|
71
|
+
in: path
|
|
72
|
+
required: true
|
|
73
|
+
schema:
|
|
74
|
+
type: integer
|
|
75
|
+
responses:
|
|
76
|
+
'200':
|
|
77
|
+
description: Success
|
|
78
|
+
''')
|
|
79
|
+
paths = build(root)
|
|
80
|
+
assert '/users' in {k.value for k in paths.paths.keys()}
|
|
81
|
+
"""
|
|
82
|
+
context = context or Context()
|
|
83
|
+
|
|
84
|
+
# Check if root is a MappingNode, if not return ValueSource with invalid data
|
|
85
|
+
if not isinstance(root, yaml.MappingNode):
|
|
86
|
+
value = context.yaml_constructor.construct_object(root, deep=True)
|
|
87
|
+
return ValueSource(value=value, value_node=root)
|
|
88
|
+
|
|
89
|
+
# Extract extensions first
|
|
90
|
+
extensions = extract_extension_fields(root, context)
|
|
91
|
+
extension_properties = {k.value for k in extensions.keys()}
|
|
92
|
+
|
|
93
|
+
# Process each field to determine if it's a path (not an extension)
|
|
94
|
+
paths = {}
|
|
95
|
+
|
|
96
|
+
for key_node, value_node in root.value:
|
|
97
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
98
|
+
|
|
99
|
+
if key not in extension_properties and key.startswith("/"):
|
|
100
|
+
# Path field (starts with / and not an extension) - build as Path Item
|
|
101
|
+
paths[KeySource(value=key, key_node=key_node)] = build_path_item(value_node, context)
|
|
102
|
+
|
|
103
|
+
# Create and return the Paths object with collected data
|
|
104
|
+
return Paths(
|
|
105
|
+
root_node=root,
|
|
106
|
+
paths=paths,
|
|
107
|
+
extensions=extensions,
|
|
108
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from ruamel import yaml
|
|
4
|
+
|
|
5
|
+
from ..context import Context
|
|
6
|
+
from ..fields import fixed_field
|
|
7
|
+
from ..sources import FieldSource, ValueSource, YAMLInvalidValue
|
|
8
|
+
from .builders import build_model
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["Reference", "build"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class Reference:
|
|
16
|
+
"""
|
|
17
|
+
Reference Object representation for OpenAPI 3.1.
|
|
18
|
+
|
|
19
|
+
Allows for a reference to another document.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
root_node: The top-level node representing the entire Reference object in the original source file
|
|
23
|
+
ref: REQUIRED. The reference identifier. This MUST be in the form of a URI.
|
|
24
|
+
summary: A short summary which by default SHOULD override that of the referenced component. If the referenced object-type does not allow a summary field, then this field has no effect.
|
|
25
|
+
description: A description which by default SHOULD override that of the referenced component. CommonMark syntax MAY be used for rich text representation. If the referenced object-type does not allow a description field, then this field has no effect.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
root_node: yaml.Node
|
|
29
|
+
ref: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "$ref"})
|
|
30
|
+
summary: FieldSource[str] | None = fixed_field()
|
|
31
|
+
description: FieldSource[str] | None = fixed_field()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build(
|
|
35
|
+
root: yaml.Node, context: Context | None = None
|
|
36
|
+
) -> Reference | ValueSource[YAMLInvalidValue]:
|
|
37
|
+
"""
|
|
38
|
+
Build a Reference object from a YAML node.
|
|
39
|
+
|
|
40
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
41
|
+
model that provides complete source fidelity for inspection and validation.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
45
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A Reference object if the node is valid, or a ValueSource containing
|
|
49
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
50
|
+
and its source location for validation).
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
from ruamel.yaml import YAML
|
|
54
|
+
yaml = YAML()
|
|
55
|
+
root = yaml.compose('''
|
|
56
|
+
$ref: '#/components/schemas/Pet'
|
|
57
|
+
summary: A Pet reference
|
|
58
|
+
description: Reference to the Pet schema in components
|
|
59
|
+
''')
|
|
60
|
+
reference = build(root)
|
|
61
|
+
assert reference.ref.value == '#/components/schemas/Pet'
|
|
62
|
+
assert reference.summary.value == 'A Pet reference'
|
|
63
|
+
assert reference.description.value == 'Reference to the Pet schema in components'
|
|
64
|
+
"""
|
|
65
|
+
return build_model(root, Reference, context=context)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
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_model
|
|
9
|
+
from .media_type import MediaType
|
|
10
|
+
from .reference import Reference
|
|
11
|
+
from .reference import build as build_reference
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = ["RequestBody", "build", "build_request_body_or_reference"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class RequestBody:
|
|
19
|
+
"""
|
|
20
|
+
Request Body Object representation for OpenAPI 3.1.
|
|
21
|
+
|
|
22
|
+
Describes a single request body.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
root_node: The top-level node representing the entire Request Body object in the original source file
|
|
26
|
+
description: A brief description of the request body. CommonMark syntax MAY be used for rich text representation.
|
|
27
|
+
content: The content of the request body. The key is a media type or media type range and the value describes it.
|
|
28
|
+
For requests that match multiple keys, only the most specific key is applicable.
|
|
29
|
+
required: Determines if the request body is required in the request. Defaults to false.
|
|
30
|
+
extensions: Specification extensions (x-* fields)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
root_node: yaml.Node
|
|
34
|
+
description: FieldSource[str] | None = fixed_field()
|
|
35
|
+
content: FieldSource[dict[KeySource[str], "MediaType"]] | None = fixed_field()
|
|
36
|
+
required: FieldSource[bool] | None = fixed_field()
|
|
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
|
+
) -> RequestBody | ValueSource[YAMLInvalidValue]:
|
|
43
|
+
"""
|
|
44
|
+
Build a RequestBody 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 RequestBody 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
|
+
description: user to add to the system
|
|
63
|
+
required: true
|
|
64
|
+
content:
|
|
65
|
+
application/json:
|
|
66
|
+
schema:
|
|
67
|
+
type: object
|
|
68
|
+
''')
|
|
69
|
+
request_body = build(root)
|
|
70
|
+
assert request_body.description.value == 'user to add to the system'
|
|
71
|
+
"""
|
|
72
|
+
context = context or Context()
|
|
73
|
+
|
|
74
|
+
# Use build_model for initial construction
|
|
75
|
+
request_body = build_model(root, RequestBody, context=context)
|
|
76
|
+
|
|
77
|
+
# If build_model returned ValueSource (invalid node), return it immediately
|
|
78
|
+
if not isinstance(request_body, RequestBody):
|
|
79
|
+
return request_body
|
|
80
|
+
|
|
81
|
+
return request_body
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def build_request_body_or_reference(
|
|
85
|
+
node: yaml.Node, context: Context
|
|
86
|
+
) -> RequestBody | Reference | ValueSource[YAMLInvalidValue]:
|
|
87
|
+
"""
|
|
88
|
+
Build either a RequestBody or Reference from a YAML node.
|
|
89
|
+
|
|
90
|
+
This helper handles the polymorphic nature of OpenAPI where many fields
|
|
91
|
+
can contain either a RequestBody object or a Reference object ($ref).
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
node: The YAML node to parse
|
|
95
|
+
context: Parsing context
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
A RequestBody, Reference, or ValueSource if the node is invalid
|
|
99
|
+
"""
|
|
100
|
+
# Check if it's a reference (has $ref key)
|
|
101
|
+
if isinstance(node, yaml.MappingNode):
|
|
102
|
+
for key_node, _ in node.value:
|
|
103
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
104
|
+
if key == "$ref":
|
|
105
|
+
return build_reference(node, context)
|
|
106
|
+
|
|
107
|
+
# Otherwise, try to build as RequestBody
|
|
108
|
+
return build(node, context)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
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_model
|
|
9
|
+
from .header import Header
|
|
10
|
+
from .link import Link
|
|
11
|
+
from .media_type import MediaType
|
|
12
|
+
from .reference import Reference
|
|
13
|
+
from .reference import build as build_reference
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = ["Response", "build", "build_response_or_reference"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class Response:
|
|
21
|
+
"""
|
|
22
|
+
Response Object representation for OpenAPI 3.1.
|
|
23
|
+
|
|
24
|
+
Describes a single response from an API Operation, including design-time, static links
|
|
25
|
+
to operations based on the response.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
root_node: The top-level node representing the entire Response object in the original source file
|
|
29
|
+
description: A description of the response. CommonMark syntax MAY be used for rich text representation.
|
|
30
|
+
headers: Maps a header name to its definition.
|
|
31
|
+
content: A map containing descriptions of potential response payloads. The key is a media type or media type range
|
|
32
|
+
and the value describes it. For responses that match multiple keys, only the most specific key is applicable.
|
|
33
|
+
links: A map of operations links that can be followed from the response. The key is a short name for the link,
|
|
34
|
+
following the naming constraints of the Components Object.
|
|
35
|
+
extensions: Specification extensions (x-* fields)
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
root_node: yaml.Node
|
|
39
|
+
description: FieldSource[str] | None = fixed_field()
|
|
40
|
+
headers: FieldSource[dict[KeySource[str], "Header | Reference"]] | None = fixed_field()
|
|
41
|
+
content: FieldSource[dict[KeySource[str], "MediaType"]] | None = fixed_field()
|
|
42
|
+
links: FieldSource[dict[KeySource[str], "Link | Reference"]] | None = fixed_field()
|
|
43
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build(
|
|
47
|
+
root: yaml.Node, context: Context | None = None
|
|
48
|
+
) -> Response | ValueSource[YAMLInvalidValue]:
|
|
49
|
+
"""
|
|
50
|
+
Build a Response object from a YAML node.
|
|
51
|
+
|
|
52
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
53
|
+
model that provides complete source fidelity for inspection and validation.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
57
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
A Response object if the node is valid, or a ValueSource containing
|
|
61
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
62
|
+
and its source location for validation).
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
from ruamel.yaml import YAML
|
|
66
|
+
yaml = YAML()
|
|
67
|
+
root = yaml.compose('''
|
|
68
|
+
description: successful operation
|
|
69
|
+
content:
|
|
70
|
+
application/json:
|
|
71
|
+
schema:
|
|
72
|
+
type: object
|
|
73
|
+
''')
|
|
74
|
+
response = build(root)
|
|
75
|
+
assert response.description.value == 'successful operation'
|
|
76
|
+
"""
|
|
77
|
+
return build_model(root, Response, context=context)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def build_response_or_reference(
|
|
81
|
+
node: yaml.Node, context: Context
|
|
82
|
+
) -> Response | Reference | ValueSource[YAMLInvalidValue]:
|
|
83
|
+
"""
|
|
84
|
+
Build either a Response or Reference from a YAML node.
|
|
85
|
+
|
|
86
|
+
This helper handles the polymorphic nature of OpenAPI where many fields
|
|
87
|
+
can contain either a Response object or a Reference object ($ref).
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
node: The YAML node to parse
|
|
91
|
+
context: Parsing context
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
A Response, Reference, or ValueSource if the node is invalid
|
|
95
|
+
"""
|
|
96
|
+
# Check if it's a reference (has $ref key)
|
|
97
|
+
if isinstance(node, yaml.MappingNode):
|
|
98
|
+
for key_node, _ in node.value:
|
|
99
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
100
|
+
if key == "$ref":
|
|
101
|
+
return build_reference(node, context)
|
|
102
|
+
|
|
103
|
+
# Otherwise, try to build as Response
|
|
104
|
+
return build(node, context)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from ruamel import yaml
|
|
6
|
+
|
|
7
|
+
from ..context import Context
|
|
8
|
+
from ..extractors import extract_extension_fields
|
|
9
|
+
from ..fields import fixed_field
|
|
10
|
+
from ..sources import FieldSource, KeySource, ValueSource, YAMLInvalidValue, YAMLValue
|
|
11
|
+
from .reference import Reference
|
|
12
|
+
from .response import Response, build_response_or_reference
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = ["Responses", "build"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class Responses:
|
|
20
|
+
"""
|
|
21
|
+
Responses Object representation for OpenAPI 3.1.
|
|
22
|
+
|
|
23
|
+
A container for the expected responses of an operation. The container maps a HTTP response code
|
|
24
|
+
to the expected response.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
root_node: The top-level node representing the entire Responses object in the original source file
|
|
28
|
+
default: The documentation of responses other than the ones declared for specific HTTP response codes.
|
|
29
|
+
Use this field to cover undeclared responses.
|
|
30
|
+
responses: Maps HTTP status codes to their Response objects. The key is the HTTP status code
|
|
31
|
+
(e.g., "200", "404", "4XX") and the value is a Response object or Reference.
|
|
32
|
+
extensions: Specification extensions (x-* fields)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
root_node: yaml.Node
|
|
36
|
+
default: FieldSource[Response | Reference] | None = fixed_field()
|
|
37
|
+
responses: dict[KeySource[str], Response | Reference] = field(default_factory=dict)
|
|
38
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build(
|
|
42
|
+
root: yaml.Node, context: Context | None = None
|
|
43
|
+
) -> Responses | ValueSource[YAMLInvalidValue]:
|
|
44
|
+
"""
|
|
45
|
+
Build a Responses object from a YAML node.
|
|
46
|
+
|
|
47
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
48
|
+
model that provides complete source fidelity for inspection and validation.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
52
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
A Responses object if the node is valid, or a ValueSource containing
|
|
56
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
57
|
+
and its source location for validation).
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
from ruamel.yaml import YAML
|
|
61
|
+
yaml = YAML()
|
|
62
|
+
root = yaml.compose('''
|
|
63
|
+
'200':
|
|
64
|
+
description: successful operation
|
|
65
|
+
'404':
|
|
66
|
+
description: not found
|
|
67
|
+
default:
|
|
68
|
+
description: unexpected error
|
|
69
|
+
''')
|
|
70
|
+
responses = build(root)
|
|
71
|
+
assert '200' in {k.value for k in responses.responses.keys()}
|
|
72
|
+
"""
|
|
73
|
+
context = context or Context()
|
|
74
|
+
|
|
75
|
+
# Check if root is a MappingNode, if not return ValueSource with invalid data
|
|
76
|
+
if not isinstance(root, yaml.MappingNode):
|
|
77
|
+
value = context.yaml_constructor.construct_object(root, deep=True)
|
|
78
|
+
return ValueSource(value=value, value_node=root)
|
|
79
|
+
|
|
80
|
+
# Process each field to determine if it's default or a status code response
|
|
81
|
+
responses = {}
|
|
82
|
+
default: FieldSource[Response | Reference] | None = None
|
|
83
|
+
|
|
84
|
+
for key_node, value_node in root.value:
|
|
85
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
86
|
+
|
|
87
|
+
if key == "default":
|
|
88
|
+
# Handle default response - can be Response or Reference
|
|
89
|
+
response_or_reference = build_response_or_reference(value_node, context)
|
|
90
|
+
default = cast(
|
|
91
|
+
FieldSource[Response | Reference],
|
|
92
|
+
FieldSource(value=response_or_reference, key_node=key_node, value_node=value_node),
|
|
93
|
+
)
|
|
94
|
+
elif _HTTP_STATUS_CODE_PATTERN.match(key):
|
|
95
|
+
# Valid HTTP status code (100-599) or pattern (1XX-5XX)
|
|
96
|
+
response_or_reference = build_response_or_reference(value_node, context)
|
|
97
|
+
responses[KeySource(value=key, key_node=key_node)] = response_or_reference
|
|
98
|
+
|
|
99
|
+
# Create and return the Responses object with collected data
|
|
100
|
+
return Responses(
|
|
101
|
+
root_node=root,
|
|
102
|
+
default=default,
|
|
103
|
+
responses=responses,
|
|
104
|
+
extensions=extract_extension_fields(root, context),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# Pattern for valid HTTP status codes: 100-599 or wildcard patterns (1XX-5XX)
|
|
109
|
+
_HTTP_STATUS_CODE_PATTERN = re.compile(r"^([1-5]XX|[1-5][0-9]{2})$")
|