jentic-openapi-datamodels 1.0.0a12__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.0a12.dist-info/METADATA +0 -52
  22. jentic_openapi_datamodels-1.0.0a12.dist-info/RECORD +0 -18
  23. /jentic/apitools/openapi/datamodels/low/{v30/py.typed → __init__.py} +0 -0
  24. {jentic_openapi_datamodels-1.0.0a12.dist-info → jentic_openapi_datamodels-1.0.0a13.dist-info}/WHEEL +0 -0
  25. {jentic_openapi_datamodels-1.0.0a12.dist-info → jentic_openapi_datamodels-1.0.0a13.dist-info}/licenses/LICENSE +0 -0
  26. {jentic_openapi_datamodels-1.0.0a12.dist-info → jentic_openapi_datamodels-1.0.0a13.dist-info}/licenses/NOTICE +0 -0
@@ -1,79 +1,65 @@
1
- """
2
- OpenAPI 3.0.4 External Documentation Object model.
1
+ from dataclasses import dataclass, field
3
2
 
4
- Allows referencing an external resource for extended documentation.
5
- """
3
+ from ruamel import yaml
6
4
 
7
- from jentic.apitools.openapi.datamodels.low.v30.specification_object import SpecificationObject
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
+ )
8
15
 
9
16
 
10
- __all__ = ["ExternalDocumentation"]
17
+ __all__ = ["ExternalDocumentation", "build"]
11
18
 
12
19
 
13
- class ExternalDocumentation(SpecificationObject):
20
+ @dataclass(frozen=True, slots=True)
21
+ class ExternalDocumentation:
14
22
  """
15
- Represents an External Documentation Object from OpenAPI 3.0.4.
23
+ External Documentation Object representation for OpenAPI 3.0.
16
24
 
17
25
  Allows referencing an external resource for extended documentation.
18
26
 
19
- Supports specification extensions (x-* fields).
20
-
21
- Example:
22
- >>> # Basic external docs
23
- >>> docs = ExternalDocumentation({
24
- ... "url": "https://example.com/docs"
25
- ... })
26
- >>> docs.url
27
- 'https://example.com/docs'
28
-
29
- >>> # With description
30
- >>> docs = ExternalDocumentation({
31
- ... "description": "Find more info here",
32
- ... "url": "https://example.com/docs/api"
33
- ... })
34
- >>> docs.description
35
- 'Find more info here'
36
- >>> docs.url
37
- 'https://example.com/docs/api'
27
+ Attributes:
28
+ root_node: The top-level node representing the entire External Documentation object in the original source file
29
+ description: A short description of the target documentation. CommonMark syntax MAY be used for rich text representation.
30
+ url: The URL for the target documentation. REQUIRED. Value MUST be in the format of a URL.
31
+ extensions: Specification extensions (x-* fields)
38
32
  """
39
33
 
40
- _supports_extensions: bool = True
41
- _fixed_fields: frozenset[str] = frozenset({"description", "url"})
42
-
43
- @property
44
- def description(self) -> str | None:
45
- """
46
- A description of the target documentation.
34
+ root_node: yaml.Node
35
+ description: FieldSource[str] | None = fixed_field()
36
+ url: FieldSource[str] | None = fixed_field()
37
+ extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
47
38
 
48
- Returns:
49
- Description or None if not present
50
- """
51
- return self.get("description")
52
39
 
53
- @description.setter
54
- def description(self, value: str | None) -> None:
55
- """Set the description."""
56
- if value is None:
57
- self.pop("description", None)
58
- else:
59
- self["description"] = value
40
+ def build(
41
+ root: yaml.Node, context: Context | None = None
42
+ ) -> ExternalDocumentation | ValueSource[YAMLInvalidValue]:
43
+ """
44
+ Build an ExternalDocumentation object from a YAML node.
60
45
 
61
- @property
62
- def url(self) -> str | None:
63
- """
64
- The URL for the target documentation.
46
+ Preserves all source data as-is, regardless of type. This is a low-level/plumbing
47
+ model that provides complete source fidelity for inspection and validation.
65
48
 
