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
|
@@ -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.0.
|
|
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})$")
|
|
@@ -3,31 +3,22 @@ from typing import Any, TypeAlias, get_args
|
|
|
3
3
|
|
|
4
4
|
from ruamel import yaml
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
from
|
|
17
|
-
from
|
|
18
|
-
from
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
build as build_external_documentation,
|
|
23
|
-
)
|
|
24
|
-
from jentic.apitools.openapi.datamodels.low.v30.reference import Reference
|
|
25
|
-
from jentic.apitools.openapi.datamodels.low.v30.reference import build as build_reference
|
|
26
|
-
from jentic.apitools.openapi.datamodels.low.v30.xml import XML
|
|
27
|
-
from jentic.apitools.openapi.datamodels.low.v30.xml import build as build_xml
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
__all__ = ["Schema", "NestedSchema", "build"]
|
|
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 .builders import build_field_source
|
|
11
|
+
from .discriminator import Discriminator
|
|
12
|
+
from .discriminator import build as build_discriminator
|
|
13
|
+
from .external_documentation import ExternalDocumentation
|
|
14
|
+
from .external_documentation import build as build_external_documentation
|
|
15
|
+
from .reference import Reference
|
|
16
|
+
from .reference import build as build_reference
|
|
17
|
+
from .xml import XML
|
|
18
|
+
from .xml import build as build_xml
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["Schema", "NestedSchema", "build", "build_schema_or_reference"]
|
|
31
22
|
|
|
32
23
|
|
|
33
24
|
# Type alias for nested schema references
|
|
@@ -52,31 +43,31 @@ class Schema:
|
|
|
52
43
|
|
|
53
44
|
# JSON Schema Core validation keywords
|
|
54
45
|
title: A title for the schema
|
|
55
|
-
|
|
46
|
+
multiple_of: A numeric instance is valid only if division by this value results in an integer
|
|
56
47
|
maximum: Upper limit for a numeric instance
|
|
57
|
-
|
|
48
|
+
exclusive_maximum: If true, the value must be strictly less than maximum
|
|
58
49
|
minimum: Lower limit for a numeric instance
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
exclusive_minimum: If true, the value must be strictly greater than minimum
|
|
51
|
+
max_length: Maximum length of a string instance
|
|
52
|
+
min_length: Minimum length of a string instance
|
|
62
53
|
pattern: A string instance is valid if the regular expression matches the instance successfully
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
max_items: Maximum number of items in an array instance
|
|
55
|
+
min_items: Minimum number of items in an array instance
|
|
56
|
+
unique_items: If true, array items must be unique
|
|
57
|
+
max_properties: Maximum number of properties in an object instance
|
|
58
|
+
min_properties: Minimum number of properties in an object instance
|
|
68
59
|
required: List of required property names
|
|
69
60
|
enum: Fixed set of allowed values
|
|
70
61
|
|
|
71
62
|
# JSON Schema Type and Structure
|
|
72
63
|
type: Value type (string, number, integer, boolean, array, object)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
64
|
+
all_of: Must be valid against all of the subschemas
|
|
65
|
+
one_of: Must be valid against exactly one of the subschemas
|
|
66
|
+
any_of: Must be valid against any of the subschemas
|
|
76
67
|
not_: Must not be valid against the given schema
|
|
77
68
|
items: Schema for array items (or array of schemas for tuple validation)
|
|
78
69
|
properties: Property name to schema mappings
|
|
79
|
-
|
|
70
|
+
additional_properties: Schema for properties not defined in properties, or boolean to allow/disallow
|
|
80
71
|
|
|
81
72
|
# JSON Schema Metadata
|
|
82
73
|
description: A short description. CommonMark syntax MAY be used for rich text representation.
|
|
@@ -86,10 +77,10 @@ class Schema:
|
|
|
86
77
|
# OpenAPI-specific extensions
|
|
87
78
|
nullable: Allows sending a null value
|
|
88
79
|
discriminator: Adds support for polymorphism
|
|
89
|
-
|
|
90
|
-
|
|
80
|
+
read_only: Relevant only for Schema "properties" definitions - sent in response but not in request
|
|
81
|
+
write_only: Relevant only for Schema "properties" definitions - sent in request but not in response
|
|
91
82
|
xml: Additional metadata for XML representations
|
|
92
|
-
|
|
83
|
+
external_docs: Additional external documentation
|
|
93
84
|
example: Example of the media type
|
|
94
85
|
deprecated: Specifies that the schema is deprecated
|
|
95
86
|
|
|
@@ -100,31 +91,37 @@ class Schema:
|
|
|
100
91
|
|
|
101
92
|
# JSON Schema Core validation keywords
|
|
102
93
|
title: FieldSource[str] | None = fixed_field()
|
|
103
|
-
|
|
94
|
+
multiple_of: FieldSource[int | float] | None = fixed_field(metadata={"yaml_name": "multipleOf"})
|
|
104
95
|
maximum: FieldSource[int | float] | None = fixed_field()
|
|
105
|
-
|
|
96
|
+
exclusive_maximum: FieldSource[bool] | None = fixed_field(
|
|
97
|
+
metadata={"yaml_name": "exclusiveMaximum"}
|
|
98
|
+
)
|
|
106
99
|
minimum: FieldSource[int | float] | None = fixed_field()
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
exclusive_minimum: FieldSource[bool] | None = fixed_field(
|
|
101
|
+
metadata={"yaml_name": "exclusiveMinimum"}
|
|
102
|
+
)
|
|
103
|
+
max_length: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "maxLength"})
|
|
104
|
+
min_length: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "minLength"})
|
|
110
105
|
pattern: FieldSource[str] | None = fixed_field()
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
106
|
+
max_items: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "maxItems"})
|
|
107
|
+
min_items: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "minItems"})
|
|
108
|
+
unique_items: FieldSource[bool] | None = fixed_field(metadata={"yaml_name": "uniqueItems"})
|
|
109
|
+
max_properties: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "maxProperties"})
|
|
110
|
+
min_properties: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "minProperties"})
|
|
116
111
|
required: FieldSource[list[ValueSource[str]]] | None = fixed_field()
|
|
117
112
|
enum: FieldSource[list[ValueSource[YAMLValue]]] | None = fixed_field()
|
|
118
113
|
|
|
119
114
|
# JSON Schema Type and Structure (nested schemas)
|
|
120
115
|
type: FieldSource[str] | None = fixed_field()
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
all_of: FieldSource[list[NestedSchema]] | None = fixed_field(metadata={"yaml_name": "allOf"})
|
|
117
|
+
one_of: FieldSource[list[NestedSchema]] | None = fixed_field(metadata={"yaml_name": "oneOf"})
|
|
118
|
+
any_of: FieldSource[list[NestedSchema]] | None = fixed_field(metadata={"yaml_name": "anyOf"})
|
|
124
119
|
not_: FieldSource[NestedSchema] | None = fixed_field(metadata={"yaml_name": "not"})
|
|
125
120
|
items: FieldSource[NestedSchema] | None = fixed_field()
|
|
126
|
-
properties: FieldSource[dict[KeySource[str],
|
|
127
|
-
|
|
121
|
+
properties: FieldSource[dict[KeySource[str], NestedSchema]] | None = fixed_field()
|
|
122
|
+
additional_properties: FieldSource["bool | NestedSchema"] | None = fixed_field(
|
|
123
|
+
metadata={"yaml_name": "additionalProperties"}
|
|
124
|
+
)
|
|
128
125
|
|
|
129
126
|
# JSON Schema Metadata
|
|
130
127
|
description: FieldSource[str] | None = fixed_field()
|
|
@@ -134,10 +131,12 @@ class Schema:
|
|
|
134
131
|
# OpenAPI-specific extensions
|
|
135
132
|
nullable: FieldSource[bool] | None = fixed_field()
|
|
136
133
|
discriminator: FieldSource[Discriminator] | None = fixed_field()
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
read_only: FieldSource[bool] | None = fixed_field(metadata={"yaml_name": "readOnly"})
|
|
135
|
+
write_only: FieldSource[bool] | None = fixed_field(metadata={"yaml_name": "writeOnly"})
|
|
139
136
|
xml: FieldSource[XML] | None = fixed_field()
|
|
140
|
-
|
|
137
|
+
external_docs: FieldSource[ExternalDocumentation] | None = fixed_field(
|
|
138
|
+
metadata={"yaml_name": "externalDocs"}
|
|
139
|
+
)
|
|
141
140
|
example: FieldSource[YAMLValue] | None = fixed_field()
|
|
142
141
|
deprecated: FieldSource[bool] | None = fixed_field()
|
|
143
142
|
|
|
@@ -153,8 +152,8 @@ def build(
|
|
|
153
152
|
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
154
153
|
model that provides complete source fidelity for inspection and validation.
|
|
155
154
|
|
|
156
|
-
Note: Schema is self-referential (can contain other Schema objects in
|
|
157
|
-
items, properties,
|
|
155
|
+
Note: Schema is self-referential (can contain other Schema objects in all_of, one_of, any_of, not_,
|
|
156
|
+
items, properties, additional_properties). The builder handles nested Schema objects by preserving
|
|
158
157
|
them as raw YAML values, letting validation layers interpret them.
|
|
159
158
|
|
|
160
159
|
Args:
|
|
@@ -172,11 +171,9 @@ def build(
|
|
|
172
171
|
root = yaml.compose("type: string\\nminLength: 1\\nmaxLength: 100")
|
|
173
172
|
schema = build(root)
|
|
174
173
|
assert schema.type.value == 'string'
|
|
175
|
-
assert schema.
|
|
174
|
+
assert schema.min_length.value == 1
|
|
176
175
|
"""
|
|
177
|
-
|
|
178
|
-
if context is None:
|
|
179
|
-
context = Context()
|
|
176
|
+
context = context or Context()
|
|
180
177
|
|
|
181
178
|
if not isinstance(root, yaml.MappingNode):
|
|
182
179
|
# Preserve invalid root data instead of returning None
|
|
@@ -216,10 +213,7 @@ def build(
|
|
|
216
213
|
FieldSource[int | float],
|
|
217
214
|
FieldSource[YAMLValue],
|
|
218
215
|
}:
|
|
219
|
-
|
|
220
|
-
field_values[field_name] = FieldSource(
|
|
221
|
-
value=value, key_node=key_node, value_node=value_node
|
|
222
|
-
)
|
|
216
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
223
217
|
|
|
224
218
|
# Handle list with ValueSource wrapping for each item (e.g., required, enum fields)
|
|
225
219
|
elif field_type_args & {
|
|
@@ -236,32 +230,26 @@ def build(
|
|
|
236
230
|
)
|
|
237
231
|
else:
|
|
238
232
|
# Not a sequence - preserve as-is for validation
|
|
239
|
-
|
|
240
|
-
field_values[field_name] = FieldSource(
|
|
241
|
-
value=value, key_node=key_node, value_node=value_node
|
|
242
|
-
)
|
|
233
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
243
234
|
|
|
244
235
|
# Recursive schema list fields (allOf, oneOf, anyOf)
|
|
245
236
|
elif key in ("allOf", "oneOf", "anyOf"):
|
|
246
237
|
if isinstance(value_node, yaml.SequenceNode):
|
|
247
238
|
schemas = []
|
|
248
239
|
for item_node in value_node.value:
|
|
249
|
-
|
|
250
|
-
schemas.append(
|
|
240
|
+
schema_or_reference = build_schema_or_reference(item_node, context)
|
|
241
|
+
schemas.append(schema_or_reference)
|
|
251
242
|
field_values[field_name] = FieldSource(
|
|
252
243
|
value=schemas, key_node=key_node, value_node=value_node
|
|
253
244
|
)
|
|
254
245
|
else:
|
|
255
246
|
# Not a sequence - preserve as-is for validation
|
|
256
|
-
|
|
257
|
-
field_values[field_name] = FieldSource(
|
|
258
|
-
value=value, key_node=key_node, value_node=value_node
|
|
259
|
-
)
|
|
247
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
260
248
|
# Recursive schema single fields (not, items)
|
|
261
249
|
elif key in ("not", "items"):
|
|
262
|
-
|
|
250
|
+
schema_or_reference = build_schema_or_reference(value_node, context)
|
|
263
251
|
field_values[field_name] = FieldSource(
|
|
264
|
-
value=
|
|
252
|
+
value=schema_or_reference, key_node=key_node, value_node=value_node
|
|
265
253
|
)
|
|
266
254
|
# additionalProperties (boolean | schema | reference)
|
|
267
255
|
elif key == "additionalProperties":
|
|
@@ -270,36 +258,32 @@ def build(
|
|
|
270
258
|
isinstance(value_node, yaml.ScalarNode)
|
|
271
259
|
and value_node.tag == "tag:yaml.org,2002:bool"
|
|
272
260
|
):
|
|
273
|
-
|
|
274
|
-
field_values[field_name] = FieldSource(
|
|
275
|
-
value=value, key_node=key_node, value_node=value_node
|
|
276
|
-
)
|
|
261
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
277
262
|
else:
|
|
278
263
|
# It's a schema or reference
|
|
279
|
-
|
|
264
|
+
schema_or_reference = build_schema_or_reference(value_node, context)
|
|
280
265
|
field_values[field_name] = FieldSource(
|
|
281
|
-
value=
|
|
266
|
+
value=schema_or_reference, key_node=key_node, value_node=value_node
|
|
282
267
|
)
|
|
283
|
-
# properties (dict[KeySource[str],
|
|
268
|
+
# properties (dict[KeySource[str], NestedSchema])
|
|
284
269
|
elif key == "properties":
|
|
285
270
|
if isinstance(value_node, yaml.MappingNode):
|
|
286
|
-
properties_dict: dict[KeySource[str],
|
|
271
|
+
properties_dict: dict[KeySource[str], NestedSchema] = {}
|
|
287
272
|
for prop_key_node, prop_value_node in value_node.value:
|
|
288
273
|
prop_key = context.yaml_constructor.construct_yaml_str(prop_key_node)
|
|
289
274
|
# Recursively build each property schema
|
|
290
|
-
|
|
275
|
+
prop_schema_or_reference: NestedSchema = build_schema_or_reference(
|
|
276
|
+
prop_value_node, context
|
|
277
|
+
)
|
|
291
278
|
properties_dict[KeySource(value=prop_key, key_node=prop_key_node)] = (
|
|
292
|
-
|
|
279
|
+
prop_schema_or_reference
|
|
293
280
|
)
|
|
294
281
|
field_values[field_name] = FieldSource(
|
|
295
282
|
value=properties_dict, key_node=key_node, value_node=value_node
|
|
296
283
|
)
|
|
297
284
|
else:
|
|
298
285
|
# Not a mapping - preserve as-is for validation
|
|
299
|
-
|
|
300
|
-
field_values[field_name] = FieldSource(
|
|
301
|
-
value=value, key_node=key_node, value_node=value_node
|
|
302
|
-
)
|
|
286
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
303
287
|
# Nested objects (discriminator, xml, externalDocs)
|
|
304
288
|
elif key == "discriminator":
|
|
305
289
|
field_values[field_name] = FieldSource(
|
|
@@ -328,7 +312,7 @@ def build(
|
|
|
328
312
|
)
|
|
329
313
|
|
|
330
314
|
|
|
331
|
-
def
|
|
315
|
+
def build_schema_or_reference(node: yaml.Node, context: Context) -> NestedSchema:
|
|
332
316
|
"""
|
|
333
317
|
Build either a Schema or Reference from a YAML node.
|
|
334
318
|
|
|
@@ -3,9 +3,9 @@ from dataclasses import dataclass
|
|
|
3
3
|
from ruamel import yaml
|
|
4
4
|
from ruamel.yaml import MappingNode, SequenceNode
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
6
|
+
from ..context import Context
|
|
7
|
+
from ..fields import patterned_field
|
|
8
|
+
from ..sources import KeySource, ValueSource, YAMLInvalidValue
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
__all__ = ["SecurityRequirement", "build"]
|
|
@@ -38,7 +38,9 @@ class SecurityRequirement:
|
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def build(
|
|
41
|
+
def build(
|
|
42
|
+
root: yaml.Node, context: Context | None = None
|
|
43
|
+
) -> SecurityRequirement | ValueSource[YAMLInvalidValue]:
|
|
42
44
|
"""
|
|
43
45
|
Build a SecurityRequirement object from a YAML node.
|
|
44
46
|
|
|
@@ -50,7 +52,9 @@ def build(root: yaml.Node, context: Context | None = None) -> SecurityRequiremen
|
|
|
50
52
|
context: Optional parsing context. If None, a default context will be created.
|
|
51
53
|
|
|
52
54
|
Returns:
|
|
53
|
-
A SecurityRequirement object if the node is valid,
|
|
55
|
+
A SecurityRequirement 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).
|
|
54
58
|
|
|
55
59
|
Example:
|
|
56
60
|
from ruamel.yaml import YAML
|
|
@@ -59,11 +63,12 @@ def build(root: yaml.Node, context: Context | None = None) -> SecurityRequiremen
|
|
|
59
63
|
security_req = build(root)
|
|
60
64
|
assert security_req.requirements is not None
|
|
61
65
|
"""
|
|
62
|
-
|
|
63
|
-
return None
|
|
66
|
+
context = context or Context()
|
|
64
67
|
|
|
65
|
-
if
|
|
66
|
-
|
|
68
|
+
if not isinstance(root, MappingNode):
|
|
69
|
+
# Preserve invalid root data instead of returning None
|
|
70
|
+
value = context.yaml_constructor.construct_object(root, deep=True)
|
|
71
|
+
return ValueSource(value=value, value_node=root)
|
|
67
72
|
|
|
68
73
|
requirements_dict: dict[KeySource[str], ValueSource[list[ValueSource[str]]]] = {}
|
|
69
74
|
|
|
@@ -2,21 +2,17 @@ from dataclasses import dataclass, field, replace
|
|
|
2
2
|
|
|
3
3
|
from ruamel import yaml
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
YAMLValue,
|
|
14
|
-
)
|
|
15
|
-
from jentic.apitools.openapi.datamodels.low.v30.oauth_flows import OAuthFlows
|
|
16
|
-
from jentic.apitools.openapi.datamodels.low.v30.oauth_flows import build as build_oauth_flows
|
|
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 .oauth_flows import OAuthFlows
|
|
10
|
+
from .oauth_flows import build as build_oauth_flows
|
|
11
|
+
from .reference import Reference
|
|
12
|
+
from .reference import build as build_reference
|
|
17
13
|
|
|
18
14
|
|
|
19
|
-
__all__ = ["SecurityScheme", "build"]
|
|
15
|
+
__all__ = ["SecurityScheme", "build", "build_security_scheme_or_reference"]
|
|
20
16
|
|
|
21
17
|
|
|
22
18
|
@dataclass(frozen=True, slots=True)
|
|
@@ -79,18 +75,15 @@ def build(
|
|
|
79
75
|
security_scheme = build(root)
|
|
80
76
|
assert security_scheme.type.value == 'apiKey'
|
|
81
77
|
"""
|
|
82
|
-
|
|
83
|
-
if context is None:
|
|
84
|
-
context = Context()
|
|
78
|
+
context = context or Context()
|
|
85
79
|
|
|
86
|
-
|
|
87
|
-
# Preserve invalid root data instead of returning None
|
|
88
|
-
value = context.yaml_constructor.construct_object(root, deep=True)
|
|
89
|
-
return ValueSource(value=value, value_node=root)
|
|
90
|
-
|
|
91
|
-
# Use build_model to handle most fields
|
|
80
|
+
# Use build_model for initial construction
|
|
92
81
|
security_scheme = build_model(root, SecurityScheme, context=context)
|
|
93
82
|
|
|
83
|
+
# If build_model returned ValueSource (invalid node), return it immediately
|
|
84
|
+
if not isinstance(security_scheme, SecurityScheme):
|
|
85
|
+
return security_scheme
|
|
86
|
+
|
|
94
87
|
# Manually handle special fields that build_model can't process (nested objects)
|
|
95
88
|
for key_node, value_node in root.value:
|
|
96
89
|
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
@@ -107,3 +100,30 @@ def build(
|
|
|
107
100
|
break
|
|
108
101
|
|
|
109
102
|
return security_scheme
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def build_security_scheme_or_reference(
|
|
106
|
+
node: yaml.Node, context: Context
|
|
107
|
+
) -> SecurityScheme | Reference | ValueSource[YAMLInvalidValue]:
|
|
108
|
+
"""
|
|
109
|
+
Build either a SecurityScheme or Reference from a YAML node.
|
|
110
|
+
|
|
111
|
+
This helper handles the polymorphic nature of OpenAPI where many fields
|
|
112
|
+
can contain either a SecurityScheme object or a Reference object ($ref).
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
node: The YAML node to parse
|
|
116
|
+
context: Parsing context
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
A SecurityScheme, Reference, or ValueSource if the node is invalid
|
|
120
|
+
"""
|
|
121
|
+
# Check if it's a reference (has $ref key)
|
|
122
|
+
if isinstance(node, yaml.MappingNode):
|
|
123
|
+
for key_node, _ in node.value:
|
|
124
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
125
|
+
if key == "$ref":
|
|
126
|
+
return build_reference(node, context)
|
|
127
|
+
|
|
128
|
+
# Otherwise, try to build as SecurityScheme
|
|
129
|
+
return build(node, context)
|
|
@@ -0,0 +1,111 @@
|
|
|
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 .server_variable import ServerVariable
|
|
10
|
+
from .server_variable import build as build_server_variable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = ["Server", "build"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class Server:
|
|
18
|
+
"""
|
|
19
|
+
Server Object representation for OpenAPI 3.0.
|
|
20
|
+
|
|
21
|
+
An object representing a Server.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
root_node: The top-level node representing the entire Server object in the original source file
|
|
25
|
+
url: REQUIRED. A URL to the target host. This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served. Variable substitutions will be made when a variable is named in {brackets}.
|
|
26
|
+
description: An optional string describing the host designated by the URL. CommonMark syntax MAY be used for rich text representation.
|
|
27
|
+
variables: A map between a variable name and its value. The value is used for substitution in the server's URL template.
|
|
28
|
+
extensions: Specification extensions (x-* fields)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
root_node: yaml.Node
|
|
32
|
+
url: FieldSource[str] | None = fixed_field()
|
|
33
|
+
description: FieldSource[str] | None = fixed_field()
|
|
34
|
+
variables: FieldSource[dict[KeySource[str], ServerVariable]] | None = fixed_field()
|
|
35
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def build(
|
|
39
|
+
root: yaml.Node, context: Context | None = None
|
|
40
|
+
) -> Server | ValueSource[YAMLInvalidValue]:
|
|
41
|
+
"""
|
|
42
|
+
Build a Server 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 Server 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
|
+
url: https://{environment}.example.com/api/v1
|
|
61
|
+
description: Production API server
|
|
62
|
+
variables:
|
|
63
|
+
environment:
|
|
64
|
+
default: production
|
|
65
|
+
enum:
|
|
66
|
+
- production
|
|
67
|
+
- staging
|
|
68
|
+
- development
|
|
69
|
+
description: The deployment environment
|
|
70
|
+
''')
|
|
71
|
+
server = build(root)
|
|
72
|
+
assert server.url.value == 'https://{environment}.example.com/api/v1'
|
|
73
|
+
assert server.description.value == 'Production API server'
|
|
74
|
+
assert 'environment' in {k.value for k in server.variables.value.keys()}
|
|
75
|
+
"""
|
|
76
|
+
context = context or Context()
|
|
77
|
+
|
|
78
|
+
# Use build_model for initial construction
|
|
79
|
+
server = build_model(root, Server, context=context)
|
|
80
|
+
|
|
81
|
+
# If build_model returned ValueSource (invalid node), return it immediately
|
|
82
|
+
if not isinstance(server, Server):
|
|
83
|
+
return server
|
|
84
|
+
|
|
85
|
+
# Manually handle nested variables field
|
|
86
|
+
for key_node, value_node in root.value:
|
|
87
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
88
|
+
|
|
89
|
+
if key == "variables":
|
|
90
|
+
# Handle variables field - map of ServerVariable objects
|
|
91
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
92
|
+
variables_dict: dict[
|
|
93
|
+
KeySource[str], ServerVariable | ValueSource[YAMLInvalidValue]
|
|
94
|
+
] = {}
|
|
95
|
+
for var_key_node, var_value_node in value_node.value:
|
|
96
|
+
var_key = context.yaml_constructor.construct_yaml_str(var_key_node)
|
|
97
|
+
# Build ServerVariable for each variable - child builder handles invalid nodes
|
|
98
|
+
variables_dict[KeySource(value=var_key, key_node=var_key_node)] = (
|
|
99
|
+
build_server_variable(var_value_node, context=context)
|
|
100
|
+
)
|
|
101
|
+
variables = FieldSource(
|
|
102
|
+
value=variables_dict, key_node=key_node, value_node=value_node
|
|
103
|
+
)
|
|
104
|
+
server = replace(server, variables=variables)
|
|
105
|
+
else:
|
|
106
|
+
# Not a mapping - preserve as-is for validation
|
|
107
|
+
variables = build_field_source(key_node, value_node, context)
|
|
108
|
+
server = replace(server, variables=variables)
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
return server
|