jentic-openapi-datamodels 1.0.0a11__py3-none-any.whl → 1.0.0a13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. jentic/apitools/openapi/datamodels/low/context.py +21 -0
  2. jentic/apitools/openapi/datamodels/low/extractors.py +126 -0
  3. jentic/apitools/openapi/datamodels/low/fields.py +45 -0
  4. jentic/apitools/openapi/datamodels/low/model_builder.py +113 -0
  5. jentic/apitools/openapi/datamodels/low/py.typed +0 -0
  6. jentic/apitools/openapi/datamodels/low/sources.py +89 -0
  7. jentic/apitools/openapi/datamodels/low/v30/__init__.py +0 -28
  8. jentic/apitools/openapi/datamodels/low/v30/discriminator.py +53 -78
  9. jentic/apitools/openapi/datamodels/low/v30/external_documentation.py +47 -61
  10. jentic/apitools/openapi/datamodels/low/v30/oauth_flow.py +54 -123
  11. jentic/apitools/openapi/datamodels/low/v30/oauth_flows.py +102 -151
  12. jentic/apitools/openapi/datamodels/low/v30/reference.py +43 -44
  13. jentic/apitools/openapi/datamodels/low/v30/schema.py +316 -607
  14. jentic/apitools/openapi/datamodels/low/v30/security_requirement.py +82 -72
  15. jentic/apitools/openapi/datamodels/low/v30/security_scheme.py +94 -286
  16. jentic/apitools/openapi/datamodels/low/v30/tag.py +88 -119
  17. jentic/apitools/openapi/datamodels/low/v30/xml.py +46 -120
  18. jentic_openapi_datamodels-1.0.0a13.dist-info/METADATA +211 -0
  19. jentic_openapi_datamodels-1.0.0a13.dist-info/RECORD +23 -0
  20. jentic/apitools/openapi/datamodels/low/v30/specification_object.py +0 -217
  21. jentic_openapi_datamodels-1.0.0a11.dist-info/METADATA +0 -52
  22. jentic_openapi_datamodels-1.0.0a11.dist-info/RECORD +0 -18
  23. /jentic/apitools/openapi/datamodels/low/{v30/py.typed → __init__.py} +0 -0
  24. {jentic_openapi_datamodels-1.0.0a11.dist-info → jentic_openapi_datamodels-1.0.0a13.dist-info}/WHEEL +0 -0
  25. {jentic_openapi_datamodels-1.0.0a11.dist-info → jentic_openapi_datamodels-1.0.0a13.dist-info}/licenses/LICENSE +0 -0
  26. {jentic_openapi_datamodels-1.0.0a11.dist-info → jentic_openapi_datamodels-1.0.0a13.dist-info}/licenses/NOTICE +0 -0
@@ -1,91 +1,101 @@
1
- """
2
- OpenAPI 3.0.4 Security Requirement Object model.
1
+ from dataclasses import dataclass
3
2
 
4
- The Security Requirement Object defines which security mechanisms can be used for a
5
- particular operation. Each named security scheme is mapped to a list of scope names
6
- required for execution (for OAuth2/OIDC) or an empty list (for other schemes).
7
- """
3
+ from ruamel import yaml
4
+ from ruamel.yaml import MappingNode, SequenceNode
8
5
 
9
- from jentic.apitools.openapi.datamodels.low.v30.specification_object import SpecificationObject
6
+ from jentic.apitools.openapi.datamodels.low.context import Context
7
+ from jentic.apitools.openapi.datamodels.low.fields import patterned_field
8
+ from jentic.apitools.openapi.datamodels.low.sources import KeySource, ValueSource
10
9
 
11
10
 
12
- __all__ = ["SecurityRequirement"]
11
+ __all__ = ["SecurityRequirement", "build"]
13
12
 
14
13
 
15
- class SecurityRequirement(SpecificationObject):
14
+ @dataclass(frozen=True, slots=True)
15
+ class SecurityRequirement:
16
16
  """
17
- Represents a Security Requirement Object from OpenAPI 3.0.4.
17
+ Security Requirement Object representation for OpenAPI 3.0.
18
18
 
19
- This IS a mapping. Keys are security scheme names, values are lists of scope strings.
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.
20
21
 
21
- Lists the required security schemes to execute an operation. For each security
22
- scheme, a list of scope names is provided. When multiple Security Requirement
23
- Objects are defined, only ONE needs to be satisfied to authorize the request.
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
24
 
25
- IMPORTANT: Security Requirement Objects do NOT support specification extensions.
26
- Any key starting with "x-" is treated as a security scheme name, not an extension.
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
27
 
28
- Example:
29
- >>> # Non-OAuth2 requirement
30
- >>> req = SecurityRequirement({"api_key": []})
31
- >>> req["api_key"]
32
- []
33
-
34
- >>> # OAuth2 requirement with scopes
35
- >>> req = SecurityRequirement({"petstore_auth": ["write:pets", "read:pets"]})
36
- >>> req["petstore_auth"]
37
- ['write:pets', 'read:pets']
38
-
39
- >>> # Empty requirement (makes security optional)
40
- >>> req = SecurityRequirement({})
41
-
42
- >>> # Security scheme named "x-custom" (NOT an extension)
43
- >>> req = SecurityRequirement({"x-custom": []})
44
- >>> "x-custom" in req
45
- True
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.
46
33
  """