66
- REQUIRED field.
49
+ Args:
50
+ root: The YAML node to parse (should be a MappingNode)
51
+ context: Optional parsing context. If None, a default context will be created.
67
52
 
68
- Returns:
69
- URL or None if not present
70
- """
71
- return self.get("url")
53
+ Returns:
54
+ An ExternalDocumentation object if the node is valid, or a ValueSource containing
55
+ the invalid data if the root is not a MappingNode (preserving the invalid data
56
+ and its source location for validation).
72
57
 
73
- @url.setter
74
- def url(self, value: str | None) -> None:
75
- """Set the URL."""
76
- if value is None:
77
- self.pop("url", None)
78
- else:
79
- self["url"] = value
58
+ Example:
59
+ from ruamel.yaml import YAML
60
+ yaml = YAML()
61
+ root = yaml.compose("url: https://example.com\\ndescription: Find more info here")
62
+ external_docs = build(root)
63
+ assert external_docs.url.value == 'https://example.com'
64
+ """
65
+ return build_model(root, ExternalDocumentation, context=context)
@@ -1,140 +1,71 @@
1
- """
2
- OpenAPI 3.0.4 OAuth Flow Object model.
1
+ from dataclasses import dataclass, field
3
2
 
4
- Configuration details for a supported OAuth Flow as defined in RFC 6749.
5
- Different OAuth flows use different combinations of the fields.
6
- """
3
+ from ruamel import yaml
7
4
 
8
- from collections.abc import Mapping
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
+ )
9
15
 
10
- from jentic.apitools.openapi.datamodels.low.v30.specification_object import SpecificationObject
11
16
 
17
+ __all__ = ["OAuthFlow", "build"]
12
18
 
13
- __all__ = ["OAuthFlow"]
14
19
 
15
-
16
- class OAuthFlow(SpecificationObject):
20
+ @dataclass(frozen=True, slots=True)
21
+ class OAuthFlow:
17
22
  """
18
- Represents an OAuth Flow Object from OpenAPI 3.0.4.
19
-
20
- Configuration details for a supported OAuth Flow. Different flow types
21
- (authorization code, implicit, password, client credentials) use different
22
- combinations of these fields.
23
-
24
- Supports specification extensions (x-* fields).
25
-
26
- Example:
27
- >>> # Authorization Code flow
28
- >>> flow = OAuthFlow({
29
- ... "authorizationUrl": "https://example.com/oauth/authorize",
30
- ... "tokenUrl": "https://example.com/oauth/token",
31
- ... "scopes": {
32
- ... "read:pets": "Read access to pets",
33
- ... "write:pets": "Write access to pets"
34
- ... }
35
- ... })
36
- >>> flow.authorization_url
37
- 'https://example.com/oauth/authorize'
38
- >>> flow.scopes
39
- {'read:pets': 'Read access to pets', 'write:pets': 'Write access to pets'}
23
+ OAuth Flow Object representation for OpenAPI 3.0.
40
24
 
41
- >>> # Implicit flow
42
- >>> flow = OAuthFlow({
43
- ... "authorizationUrl": "https://example.com/oauth/authorize",
44
- ... "scopes": {"read": "Read access"}
45
- ... })
46
- >>> print(flow.token_url)
47
- None
25
+ Configuration details for a supported OAuth Flow.
48
26
 
49
- >>> # With extensions
50
- >>> flow = OAuthFlow({
51
- ... "tokenUrl": "https://example.com/oauth/token",
52
- ... "scopes": {},
53
- ... "x-token-ttl": 3600
54
- ... })
55
- >>> flow.get_extensions()
56
- {'x-token-ttl': 3600}
27
+ Attributes:
28
+ root_node: The top-level node representing the entire OAuth Flow object in the original source file
29
+ authorization_url: The authorization URL to be used for this flow. REQUIRED for implicit and authorization_code flows.
30
+ token_url: The token URL to be used for this flow. REQUIRED for password, client_credentials, and authorization_code flows.
31
+ refresh_url: The URL to be used for obtaining refresh tokens. This MUST be in the form of a URL.
32
+ scopes: The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it.
33
+ extensions: Specification extensions (x-* fields)
57
34
  """
