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,498 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, TypeAlias, get_args
|
|
3
|
+
|
|
4
|
+
from ruamel import yaml
|
|
5
|
+
from ruamel.yaml.comments import CommentedSeq
|
|
6
|
+
|
|
7
|
+
from ..context import Context
|
|
8
|
+
from ..extractors import extract_extension_fields
|
|
9
|
+
from ..fields import fixed_field, fixed_fields
|
|
10
|
+
from ..sources import FieldSource, KeySource, ValueSource, YAMLValue
|
|
11
|
+
from .builders import build_field_source
|
|
12
|
+
from .discriminator import Discriminator
|
|
13
|
+
from .discriminator import build as build_discriminator
|
|
14
|
+
from .external_documentation import ExternalDocumentation
|
|
15
|
+
from .external_documentation import build as build_external_documentation
|
|
16
|
+
from .xml import XML
|
|
17
|
+
from .xml import build as build_xml
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["Schema", "BooleanJSONSchema", "NestedSchema", "build"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
BooleanJSONSchema: TypeAlias = ValueSource[bool]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Type alias for nested schema references
|
|
27
|
+
# A schema node that can be nested within another schema, representing:
|
|
28
|
+
# - Schema: A valid schema object
|
|
29
|
+
# - Boolean JSON Schema: True or False
|
|
30
|
+
# - ValueSource[str | int | float | None | CommentedSeq]: Invalid/malformed data preserved for validation
|
|
31
|
+
|
|
32
|
+
NestedSchema: TypeAlias = (
|
|
33
|
+
"Schema | BooleanJSONSchema | ValueSource[str | int | float | None | CommentedSeq]"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class Schema:
|
|
39
|
+
"""
|
|
40
|
+
Schema Object representation for OpenAPI 3.1.
|
|
41
|
+
|
|
42
|
+
In OpenAPI 3.1, the Schema Object is a full JSON Schema 2020-12 vocabulary with OpenAPI extensions.
|
|
43
|
+
This represents a complete JSON Schema with additional OpenAPI-specific fields.
|
|
44
|
+
|
|
45
|
+
Attributes:
|
|
46
|
+
root_node: The top-level node representing the entire Schema object in the original source file
|
|
47
|
+
|
|
48
|
+
# JSON Schema Core Keywords (2020-12)
|
|
49
|
+
schema_: The $schema keyword - URI of the meta-schema
|
|
50
|
+
id_: The $id keyword - URI that identifies the schema resource
|
|
51
|
+
ref_: The $ref keyword - URI reference to another schema
|
|
52
|
+
anchor: The $anchor keyword - Plain name fragment for identification
|
|
53
|
+
dynamic_ref: The $dynamicRef keyword - Dynamic reference to another schema
|
|
54
|
+
dynamic_anchor: The $dynamicAnchor keyword - Dynamic anchor for identification
|
|
55
|
+
vocabulary: The $vocabulary keyword - Available vocabularies and their usage
|
|
56
|
+
comment: The $comment keyword - Comments for schema authors
|
|
57
|
+
defs: The $defs keyword - Schema definitions for reuse
|
|
58
|
+
|
|
59
|
+
# JSON Schema Validation Keywords (2020-12)
|
|
60
|
+
# Validation - Any Type
|
|
61
|
+
type: The type keyword - Value type(s): string, number, integer, boolean, array, object, null
|
|
62
|
+
enum: The enum keyword - Fixed set of allowed values
|
|
63
|
+
const: The const keyword - Single allowed value
|
|
64
|
+
|
|
65
|
+
# Validation - Numeric
|
|
66
|
+
multiple_of: A numeric instance is valid only if division by this value results in an integer
|
|
67
|
+
maximum: Upper limit for a numeric instance (inclusive)
|
|
68
|
+
exclusive_maximum: Upper limit for a numeric instance (exclusive)
|
|
69
|
+
minimum: Lower limit for a numeric instance (inclusive)
|
|
70
|
+
exclusive_minimum: Lower limit for a numeric instance (exclusive)
|
|
71
|
+
|
|
72
|
+
# Validation - String
|
|
73
|
+
max_length: Maximum length of a string instance
|
|
74
|
+
min_length: Minimum length of a string instance
|
|
75
|
+
pattern: A string instance is valid if the regular expression matches the instance successfully
|
|
76
|
+
|
|
77
|
+
# Validation - Array
|
|
78
|
+
max_items: Maximum number of items in an array instance
|
|
79
|
+
min_items: Minimum number of items in an array instance
|
|
80
|
+
unique_items: If true, array items must be unique
|
|
81
|
+
max_contains: Maximum number of items that must match the contains schema
|
|
82
|
+
min_contains: Minimum number of items that must match the contains schema
|
|
83
|
+
|
|
84
|
+
# Validation - Object
|
|
85
|
+
max_properties: Maximum number of properties in an object instance
|
|
86
|
+
min_properties: Minimum number of properties in an object instance
|
|
87
|
+
required: List of required property names
|
|
88
|
+
dependent_required: Dependencies between properties (property-based requirements)
|
|
89
|
+
|
|
90
|
+
# JSON Schema Applicator Keywords (2020-12)
|
|
91
|
+
# Schema Composition
|
|
92
|
+
all_of: Must be valid against all of the subschemas
|
|
93
|
+
any_of: Must be valid against any of the subschemas
|
|
94
|
+
one_of: Must be valid against exactly one of the subschemas
|
|
95
|
+
not_: Must not be valid against the given schema
|
|
96
|
+
|
|
97
|
+
# Conditional Application
|
|
98
|
+
if_: Conditional schema - if this validates, then/else is applied
|
|
99
|
+
then_: Schema to apply if 'if' validates
|
|
100
|
+
else_: Schema to apply if 'if' does not validate
|
|
101
|
+
|
|
102
|
+
# Array Applicators
|
|
103
|
+
prefix_items: Array of schemas for validating tuple-like arrays (positional items)
|
|
104
|
+
items: Schema for array items (all items or items after prefix_items)
|
|
105
|
+
contains: Schema that at least one array item must validate against
|
|
106
|
+
|
|
107
|
+
# Object Applicators
|
|
108
|
+
properties: Property name to schema mappings
|
|
109
|
+
pattern_properties: Property patterns to schema mappings
|
|
110
|
+
additional_properties: Schema for properties not defined in properties/pattern_properties, or boolean
|
|
111
|
+
property_names: Schema that all property names must validate against
|
|
112
|
+
|
|
113
|
+
# Dependent Schemas
|
|
114
|
+
dependent_schemas: Schemas that must validate when specific properties are present
|
|
115
|
+
|
|
116
|
+
# Unevaluated Locations
|
|
117
|
+
unevaluated_items: Schema for array items not evaluated by other keywords
|
|
118
|
+
unevaluated_properties: Schema for object properties not evaluated by other keywords
|
|
119
|
+
|
|
120
|
+
# JSON Schema Meta-Data Keywords (2020-12)
|
|
121
|
+
title: A title for the schema
|
|
122
|
+
description: A description of the schema. CommonMark syntax MAY be used for rich text representation.
|
|
123
|
+
default: Default value for the schema
|
|
124
|
+
deprecated: Specifies that the schema is deprecated
|
|
125
|
+
read_only: Indicates the value should not be modified (sent in response but not in request)
|
|
126
|
+
write_only: Indicates the value should only be sent in requests (not in responses)
|
|
127
|
+
examples: Array of example values
|
|
128
|
+
|
|
129
|
+
# JSON Schema Format Keywords (2020-12)
|
|
130
|
+
format: Additional format hint for the type (e.g., "email", "uuid", "uri", "date-time")
|
|
131
|
+
|
|
132
|
+
# JSON Schema Content Keywords (2020-12)
|
|
133
|
+
content_encoding: Content encoding for string instances (e.g., "base64")
|
|
134
|
+
content_media_type: Media type of string instance contents (e.g., "application/json")
|
|
135
|
+
content_schema: Schema for validating the decoded content
|
|
136
|
+
|
|
137
|
+
# OpenAPI-specific extensions (not in JSON Schema 2020-12)
|
|
138
|
+
discriminator: Adds support for polymorphism
|
|
139
|
+
xml: Additional metadata for XML representations
|
|
140
|
+
external_docs: Additional external documentation
|
|
141
|
+
example: A single example value (OpenAPI extension, use examples for standard JSON Schema)
|
|
142
|
+
|
|
143
|
+
extensions: Specification extensions (x-* fields)
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
root_node: yaml.Node
|
|
147
|
+
|
|
148
|
+
# Core Keywords
|
|
149
|
+
id_: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "$id"})
|
|
150
|
+
schema_: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "$schema"})
|
|
151
|
+
ref_: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "$ref"})
|
|
152
|
+
comment: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "$comment"})
|
|
153
|
+
defs: FieldSource[dict[KeySource[str], NestedSchema]] | None = fixed_field(
|
|
154
|
+
metadata={"yaml_name": "$defs"}
|
|
155
|
+
)
|
|
156
|
+
anchor: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "$anchor"})
|
|
157
|
+
dynamic_anchor: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "$dynamicAnchor"})
|
|
158
|
+
dynamic_ref: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "$dynamicRef"})
|
|
159
|
+
vocabulary: FieldSource[dict[KeySource[str], ValueSource[bool]]] | None = fixed_field(
|
|
160
|
+
metadata={"yaml_name": "$vocabulary"}
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# JSON Schema Applicator Keywords
|
|
164
|
+
all_of: FieldSource[list[NestedSchema]] | None = fixed_field(metadata={"yaml_name": "allOf"})
|
|
165
|
+
any_of: FieldSource[list[NestedSchema]] | None = fixed_field(metadata={"yaml_name": "anyOf"})
|
|
166
|
+
one_of: FieldSource[list[NestedSchema]] | None = fixed_field(metadata={"yaml_name": "oneOf"})
|
|
167
|
+
if_: FieldSource[NestedSchema] | None = fixed_field(metadata={"yaml_name": "if"})
|
|
168
|
+
then_: FieldSource[NestedSchema] | None = fixed_field(metadata={"yaml_name": "then"})
|
|
169
|
+
else_: FieldSource[NestedSchema] | None = fixed_field(metadata={"yaml_name": "else"})
|
|
170
|
+
not_: FieldSource[NestedSchema] | None = fixed_field(metadata={"yaml_name": "not"})
|
|
171
|
+
properties: FieldSource[dict[KeySource[str], NestedSchema]] | None = fixed_field()
|
|
172
|
+
additional_properties: FieldSource[NestedSchema] | None = fixed_field(
|
|
173
|
+
metadata={"yaml_name": "additionalProperties"}
|
|
174
|
+
)
|
|
175
|
+
pattern_properties: FieldSource[dict[KeySource[str], NestedSchema]] | None = fixed_field(
|
|
176
|
+
metadata={"yaml_name": "patternProperties"}
|
|
177
|
+
)
|
|
178
|
+
dependent_schemas: FieldSource[dict[KeySource[str], NestedSchema]] | None = fixed_field(
|
|
179
|
+
metadata={"yaml_name": "dependentSchemas"}
|
|
180
|
+
)
|
|
181
|
+
property_names: FieldSource[NestedSchema] | None = fixed_field(
|
|
182
|
+
metadata={"yaml_name": "propertyNames"}
|
|
183
|
+
)
|
|
184
|
+
contains: FieldSource[NestedSchema] | None = fixed_field()
|
|
185
|
+
items: FieldSource[NestedSchema] | None = fixed_field()
|
|
186
|
+
prefix_items: FieldSource[list[NestedSchema]] | None = fixed_field(
|
|
187
|
+
metadata={"yaml_name": "prefixItems"}
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Validation Keywords
|
|
191
|
+
type: FieldSource[str | list[ValueSource[str]]] | None = fixed_field()
|
|
192
|
+
enum: FieldSource[list[ValueSource[YAMLValue]]] | None = fixed_field()
|
|
193
|
+
const: FieldSource[YAMLValue] | None = fixed_field()
|
|
194
|
+
max_length: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "maxLength"})
|
|
195
|
+
min_length: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "minLength"})
|
|
196
|
+
pattern: FieldSource[str] | None = fixed_field()
|
|
197
|
+
exclusive_maximum: FieldSource[int | float] | None = fixed_field(
|
|
198
|
+
metadata={"yaml_name": "exclusiveMaximum"}
|
|
199
|
+
)
|
|
200
|
+
exclusive_minimum: FieldSource[int | float] | None = fixed_field(
|
|
201
|
+
metadata={"yaml_name": "exclusiveMinimum"}
|
|
202
|
+
)
|
|
203
|
+
minimum: FieldSource[int | float] | None = fixed_field()
|
|
204
|
+
maximum: FieldSource[int | float] | None = fixed_field()
|
|
205
|
+
multiple_of: FieldSource[int | float] | None = fixed_field(metadata={"yaml_name": "multipleOf"})
|
|
206
|
+
dependent_required: FieldSource[dict[KeySource[str], list[ValueSource[str]]]] | None = (
|
|
207
|
+
fixed_field(metadata={"yaml_name": "dependentRequired"})
|
|
208
|
+
)
|
|
209
|
+
max_properties: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "maxProperties"})
|
|
210
|
+
min_properties: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "minProperties"})
|
|
211
|
+
required: FieldSource[list[ValueSource[str]]] | None = fixed_field()
|
|
212
|
+
max_items: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "maxItems"})
|
|
213
|
+
min_items: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "minItems"})
|
|
214
|
+
max_contains: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "maxContains"})
|
|
215
|
+
min_contains: FieldSource[int] | None = fixed_field(metadata={"yaml_name": "minContains"})
|
|
216
|
+
unique_items: FieldSource[bool] | None = fixed_field(metadata={"yaml_name": "uniqueItems"})
|
|
217
|
+
|
|
218
|
+
# Meta Data Keywords
|
|
219
|
+
title: FieldSource[str] | None = fixed_field()
|
|
220
|
+
description: FieldSource[str] | None = fixed_field()
|
|
221
|
+
default: FieldSource[YAMLValue] | None = fixed_field()
|
|
222
|
+
deprecated: FieldSource[bool] | None = fixed_field()
|
|
223
|
+
examples: FieldSource[list[ValueSource[YAMLValue]]] | None = fixed_field()
|
|
224
|
+
read_only: FieldSource[bool] | None = fixed_field(metadata={"yaml_name": "readOnly"})
|
|
225
|
+
write_only: FieldSource[bool] | None = fixed_field(metadata={"yaml_name": "writeOnly"})
|
|
226
|
+
|
|
227
|
+
# Format Annotation Keywords
|
|
228
|
+
format: FieldSource[str] | None = fixed_field()
|
|
229
|
+
|
|
230
|
+
# Content Keywords
|
|
231
|
+
content_encoding: FieldSource[str] | None = fixed_field(
|
|
232
|
+
metadata={"yaml_name": "contentEncoding"}
|
|
233
|
+
)
|
|
234
|
+
content_media_type: FieldSource[str] | None = fixed_field(
|
|
235
|
+
metadata={"yaml_name": "contentMediaType"}
|
|
236
|
+
)
|
|
237
|
+
content_schema: FieldSource[NestedSchema] | None = fixed_field(
|
|
238
|
+
metadata={"yaml_name": "contentSchema"}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Unevaluated Keywords
|
|
242
|
+
unevaluated_items: FieldSource[NestedSchema] | None = fixed_field(
|
|
243
|
+
metadata={"yaml_name": "unevaluatedItems"}
|
|
244
|
+
)
|
|
245
|
+
unevaluated_properties: FieldSource[NestedSchema] | None = fixed_field(
|
|
246
|
+
metadata={"yaml_name": "unevaluatedProperties"}
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# OpenAPI base vocabulary Keywords (not in JSON Schema 2020-12)
|
|
250
|
+
discriminator: FieldSource[Discriminator] | None = fixed_field()
|
|
251
|
+
xml: FieldSource[XML] | None = fixed_field()
|
|
252
|
+
external_docs: FieldSource[ExternalDocumentation] | None = fixed_field(
|
|
253
|
+
metadata={"yaml_name": "externalDocs"}
|
|
254
|
+
)
|
|
255
|
+
example: FieldSource[YAMLValue] | None = fixed_field()
|
|
256
|
+
|
|
257
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def build(
|
|
261
|
+
root: yaml.Node, context: Context | None = None
|
|
262
|
+
) -> "Schema | BooleanJSONSchema | ValueSource[str | int | float | None | CommentedSeq]":
|
|
263
|
+
"""
|
|
264
|
+
Build a Schema object from a YAML node.
|
|
265
|
+
|
|
266
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
267
|
+
model that provides complete source fidelity for inspection and validation.
|
|
268
|
+
|
|
269
|
+
Note: Schema is self-referential (can contain other Schema objects in all_of, one_of, any_of, not_,
|
|
270
|
+
items, properties, additional_properties). The builder handles nested Schema objects by preserving
|
|
271
|
+
them as raw YAML values, letting validation layers interpret them.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
275
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
A Schema object if the node is valid, or a ValueSource containing
|
|
279
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
280
|
+
and its source location for validation).
|
|
281
|
+
|
|
282
|
+
Example:
|
|
283
|
+
from ruamel.yaml import YAML
|
|
284
|
+
yaml = YAML()
|
|
285
|
+
root = yaml.compose("type: string\\nminLength: 1\\nmaxLength: 100")
|
|
286
|
+
schema = build(root)
|
|
287
|
+
assert schema.type.value == 'string'
|
|
288
|
+
assert schema.min_length.value == 1
|
|
289
|
+
"""
|
|
290
|
+
context = context or Context()
|
|
291
|
+
|
|
292
|
+
if not isinstance(root, yaml.MappingNode):
|
|
293
|
+
# Preserve invalid root data instead of returning None
|
|
294
|
+
value = context.yaml_constructor.construct_object(root, deep=True)
|
|
295
|
+
return ValueSource(value=value, value_node=root)
|
|
296
|
+
|
|
297
|
+
# Build YAML name to Python field name mapping
|
|
298
|
+
_fixed_fields = fixed_fields(Schema)
|
|
299
|
+
yaml_to_field = {
|
|
300
|
+
f.metadata.get("yaml_name", fname): fname for fname, f in _fixed_fields.items()
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# Accumulate all field values in a single pass
|
|
304
|
+
field_values: dict[str, Any] = {}
|
|
305
|
+
|
|
306
|
+
for key_node, value_node in root.value:
|
|
307
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
308
|
+
|
|
309
|
+
# Skip extension fields - handled separately at the end
|
|
310
|
+
if key.startswith("x-"):
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
# Map YAML key to Python field name
|
|
314
|
+
field_name = yaml_to_field.get(key)
|
|
315
|
+
if not field_name:
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
# Get field metadata
|
|
319
|
+
field_info = _fixed_fields[field_name]
|
|
320
|
+
field_type_args = set(get_args(field_info.type))
|
|
321
|
+
|
|
322
|
+
# Simple scalar fields (handled like build_model does)
|
|
323
|
+
if field_type_args & {
|
|
324
|
+
FieldSource[str],
|
|
325
|
+
FieldSource[bool],
|
|
326
|
+
FieldSource[int],
|
|
327
|
+
FieldSource[int | float],
|
|
328
|
+
FieldSource[YAMLValue],
|
|
329
|
+
}:
|
|
330
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
331
|
+
|
|
332
|
+
# Handle list with ValueSource wrapping for each item (e.g., required, enum fields)
|
|
333
|
+
elif field_type_args & {
|
|
334
|
+
FieldSource[list[ValueSource[str]]],
|
|
335
|
+
FieldSource[list[ValueSource[YAMLValue]]],
|
|
336
|
+
}:
|
|
337
|
+
if isinstance(value_node, yaml.SequenceNode):
|
|
338
|
+
value_list: list[ValueSource[Any]] = []
|
|
339
|
+
for item_node in value_node.value:
|
|
340
|
+
item_value = context.yaml_constructor.construct_object(item_node, deep=True)
|
|
341
|
+
value_list.append(ValueSource(value=item_value, value_node=item_node))
|
|
342
|
+
field_values[field_name] = FieldSource(
|
|
343
|
+
value=value_list, key_node=key_node, value_node=value_node
|
|
344
|
+
)
|
|
345
|
+
else:
|
|
346
|
+
# Not a sequence - preserve as-is for validation
|
|
347
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
348
|
+
|
|
349
|
+
# Recursive schema list fields (allOf, oneOf, anyOf, prefixItems)
|
|
350
|
+
elif key in ("allOf", "oneOf", "anyOf", "prefixItems"):
|
|
351
|
+
if isinstance(value_node, yaml.SequenceNode):
|
|
352
|
+
schemas = []
|
|
353
|
+
for item_node in value_node.value:
|
|
354
|
+
schema = build(item_node, context)
|
|
355
|
+
schemas.append(schema)
|
|
356
|
+
field_values[field_name] = FieldSource(
|
|
357
|
+
value=schemas, key_node=key_node, value_node=value_node
|
|
358
|
+
)
|
|
359
|
+
else:
|
|
360
|
+
# Not a sequence - preserve as-is for validation
|
|
361
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
362
|
+
# Recursive schema single fields (not, if, then, else, contains, propertyNames, contentSchema)
|
|
363
|
+
elif key in ("not", "if", "then", "else", "contains", "propertyNames", "contentSchema"):
|
|
364
|
+
schema = build(value_node, context)
|
|
365
|
+
field_values[field_name] = FieldSource(
|
|
366
|
+
value=schema, key_node=key_node, value_node=value_node
|
|
367
|
+
)
|
|
368
|
+
# items (boolean | schema in JSON Schema 2020-12)
|
|
369
|
+
elif key == "items":
|
|
370
|
+
# Check if it's a boolean or a schema
|
|
371
|
+
if (
|
|
372
|
+
isinstance(value_node, yaml.ScalarNode)
|
|
373
|
+
and value_node.tag == "tag:yaml.org,2002:bool"
|
|
374
|
+
):
|
|
375
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
376
|
+
else:
|
|
377
|
+
# It's a schema
|
|
378
|
+
schema = build(value_node, context)
|
|
379
|
+
field_values[field_name] = FieldSource(
|
|
380
|
+
value=schema, key_node=key_node, value_node=value_node
|
|
381
|
+
)
|
|
382
|
+
# Boolean | schema fields (additionalProperties, unevaluatedItems, unevaluatedProperties)
|
|
383
|
+
elif key in ("additionalProperties", "unevaluatedItems", "unevaluatedProperties"):
|
|
384
|
+
# Check if it's a boolean or a schema
|
|
385
|
+
if (
|
|
386
|
+
isinstance(value_node, yaml.ScalarNode)
|
|
387
|
+
and value_node.tag == "tag:yaml.org,2002:bool"
|
|
388
|
+
):
|
|
389
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
390
|
+
else:
|
|
391
|
+
# It's a schema
|
|
392
|
+
schema = build(value_node, context)
|
|
393
|
+
field_values[field_name] = FieldSource(
|
|
394
|
+
value=schema, key_node=key_node, value_node=value_node
|
|
395
|
+
)
|
|
396
|
+
# Dict of schemas (properties, $defs, patternProperties, dependentSchemas)
|
|
397
|
+
elif key in ("properties", "$defs", "patternProperties", "dependentSchemas"):
|
|
398
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
399
|
+
schemas_dict: dict[KeySource[str], NestedSchema] = {}
|
|
400
|
+
for map_key_node, map_value_node in value_node.value:
|
|
401
|
+
map_key = context.yaml_constructor.construct_yaml_str(map_key_node)
|
|
402
|
+
# Recursively build each schema
|
|
403
|
+
nested_schema: NestedSchema = build(map_value_node, context)
|
|
404
|
+
schemas_dict[KeySource(value=map_key, key_node=map_key_node)] = nested_schema
|
|
405
|
+
field_values[field_name] = FieldSource(
|
|
406
|
+
value=schemas_dict, key_node=key_node, value_node=value_node
|
|
407
|
+
)
|
|
408
|
+
else:
|
|
409
|
+
# Not a mapping - preserve as-is for validation
|
|
410
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
411
|
+
# $vocabulary (dict[KeySource[str], ValueSource[bool]])
|
|
412
|
+
elif key == "$vocabulary":
|
|
413
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
414
|
+
vocabulary_dict: dict[KeySource[str], ValueSource[bool]] = {}
|
|
415
|
+
for vocab_key_node, vocab_value_node in value_node.value:
|
|
416
|
+
vocab_key = context.yaml_constructor.construct_yaml_str(vocab_key_node)
|
|
417
|
+
vocab_value = context.yaml_constructor.construct_object(
|
|
418
|
+
vocab_value_node, deep=True
|
|
419
|
+
)
|
|
420
|
+
vocabulary_dict[KeySource(value=vocab_key, key_node=vocab_key_node)] = (
|
|
421
|
+
ValueSource(value=vocab_value, value_node=vocab_value_node)
|
|
422
|
+
)
|
|
423
|
+
field_values[field_name] = FieldSource(
|
|
424
|
+
value=vocabulary_dict, key_node=key_node, value_node=value_node
|
|
425
|
+
)
|
|
426
|
+
else:
|
|
427
|
+
# Not a mapping - preserve as-is for validation
|
|
428
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
429
|
+
# dependentRequired (dict[KeySource[str], list[ValueSource[str]]])
|
|
430
|
+
elif key == "dependentRequired":
|
|
431
|
+
if isinstance(value_node, yaml.MappingNode):
|
|
432
|
+
dependent_required_dict: dict[KeySource[str], list[ValueSource[str]]] = {}
|
|
433
|
+
for dep_key_node, dep_value_node in value_node.value:
|
|
434
|
+
dep_key = context.yaml_constructor.construct_yaml_str(dep_key_node)
|
|
435
|
+
if isinstance(dep_value_node, yaml.SequenceNode):
|
|
436
|
+
dep_list: list[ValueSource[str]] = []
|
|
437
|
+
for item_node in dep_value_node.value:
|
|
438
|
+
item_value = context.yaml_constructor.construct_object(
|
|
439
|
+
item_node, deep=True
|
|
440
|
+
)
|
|
441
|
+
dep_list.append(ValueSource(value=item_value, value_node=item_node))
|
|
442
|
+
dependent_required_dict[KeySource(value=dep_key, key_node=dep_key_node)] = (
|
|
443
|
+
dep_list
|
|
444
|
+
)
|
|
445
|
+
else:
|
|
446
|
+
# Not a sequence - preserve as invalid for validation
|
|
447
|
+
invalid_value = context.yaml_constructor.construct_object(
|
|
448
|
+
dep_value_node, deep=True
|
|
449
|
+
)
|
|
450
|
+
dependent_required_dict[KeySource(value=dep_key, key_node=dep_key_node)] = (
|
|
451
|
+
invalid_value # type: ignore
|
|
452
|
+
)
|
|
453
|
+
field_values[field_name] = FieldSource(
|
|
454
|
+
value=dependent_required_dict, key_node=key_node, value_node=value_node
|
|
455
|
+
)
|
|
456
|
+
else:
|
|
457
|
+
# Not a mapping - preserve as-is for validation
|
|
458
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
459
|
+
# type field (can be string or list of strings in JSON Schema 2020-12)
|
|
460
|
+
elif key == "type":
|
|
461
|
+
if isinstance(value_node, yaml.SequenceNode):
|
|
462
|
+
# It's a list of types
|
|
463
|
+
type_list: list[ValueSource[str]] = []
|
|
464
|
+
for item_node in value_node.value:
|
|
465
|
+
item_value = context.yaml_constructor.construct_object(item_node, deep=True)
|
|
466
|
+
type_list.append(ValueSource(value=item_value, value_node=item_node))
|
|
467
|
+
field_values[field_name] = FieldSource(
|
|
468
|
+
value=type_list, key_node=key_node, value_node=value_node
|
|
469
|
+
)
|
|
470
|
+
else:
|
|
471
|
+
# It's a single type string
|
|
472
|
+
field_values[field_name] = build_field_source(key_node, value_node, context)
|
|
473
|
+
# Nested objects (discriminator, xml, externalDocs)
|
|
474
|
+
elif key == "discriminator":
|
|
475
|
+
field_values[field_name] = FieldSource(
|
|
476
|
+
value=build_discriminator(value_node, context=context),
|
|
477
|
+
key_node=key_node,
|
|
478
|
+
value_node=value_node,
|
|
479
|
+
)
|
|
480
|
+
elif key == "xml":
|
|
481
|
+
field_values[field_name] = FieldSource(
|
|
482
|
+
value=build_xml(value_node, context=context),
|
|
483
|
+
key_node=key_node,
|
|
484
|
+
value_node=value_node,
|
|
485
|
+
)
|
|
486
|
+
elif key == "externalDocs":
|
|
487
|
+
field_values[field_name] = FieldSource(
|
|
488
|
+
value=build_external_documentation(value_node, context=context),
|
|
489
|
+
key_node=key_node,
|
|
490
|
+
value_node=value_node,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
# Build and return the Schema instance (single constructor call)
|
|
494
|
+
return Schema(
|
|
495
|
+
root_node=root,
|
|
496
|
+
**field_values,
|
|
497
|
+
extensions=extract_extension_fields(root, context),
|
|
498
|
+
)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from ruamel import yaml
|
|
4
|
+
from ruamel.yaml import MappingNode, SequenceNode
|
|
5
|
+
|
|
6
|
+
from ..context import Context
|
|
7
|
+
from ..fields import patterned_field
|
|
8
|
+
from ..sources import KeySource, ValueSource, YAMLInvalidValue
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["SecurityRequirement", "build"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class SecurityRequirement:
|
|
16
|
+
"""
|
|
17
|
+
Security Requirement Object representation for OpenAPI 3.1.
|
|
18
|
+
|
|
19
|
+
Lists the required security schemes to execute an operation. Each named security scheme
|
|
20
|
+
must correspond to a security scheme declared in the Security Schemes under the Components Object.
|
|
21
|
+
|
|
22
|
+
When multiple Security Requirement Objects are specified, only ONE needs to be satisfied
|
|
23
|
+
to authorize a request. Within a single Security Requirement Object, ALL schemes must be satisfied.
|
|
24
|
+
|
|
25
|
+
Note: An empty Security Requirement object ({}) makes security optional for the operation.
|
|
26
|
+
Note: Specification extensions (x-* fields) are NOT supported for Security Requirement objects.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
root_node: The top-level node representing the entire Security Requirement object in the original source file
|
|
30
|
+
requirements: Dictionary mapping security scheme names to arrays of scope strings.
|
|
31
|
+
For OAuth2 schemes, the array contains required scopes.
|
|
32
|
+
For other schemes (API key, HTTP), the array is empty.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
root_node: yaml.Node
|
|
36
|
+
requirements: ValueSource[dict[KeySource[str], ValueSource[list[ValueSource[str]]]]] | None = (
|
|
37
|
+
patterned_field()
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build(
|
|
42
|
+
root: yaml.Node, context: Context | None = None
|
|
43
|
+
) -> SecurityRequirement | ValueSource[YAMLInvalidValue]:
|
|
44
|
+
"""
|
|
45
|
+
Build a SecurityRequirement 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 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).
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
from ruamel.yaml import YAML
|
|
61
|
+
yaml = YAML()
|
|
62
|
+
root = yaml.compose("api_key: []")
|
|
63
|
+
security_req = build(root)
|
|
64
|
+
assert security_req.requirements is not None
|
|
65
|
+
"""
|
|
66
|
+
context = context or Context()
|
|
67
|
+
|
|
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)
|
|
72
|
+
|
|
73
|
+
requirements_dict: dict[KeySource[str], ValueSource[list[ValueSource[str]]]] = {}
|
|
74
|
+
|
|
75
|
+
for key_node, value_node in root.value:
|
|
76
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
77
|
+
|
|
78
|
+
# Skip non-string keys
|
|
79
|
+
if not isinstance(key, str):
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
# Security scheme requirement field
|
|
83
|
+
# For requirements, we need to wrap each scope string in ValueSource
|
|
84
|
+
if isinstance(value_node, SequenceNode):
|
|
85
|
+
# Wrap each scope string in the array with its source node
|
|
86
|
+
scope_list: list[ValueSource[str]] = []
|
|
87
|
+
for item_node in value_node.value:
|
|
88
|
+
item_value = context.yaml_constructor.construct_object(item_node, deep=True)
|
|
89
|
+
scope_list.append(ValueSource(value=item_value, value_node=item_node))
|
|
90
|
+
|
|
91
|
+
requirements_dict[KeySource(value=key, key_node=key_node)] = ValueSource(
|
|
92
|
+
value=scope_list, value_node=value_node
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
# Not a sequence - preserve as-is for validation to catch
|
|
96
|
+
value = context.yaml_constructor.construct_object(value_node, deep=True)
|
|
97
|
+
requirements_dict[KeySource(value=key, key_node=key_node)] = ValueSource(
|
|
98
|
+
value=value, value_node=value_node
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return SecurityRequirement(
|
|
102
|
+
root_node=root,
|
|
103
|
+
requirements=(
|
|
104
|
+
ValueSource(value=requirements_dict, value_node=root) if requirements_dict else None
|
|
105
|
+
),
|
|
106
|
+
)
|