47
34
 
48
- _supports_extensions: bool = False
49
-
50
- def __getitem__(self, key: str) -> list[str]:
51
- """Get scopes for a security scheme (dict-style access)."""
52
- return super().__getitem__(key) # type: ignore
53
-
54
- def __getattr__(self, name: str) -> list[str]:
55
- """Get scopes for a security scheme (attribute-style access)."""
56
- return super().__getattr__(name) # type: ignore
57
-
58
- def get_schemes(self) -> list[str]:
59
- """
60
- Get the list of security scheme names referenced.
61
-
62
- Returns:
63
- List of security scheme names
64
- """
65
- return list(self.keys())
35
+ root_node: yaml.Node
36
+ requirements: ValueSource[dict[KeySource[str], ValueSource[list[ValueSource[str]]]]] | None = (
37
+ patterned_field()
38
+ )
66
39
 
67
- def get_scopes(self, scheme_name: str) -> list[str]:
68
- """
69
- Get the scopes required for a specific security scheme.
70
40
 
71
- Args:
72
- scheme_name: Name of the security scheme
73
-
74
- Returns:
75
- List of scope names (empty for non-OAuth2/OIDC schemes)
41
+ def build(root: yaml.Node, context: Context | None = None) -> SecurityRequirement | None:
42
+ """
43
+ Build a SecurityRequirement object from a YAML node.
76
44
 
77
- Raises:
78
- KeyError: If scheme_name is not in this requirement
79
- """
80
- return self[scheme_name]
45
+ Preserves all source data as-is, regardless of type. This is a low-level/plumbing
46
+ model that provides complete source fidelity for inspection and validation.
81
47
 
82
- def is_empty(self) -> bool:
83
- """
84
- Check if this is an empty security requirement.
48
+ Args:
49
+ root: The YAML node to parse (should be a MappingNode)
50
+ context: Optional parsing context. If None, a default context will be created.
85
51
 
86
- Empty requirements ({}) make security optional for an operation.
52
+ Returns:
53
+ A SecurityRequirement object if the node is valid, None otherwise
87
54
 
88
- Returns:
89
- True if requirements mapping is empty
90
- """
91
- return len(self) == 0
55
+ Example:
56
+ from ruamel.yaml import YAML
57
+ yaml = YAML()
58
+ root = yaml.compose("api_key: []")
59
+ security_req = build(root)
60
+ assert security_req.requirements is not None
61
+ """
62
+ if not isinstance(root, MappingNode):
63
+ return None
64
+
65
+ if context is None:
66
+ context = Context()
67
+
68
+ requirements_dict: dict[KeySource[str], ValueSource[list[ValueSource[str]]]] = {}
69
+
70
+ for key_node, value_node in root.value:
71
+ key = context.yaml_constructor.construct_yaml_str(key_node)
72
+
73
+ # Skip non-string keys
74
+ if not isinstance(key, str):
75
+ continue
76
+
77
+ # Security scheme requirement field
78
+ # For requirements, we need to wrap each scope string in ValueSource
79
+ if isinstance(value_node, SequenceNode):
80
+ # Wrap each scope string in the array with its source node
81
+ scope_list: list[ValueSource[str]] = []
82
+ for item_node in value_node.value:
83
+ item_value = context.yaml_constructor.construct_object(item_node, deep=True)
84
+ scope_list.append(ValueSource(value=item_value, value_node=item_node))
85
+
86
+ requirements_dict[KeySource(value=key, key_node=key_node)] = ValueSource(
87
+ value=scope_list, value_node=value_node
88
+ )
89
+ else:
90
+ # Not a sequence - preserve as-is for validation to catch
91
+ value = context.yaml_constructor.construct_object(value_node, deep=True)
92
+ requirements_dict[KeySource(value=key, key_node=key_node)] = ValueSource(
93
+ value=value, value_node=value_node
94
+ )
95
+
96
+ return SecurityRequirement(
97
+ root_node=root,
98
+ requirements=(
99
+ ValueSource(value=requirements_dict, value_node=root) if requirements_dict else None
100
+ ),
101
+ )
@@ -1,301 +1,109 @@
1
- """
2
- OpenAPI 3.0.4 Security Scheme Object model.
3
-
4
- Defines a security scheme that can be used by the operations.
5
- """
6
-
7
- from collections.abc import Mapping
8
- from typing import Any
9
-
1
+ from dataclasses import dataclass, field, replace
2
+
3
+ from ruamel import yaml
4
+
5
+ from jentic.apitools.openapi.datamodels.low.context import Context
6
+ from jentic.apitools.openapi.datamodels.low.fields import fixed_field
7
+ from jentic.apitools.openapi.datamodels.low.model_builder import build_model
8
+ from jentic.apitools.openapi.datamodels.low.sources import (
9
+ FieldSource,
10
+ KeySource,
11
+ ValueSource,
12
+ YAMLInvalidValue,
13
+ YAMLValue,
14
+ )
10
15
  from jentic.apitools.openapi.datamodels.low.v30.oauth_flows import OAuthFlows