58
35
 
59
- _supports_extensions: bool = True
60
-
61
- @property
62
- def authorization_url(self) -> str | None:
63
- """
64
- OAuth authorization endpoint URL.
65
-
66
- REQUIRED for: authorizationCode, implicit flows.
67
-
68
- Returns:
69
- Authorization URL or None if not present
70
- """
71
- return self.get("authorizationUrl")
72
-
73
- @authorization_url.setter
74
- def authorization_url(self, value: str | None) -> None:
75
- """Set the authorization URL."""
76
- if value is None:
77
- self.pop("authorizationUrl", None)
78
- else:
79
- self["authorizationUrl"] = value
36
+ root_node: yaml.Node
37
+ authorization_url: FieldSource[str] | None = fixed_field(
38
+ metadata={"yaml_name": "authorizationUrl"}
39
+ )
40
+ token_url: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "tokenUrl"})
41
+ refresh_url: FieldSource[str] | None = fixed_field(metadata={"yaml_name": "refreshUrl"})
42
+ scopes: FieldSource[dict[KeySource[str], ValueSource[str]]] | None = fixed_field()
43
+ extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
80
44
 
81
- @property
82
- def token_url(self) -> str | None:
83
- """
84
- OAuth token endpoint URL.
85
45
 
86
- REQUIRED for: authorizationCode, password, clientCredentials flows.
87
-
88
- Returns:
89
- Token URL or None if not present
90
- """
91
- return self.get("tokenUrl")
92
-
93
- @token_url.setter
94
- def token_url(self, value: str | None) -> None:
95
- """Set the token URL."""
96
- if value is None:
97
- self.pop("tokenUrl", None)
98
- else:
99
- self["tokenUrl"] = value
100
-
101
- @property
102
- def refresh_url(self) -> str | None:
103
- """
104
- OAuth refresh token endpoint URL (optional for all flows).
105
-
106
- Returns:
107
- Refresh URL or None if not present
108
- """
109
- return self.get("refreshUrl")
110
-
111
- @refresh_url.setter
112
- def refresh_url(self, value: str | None) -> None:
113
- """Set the refresh URL."""
114
- if value is None:
115
- self.pop("refreshUrl", None)
116
- else:
117
- self["refreshUrl"] = value
46
+ def build(
47
+ root: yaml.Node, context: Context | None = None
48
+ ) -> OAuthFlow | ValueSource[YAMLInvalidValue]:
49
+ """
50
+ Build an OAuthFlow object from a YAML node.
118
51
 
119
- @property
120
- def scopes(self) -> dict[str, str]:
121
- """
122
- Available scopes for the OAuth2 security scheme.
52
+ Preserves all source data as-is, regardless of type. This is a low-level/plumbing
53
+ model that provides complete source fidelity for inspection and validation.
123
54
 
124
- Maps scope names to their descriptions. REQUIRED for all flows.
55
+ Args:
56
+ root: The YAML node to parse (should be a MappingNode)
57
+ context: Optional parsing context. If None, a default context will be created.
125
58
 
126
- Returns:
127
- Dictionary mapping scope names to descriptions (empty dict if not present)
128
- """
129
- scopes = self.get("scopes")
130
- if scopes is None:
131
- return {}
132
- return dict(scopes) if isinstance(scopes, Mapping) else {}
59
+ Returns:
60
+ An OAuthFlow object if the node is valid, or a ValueSource containing
61
+ the invalid data if the root is not a MappingNode (preserving the invalid data
62
+ and its source location for validation).
133
63
 
134
- @scopes.setter
135
- def scopes(self, value: dict[str, str] | Mapping[str, str] | None) -> None:
136
- """Set the scopes mapping."""
137
- if value is None:
138
- self.pop("scopes", None)
139
- else:
140
- self["scopes"] = dict(value) if isinstance(value, Mapping) else value
64
+ Example:
65
+ from ruamel.yaml import YAML
66
+ yaml = YAML()
67
+ root = yaml.compose("tokenUrl: https://example.com/oauth/token\\nscopes:\\n read: Read access")
68
+ oauth_flow = build(root)
69
+ assert oauth_flow.tokenUrl.value == 'https://example.com/oauth/token'
70
+ """
71
+ return build_model(root, OAuthFlow, context=context)
@@ -1,165 +1,116 @@
1
- """
2
- OpenAPI 3.0.4 OAuth Flows Object model.
3
-
4
- Allows configuration of the supported OAuth Flows.
5
- """
6
-
7
- from collections.abc import Mapping
1
+ from dataclasses import dataclass, field
8
2
  from typing import Any
9
3
 
4
+ from ruamel import yaml
5
+
6
+ from jentic.apitools.openapi.datamodels.low.context import Context
7
+ from jentic.apitools.openapi.datamodels.low.extractors import extract_extension_fields
8
+ from jentic.apitools.openapi.datamodels.low.fields import fixed_field, fixed_fields
9
+ from jentic.apitools.openapi.datamodels.low.sources import (
10
+ FieldSource,
11
+ KeySource,
12
+ ValueSource,
13
+ YAMLInvalidValue,
14
+ YAMLValue,
15
+ )
10
16
  from jentic.apitools.openapi.datamodels.low.v30.oauth_flow import OAuthFlow
11
- from jentic.apitools.openapi.datamodels.low.v30.specification_object import SpecificationObject
17
+ from jentic.apitools.openapi.datamodels.low.v30.oauth_flow import build as build_oauth_flow
12
18
 
13
19
 
14
- __all__ = ["OAuthFlows"]
20
+ __all__ = ["OAuthFlows", "build"]
15
21
 
16
22
 
17
- class OAuthFlows(SpecificationObject):
23
+ @dataclass(frozen=True, slots=True)
24
+ class OAuthFlows:
25
+ """
26
+ OAuth Flows Object representation for OpenAPI 3.0.
27
+
28
+ A container for the list of possible OAuth 2.0 authorization flows.
29
+ Used within Security Scheme Objects when type is set to "oauth2".
30
+
31
+ Attributes:
32
+ root_node: The top-level node representing the entire OAuth Flows object in the original source file
33
+ implicit: Configuration for the OAuth Implicit flow
34
+ password: Configuration for the OAuth Resource Owner Password flow
35
+ client_credentials: Configuration for the OAuth Client Credentials flow (application flow)
36
+ authorization_code: Configuration for the OAuth Authorization Code flow (three-legged OAuth)
37
+ extensions: Specification extensions (x-* fields)
18
38
  """
19
- Represents an OAuth Flows Object from OpenAPI 3.0.4.
20
39
 
21
- Allows configuration of the supported OAuth Flows. Each property corresponds
22
- to a different OAuth 2.0 flow type as defined in RFC 6749.
40
+ root_node: yaml.Node
41
+ implicit: FieldSource[OAuthFlow] | None = fixed_field()
42
+ password: FieldSource[OAuthFlow] | None = fixed_field()
43
+ client_credentials: FieldSource[OAuthFlow] | None = fixed_field(
44
+ metadata={"yaml_name": "clientCredentials"}
45
+ )
46
+ authorization_code: FieldSource[OAuthFlow] | None = fixed_field(
47
+ metadata={"yaml_name": "authorizationCode"}
48
+ )
49
+ extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
23
50
 