11
- from jentic.apitools.openapi.datamodels.low.v30.specification_object import SpecificationObject
16
+ from jentic.apitools.openapi.datamodels.low.v30.oauth_flows import build as build_oauth_flows
12
17
 
13
18
 
14
- __all__ = ["SecurityScheme"]
19
+ __all__ = ["SecurityScheme", "build"]
15
20
 
16
21
 
17
- class SecurityScheme(SpecificationObject):
22
+ @dataclass(frozen=True, slots=True)
23
+ class SecurityScheme:
18
24
  """
19
- Represents a Security Scheme Object from OpenAPI 3.0.4.
20
-
21
- Defines a security scheme that can be used by the operations. Different
22
- scheme types require different combinations of fields.
23
-
24
- Supports specification extensions (x-* fields).
25
-
26
- Example:
27
- >>> # API Key scheme
28
- >>> scheme = SecurityScheme({
29
- ... "type": "apiKey",
30
- ... "name": "api_key",
31
- ... "in": "header"
32
- ... })
33
- >>> scheme.type
34
- 'apiKey'
35
- >>> scheme.in_
36
- 'header'
37
-
38
- >>> # HTTP Bearer scheme
39
- >>> scheme = SecurityScheme({
40
- ... "type": "http",
41
- ... "scheme": "bearer",
42
- ... "bearerFormat": "JWT"
43
- ... })
44
- >>> scheme.is_http()
45
- True
46
- >>> scheme.bearer_format
47
- 'JWT'
48
-
49
- >>> # OAuth2 scheme with flows
50
- >>> scheme = SecurityScheme({
51
- ... "type": "oauth2",
52
- ... "flows": {
53
- ... "implicit": {
54
- ... "authorizationUrl": "https://example.com/oauth/authorize",
55
- ... "scopes": {"read": "Read access"}
56
- ... }
57
- ... }
58
- ... })
59
- >>> scheme.is_oauth2()
60
- True
61
- >>> scheme.flows.implicit.authorization_url
62
- 'https://example.com/oauth/authorize'
63
-
64
- >>> # OpenID Connect scheme
65
- >>> scheme = SecurityScheme({
66
- ... "type": "openIdConnect",
67
- ... "openIdConnectUrl": "https://example.com/.well-known/openid-configuration"
68
- ... })
69
- >>> scheme.is_openid_connect()
70
- True
25
+ Security Scheme Object representation for OpenAPI 3.0.
26
+
27
+ Defines a security scheme that can be used by operations.
28
+ The security scheme type determines which additional fields are required.
29
+
30
+ Attributes:
31
+ root_node: The top-level node representing the entire Security Scheme object in the original source file
32
+ type: REQUIRED. The type of the security scheme. Valid values: "apiKey", "http", "oauth2", "openIdConnect"
33
+ description: A short description for the security scheme. CommonMark syntax MAY be used for rich text representation
34
+ name: REQUIRED for apiKey. The name of the header, query or cookie parameter to be used
35
+ in_: REQUIRED for apiKey. The location of the API key. Valid values: "query", "header", "cookie"
36
+ scheme: REQUIRED for http. The name of the HTTP Authorization scheme (e.g., "basic", "bearer")
37
+ bearer_format: A hint to the client to identify how the bearer token is formatted (e.g., "JWT")
38
+ flows: REQUIRED for oauth2. An object containing configuration information for the flow types supported
39
+ openid_connect_url: REQUIRED for openIdConnect. OpenId Connect URL to discover OAuth2 configuration values
40
+ extensions: Specification extensions (x-* fields)
71
41
  """
72
42
 
73
- _supports_extensions: bool = True
74
- _fixed_fields: frozenset[str] = frozenset(
75
- {"type", "description", "name", "in", "scheme", "bearerFormat", "flows", "openIdConnectUrl"}
43
+ root_node: yaml.Node
44
+ type: FieldSource[str] | None = fixed_field()
45
+ description: FieldSource[str] | None = fixed_field()
46
+ name: FieldSource[str] | None = fixed_field()
47
+ in_: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "in"})
48
+ scheme: FieldSource[str] | None = fixed_field()
49
+ bearer_format: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "bearerFormat"})
50
+ flows: FieldSource[OAuthFlows] | None = fixed_field()
51
+ openid_connect_url: FieldSource[str] | None = fixed_field(
52
+ metadata={"yaml_name": "openIdConnectUrl"}
76
53
  )
54
+ extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
77
55
 
78
- def __init__(self, data: Mapping[str, Any] | None = None):
79
- """
80
- Initialize a SecurityScheme object.
81
-
82
- Automatically marshals nested flows data (Mapping) into OAuthFlows instance.
83
-
84
- Args:
85
- data: Optional mapping to initialize the object with
86
- """
87
- super().__init__()
88
- if data:
89
- for key, value in data.items():
90
- # Marshal flows field specifically if it's a raw Mapping (not already OAuthFlows)
91
- if (
92
- key == "flows"
93
- and isinstance(value, Mapping)
94
- and not isinstance(value, OAuthFlows)
95
- ):
96
- self[key] = OAuthFlows(value)
97
- else:
98
- # Store as-is (already OAuthFlows, extension, or other)
99
- self[key] = self._copy_value(value)
100
-
101
- @property
102
- def type(self) -> str | None:
103
- """
104
- The type of the security scheme.
105
-
106
- Valid values: "apiKey", "http", "oauth2", "openIdConnect", "mutualTLS"
107
-
108
- REQUIRED field.
109
-
110
- Returns:
111
- Security scheme type or None if not present
112
- """
113
- return self.get("type")
114
-
115
- @type.setter
116
- def type(self, value: str | None) -> None:
117
- """Set the security scheme type."""
118
- if value is None:
119
- self.pop("type", None)
120
- else:
121
- self["type"] = value
122
-
123
- @property
124
- def description(self) -> str | None:
125
- """
126
- A description for security scheme.
127
-
128
- Returns:
129
- Description or None if not present
130
- """
131
- return self.get("description")
132
-
133
- @description.setter
134
- def description(self, value: str | None) -> None:
135
- """Set the description."""
136
- if value is None:
137
- self.pop("description", None)
138
- else:
139
- self["description"] = value
140
-
141
- @property
142
- def name(self) -> str | None:
143
- """
144
- The name of the header, query or cookie parameter.
145
-
146
- REQUIRED for apiKey type.
147
-
148
- Returns:
149
- Parameter name or None if not present
150
- """
151
- return self.get("name")
152
-
153
- @name.setter
154
- def name(self, value: str | None) -> None:
155
- """Set the parameter name."""
156
- if value is None:
157
- self.pop("name", None)
158
- else:
159
- self["name"] = value
160
-
161
- @property
162
- def in_(self) -> str | None:
163
- """
164
- The location of the API key.
165
-
166
- Valid values: "query", "header", "cookie"
167
-
168
- REQUIRED for apiKey type.
169
56
 
170
- Note: Uses 'in_' to avoid Python keyword collision.
171
-
172
- Returns:
173
- Location or None if not present
174
- """
175
- return self.get("in")
176
-
177
- @in_.setter
178
- def in_(self, value: str | None) -> None:
179
- """Set the API key location."""
180
- if value is None:
181
- self.pop("in", None)
182
- else:
183
- self["in"] = value
184
-
185
- @property
186
- def scheme(self) -> str | None:
187
- """
188
- The name of the HTTP Authorization scheme.
189
-
190
- Examples: "bearer", "basic", "digest"
191
-
192
- REQUIRED for http type.
193
-
194
- Returns:
195
- Scheme name or None if not present
196
- """
197
- return self.get("scheme")
198
-
199
- @scheme.setter
200
- def scheme(self, value: str | None) -> None:
201
- """Set the HTTP scheme."""
202
- if value is None:
203
- self.pop("scheme", None)
204
- else:
205
- self["scheme"] = value
206
-
207
- @property
208
- def bearer_format(self) -> str | None:
209
- """
210
- A hint to the client to identify how the bearer token is formatted.
211
-
212
- Examples: "JWT", "opaque"
213
-
214
- Returns:
215
- Bearer format or None if not present
216
- """
217
- return self.get("bearerFormat")
218
-
219
- @bearer_format.setter
220
- def bearer_format(self, value: str | None) -> None:
221
- """Set the bearer format."""
222
- if value is None:
223
- self.pop("bearerFormat", None)
224
- else:
225
- self["bearerFormat"] = value
226
-
227
- @property
228
- def flows(self) -> OAuthFlows | None:
229
- """
230
- Configuration information for the OAuth flows.
231
-
232
- REQUIRED for oauth2 type.
233
-
234
- Returns:
235
- OAuthFlows instance or None if not present
236
- """
237
- return self.get("flows")
238
-
239
- @flows.setter
240
- def flows(self, value: OAuthFlows | None) -> None:
241
- """Set the OAuth flows configuration."""
242
- if value is None:
243
- self.pop("flows", None)
244
- else:
245
- self["flows"] = value
246
-
247
- @property
248
- def open_id_connect_url(self) -> str | None:
249
- """
250
- OpenID Connect URL to discover OAuth2 configuration values.
251
-
252
- REQUIRED for openIdConnect type.
253
-
254
- Returns:
255
- OpenID Connect URL or None if not present
256
- """
257
- return self.get("openIdConnectUrl")
258
-
259
- @open_id_connect_url.setter
260
- def open_id_connect_url(self, value: str | None) -> None:
261
- """Set the OpenID Connect URL."""
262
- if value is None:
263
- self.pop("openIdConnectUrl", None)
264
- else:
265
- self["openIdConnectUrl"] = value
266
-
267
- def is_api_key(self) -> bool:
268
- """
269
- Check if this is an API Key security scheme.
270
-
271
- Returns:
272
- True if type is "apiKey"
273
- """
274
- return self.type == "apiKey"
275
-
276
- def is_http(self) -> bool:
277
- """
278
- Check if this is an HTTP security scheme.
279
-
280
- Returns:
281
- True if type is "http"
282
- """
283
- return self.type == "http"
57
+ def build(
58
+ root: yaml.Node, context: Context | None = None
59
+ ) -> SecurityScheme | ValueSource[YAMLInvalidValue]:
60
+ """
61
+ Build a SecurityScheme object from a YAML node.
284
62
 
285
- def is_oauth2(self) -> bool:
286
- """
287
- Check if this is an OAuth2 security scheme.
63
+ Preserves all source data as-is, regardless of type. This is a low-level/plumbing
64
+ model that provides complete source fidelity for inspection and validation.
288
65
 