24
- Supports specification extensions (x-* fields).
25
51
 
26
- Example:
27
- >>> # Multiple flows
28
- >>> flows = OAuthFlows({
29
- ... "implicit": {
30
- ... "authorizationUrl": "https://example.com/oauth/authorize",
31
- ... "scopes": {"read": "Read access", "write": "Write access"}
32
- ... },
33
- ... "authorizationCode": {
34
- ... "authorizationUrl": "https://example.com/oauth/authorize",
35
- ... "tokenUrl": "https://example.com/oauth/token",
36
- ... "scopes": {"read": "Read access", "write": "Write access"}
37
- ... }
38
- ... })
39
- >>> flows.implicit.authorization_url
40
- 'https://example.com/oauth/authorize'
41
- >>> flows.authorization_code.token_url
42
- 'https://example.com/oauth/token'
43
-
44
- >>> # Single flow
45
- >>> flows = OAuthFlows({
46
- ... "clientCredentials": {
47
- ... "tokenUrl": "https://example.com/oauth/token",
48
- ... "scopes": {"api": "API access"}
49
- ... }
50
- ... })
51
- >>> flows.client_credentials.scopes
52
- {'api': 'API access'}
53
- >>> print(flows.implicit)
54
- None
55
-
56
- >>> # With extensions
57
- >>> flows = OAuthFlows({
58
- ... "password": {
59
- ... "tokenUrl": "https://example.com/oauth/token",
60
- ... "scopes": {}
61
- ... },
62
- ... "x-flow-timeout": 3600
63
- ... })
64
- >>> flows.get_extensions()
65
- {'x-flow-timeout': 3600}
52
+ def build(
53
+ root: yaml.Node, context: Context | None = None
54
+ ) -> OAuthFlows | ValueSource[YAMLInvalidValue]:
66
55
  """
56
+ Build an OAuthFlows object from a YAML node.
67
57
 
68
- _supports_extensions: bool = True
69
- _fixed_fields: frozenset[str] = frozenset(
70
- {"implicit", "password", "clientCredentials", "authorizationCode"}
71
- )
58
+ Preserves all source data as-is, regardless of type. This is a low-level/plumbing
59
+ model that provides complete source fidelity for inspection and validation.
60
+
61
+ Args:
62
+ root: The YAML node to parse (should be a MappingNode)
63
+ context: Optional parsing context. If None, a default context will be created.
64
+
65
+ Returns:
66
+ An OAuthFlows object if the node is valid, or a ValueSource containing
67
+ the invalid data if the root is not a MappingNode (preserving the invalid data
68
+ and its source location for validation).
72
69
 
73
- def __init__(self, data: Mapping[str, Any] | None = None):
74
- """
75
- Initialize an OAuthFlows object.
76
-
77
- Automatically marshals nested flow data (Mappings) into OAuthFlow instances.
78
-
79
- Args:
80
- data: Optional mapping to initialize the object with
81
- """
82
- super().__init__()
83
- if data:
84
- for key, value in data.items():
85
- if (
86
- key in self._fixed_fields
87
- and isinstance(value, Mapping)
88
- and not isinstance(value, OAuthFlow)
89
- ):
90
- self[key] = OAuthFlow(value)
91
- else:
92
- # Store as-is (already OAuthFlow, extension, or other)
93
- self[key] = self._copy_value(value)
94
-
95
- @property
96
- def implicit(self) -> OAuthFlow | None:
97
- """
98
- Configuration for the OAuth Implicit flow.
99
-
100
- Returns:
101
- OAuthFlow instance or None if not configured
102
- """
103
- return self.get("implicit")
104
-
105
- @implicit.setter
106
- def implicit(self, value: OAuthFlow | None) -> None:
107
- """Set the implicit flow configuration."""
108
- if value is None:
109
- self.pop("implicit", None)
110
- else:
111
- self["implicit"] = value
112
-
113
- @property
114
- def password(self) -> OAuthFlow | None:
115
- """
116
- Configuration for the OAuth Resource Owner Password flow.
117
-
118
- Returns:
119
- OAuthFlow instance or None if not configured
120
- """
121
- return self.get("password")
122
-
123
- @password.setter
124
- def password(self, value: OAuthFlow | None) -> None:
125
- """Set the password flow configuration."""
126
- if value is None:
127
- self.pop("password", None)
128
- else:
129
- self["password"] = value
130
-
131
- @property
132
- def client_credentials(self) -> OAuthFlow | None:
133
- """
134
- Configuration for the OAuth Client Credentials flow.
135
-
136
- Returns:
137
- OAuthFlow instance or None if not configured
138
- """
139
- return self.get("clientCredentials")
140
-
141
- @client_credentials.setter
142
- def client_credentials(self, value: OAuthFlow | None) -> None:
143
- """Set the client credentials flow configuration."""
144
- if value is None:
145
- self.pop("clientCredentials", None)
146
- else:
147
- self["clientCredentials"] = value
148
-
149
- @property
150
- def authorization_code(self) -> OAuthFlow | None:
151
- """
152
- Configuration for the OAuth Authorization Code flow.
153
-
154
- Returns:
155
- OAuthFlow instance or None if not configured
156
- """
157
- return self.get("authorizationCode")
158
-
159
- @authorization_code.setter
160
- def authorization_code(self, value: OAuthFlow | None) -> None:
161
- """Set the authorization code flow configuration."""
162
- if value is None:
163
- self.pop("authorizationCode", None)
164
- else:
165
- self["authorizationCode"] = value
70
+ Example:
71
+ from ruamel.yaml import YAML
72
+ yaml = YAML()
73
+ root = yaml.compose("implicit:\\n authorizationUrl: https://example.com/auth\\n scopes: {}")
74
+ flows = build(root)
75
+ assert flows.implicit.value.authorization_url.value == 'https://example.com/auth'
76
+ """
77
+ # Initialize context once at the beginning
78
+ if context is None:
79
+ context = Context()
80
+
81
+ if not isinstance(root, yaml.MappingNode):
82
+ # Preserve invalid root data instead of returning None
83
+ value = context.yaml_constructor.construct_object(root, deep=True)
84
+ return ValueSource(value=value, value_node=root)
85
+
86
+ # Get fixed specification fields for this dataclass type
87
+ _fixed_fields = fixed_fields(OAuthFlows)
88
+
89
+ # Build YAML name to Python field name mapping
90
+ yaml_to_field = {
91
+ field.metadata.get("yaml_name", fname): fname for fname, field in _fixed_fields.items()
92
+ }
93
+
94
+ # Extract field values in a single pass
95
+ field_values: dict[str, Any] = {}
96
+ for key_node, value_node in root.value:
97
+ key = context.yaml_constructor.construct_yaml_str(key_node)
98
+
99
+ # Map YAML key to Python field name
100
+ field_name = yaml_to_field.get(key)
101
+ if field_name:
102
+ # Build OAuthFlow for this field - child builder handles invalid nodes
103
+ # FieldSource will auto-unwrap ValueSource if child returns it for invalid data
104
+ oauth_flow = build_oauth_flow(value_node, context=context)
105
+ field_values[field_name] = FieldSource(
106
+ value=oauth_flow, key_node=key_node, value_node=value_node
107
+ )
108
+
109
+ return OAuthFlows(
110
+ root_node=root,
111
+ implicit=field_values.get("implicit"),
112
+ password=field_values.get("password"),
113
+ client_credentials=field_values.get("client_credentials"),
114
+ authorization_code=field_values.get("authorization_code"),
115
+ extensions=extract_extension_fields(root, context),
116
+ )