289
- Returns:
290
- True if type is "oauth2"
291
- """
292
- return self.type == "oauth2"
66
+ Args:
67
+ root: The YAML node to parse (should be a MappingNode)
68
+ context: Optional parsing context. If None, a default context will be created.
293
69
 
294
- def is_openid_connect(self) -> bool:
295
- """
296
- Check if this is an OpenID Connect security scheme.
70
+ Returns:
71
+ A SecurityScheme object if the node is valid, or a ValueSource containing
72
+ the invalid data if the root is not a MappingNode (preserving the invalid data
73
+ and its source location for validation).
297
74
 
298
- Returns:
299
- True if type is "openIdConnect"
300
- """
301
- return self.type == "openIdConnect"
75
+ Example:
76
+ from ruamel.yaml import YAML
77
+ yaml = YAML()
78
+ root = yaml.compose("type: apiKey\\nname: api_key\\nin: header")
79
+ security_scheme = build(root)
80
+ assert security_scheme.type.value == 'apiKey'
81
+ """
82
+ # Initialize context once at the beginning
83
+ if context is None:
84
+ context = Context()
85
+
86
+ if not isinstance(root, yaml.MappingNode):
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
92
+ security_scheme = build_model(root, SecurityScheme, context=context)
93
+
94
+ # Manually handle special fields that build_model can't process (nested objects)
95
+ for key_node, value_node in root.value:
96
+ key = context.yaml_constructor.construct_yaml_str(key_node)
97
+
98
+ if key == "flows":
99
+ # Handle nested OAuthFlows object - child builder handles invalid nodes
100
+ # FieldSource will auto-unwrap ValueSource if child returns it for invalid data
101
+ flows = FieldSource(
102
+ value=build_oauth_flows(value_node, context=context),
103
+ key_node=key_node,
104
+ value_node=value_node,
105
+ )
106
+ security_scheme = replace(security_scheme, flows=flows)
107
+ break
108
+
109
+ return security_scheme