jentic-openapi-datamodels 1.0.0a11__tar.gz → 1.0.0a13__tar.gz

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 (37) hide show
  1. jentic_openapi_datamodels-1.0.0a13/PKG-INFO +211 -0
  2. jentic_openapi_datamodels-1.0.0a13/README.md +197 -0
  3. {jentic_openapi_datamodels-1.0.0a11 → jentic_openapi_datamodels-1.0.0a13}/pyproject.toml +4 -2
  4. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/context.py +21 -0
  5. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/extractors.py +126 -0
  6. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/fields.py +45 -0
  7. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/model_builder.py +113 -0
  8. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/py.typed +0 -0
  9. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/sources.py +89 -0
  10. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/__init__.py +0 -0
  11. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/discriminator.py +66 -0
  12. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/external_documentation.py +65 -0
  13. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/oauth_flow.py +71 -0
  14. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/oauth_flows.py +116 -0
  15. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/reference.py +63 -0
  16. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/schema.py +335 -0
  17. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/security_requirement.py +101 -0
  18. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/security_scheme.py +109 -0
  19. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/tag.py +101 -0
  20. jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/v30/xml.py +60 -0
  21. jentic_openapi_datamodels-1.0.0a11/PKG-INFO +0 -52
  22. jentic_openapi_datamodels-1.0.0a11/README.md +0 -39
  23. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/__init__.py +0 -28
  24. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/discriminator.py +0 -91
  25. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/external_documentation.py +0 -79
  26. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/oauth_flow.py +0 -140
  27. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/oauth_flows.py +0 -165
  28. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/reference.py +0 -64
  29. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/schema.py +0 -626
  30. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/security_requirement.py +0 -91
  31. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/security_scheme.py +0 -301
  32. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/specification_object.py +0 -217
  33. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/tag.py +0 -132
  34. jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/xml.py +0 -134
  35. {jentic_openapi_datamodels-1.0.0a11 → jentic_openapi_datamodels-1.0.0a13}/LICENSE +0 -0
  36. {jentic_openapi_datamodels-1.0.0a11 → jentic_openapi_datamodels-1.0.0a13}/NOTICE +0 -0
  37. /jentic_openapi_datamodels-1.0.0a11/src/jentic/apitools/openapi/datamodels/low/v30/py.typed → /jentic_openapi_datamodels-1.0.0a13/src/jentic/apitools/openapi/datamodels/low/__init__.py +0 -0
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: jentic-openapi-datamodels
3
+ Version: 1.0.0a13
4
+ Summary: Jentic OpenAPI Data Models
5
+ Author: Jentic
6
+ Author-email: Jentic <hello@jentic.com>
7
+ License-Expression: Apache-2.0
8
+ License-File: LICENSE
9
+ License-File: NOTICE
10
+ Requires-Dist: ruamel-yaml~=0.18.15
11
+ Requires-Python: >=3.11
12
+ Project-URL: Homepage, https://github.com/jentic/jentic-openapi-tools
13
+ Description-Content-Type: text/markdown
14
+
15
+ from semantic_release.cli.commands.version import build_distributions
16
+
17
+ # jentic-openapi-datamodels
18
+
19
+ Low-level and high-level data models for OpenAPI specifications.
20
+
21
+ This package provides data model classes for representing OpenAPI specification objects in Python.
22
+
23
+ ## Features
24
+
25
+ **Low-Level Architecture**
26
+ - **Preserve Everything**: All data from source documents preserved exactly as-is, including invalid values
27
+ - **Zero Validation**: No validation or coercion during parsing - deferred to higher layers
28
+ - **Separation of Concerns**: Low-level model focuses on faithful representation; validation belongs elsewhere
29
+
30
+ **Source Tracking**
31
+ - **Complete Source Fidelity**: Every field tracks its exact YAML node location
32
+ - **Precise Error Reporting**: Line and column numbers via `start_mark` and `end_mark`
33
+ - **Metadata Preservation**: Full position tracking for accurate diagnostics
34
+
35
+ **Python Integration**
36
+ - **Python-Idiomatic Naming**: snake_case field names (e.g., `bearer_format`, `property_name`)
37
+ - **Spec-Aligned Mapping**: Automatic YAML name mapping (e.g., `bearerFormat` ↔ `bearer_format`)
38
+ - **Type Safety**: Full type hints with Generic types (`FieldSource[T]`, `KeySource[T]`, `ValueSource[T]`)
39
+
40
+ **Extensibility**
41
+ - **Extension Support**: Automatic extraction of OpenAPI `x-*` specification extensions
42
+ - **Unknown Field Tracking**: Capture typos and invalid fields for validation tools
43
+ - **Generic Builder Pattern**: Core `build_model()` function with object-specific builders for complex cases
44
+
45
+ **Performance**
46
+ - **Memory Efficient**: Immutable frozen dataclasses with `__slots__` for optimal memory usage
47
+ - **Shared Context**: All instances share a single YAML constructor for efficiency
48
+
49
+ **Version Support**
50
+ - **OpenAPI 2.0**: Planned for future release
51
+ - **OpenAPI 3.0.x**: Currently implemented
52
+ - **OpenAPI 3.1.x**: Planned for future release
53
+ - **OpenAPI 3.2.x**: Planned for future release
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install jentic-openapi-datamodels
59
+ ```
60
+
61
+ **Prerequisites:**
62
+ - Python 3.11+
63
+
64
+ ## Quick Start
65
+
66
+ ### Basic Usage
67
+
68
+ ```python
69
+ from ruamel.yaml import YAML
70
+ from jentic.apitools.openapi.datamodels.low.v30.security_scheme import build
71
+
72
+ # Parse YAML
73
+ yaml = YAML()
74
+ root = yaml.compose("""
75
+ type: http
76
+ scheme: bearer
77
+ bearerFormat: JWT
78
+ """)
79
+
80
+ # Build low-level model
81
+ security_scheme = build(root)
82
+
83
+ # Access via Python field names (snake_case)
84
+ print(security_scheme.bearer_format.value) # "JWT"
85
+
86
+ # Access source location information
87
+ print(security_scheme.bearer_format.key_node.value) # "bearerFormat"
88
+ print(security_scheme.bearer_format.key_node.start_mark.line) # Line number
89
+ ```
90
+
91
+ ### Field Name Mapping
92
+
93
+ YAML `camelCase` fields automatically map to Python `snake_case`:
94
+ - `bearerFormat` → `bearer_format`
95
+ - `authorizationUrl` → `authorization_url`
96
+ - `openIdConnectUrl` → `openid_connect_url`
97
+
98
+ Special cases for Python reserved keywords/special characters:
99
+ - `$ref` → `ref`
100
+ - `in` → `in_`
101
+
102
+ ### Source Tracking
103
+
104
+ The package provides three immutable wrapper types for preserving source information:
105
+
106
+ **FieldSource[T]** - For OpenAPI fields with key-value pairs
107
+ - Used for: Fixed fields (`name`, `bearer_format`) and patterned fields (status codes, path items, schema properties)
108
+ - Tracks: Both key and value nodes
109
+ - Example: `SecurityScheme.bearer_format` is `FieldSource[str]`, response status codes are `FieldSource[Response]`
110
+
111
+ **KeySource[T]** - For dictionary keys
112
+ - Used for: keys in OpenAPI fields, `x-*` extensions and mapping dictionaries
113
+ - Tracks: Only key node
114
+ - Example: Keys in `Discriminator.mapping` are `KeySource[str]`
115
+
116
+ **ValueSource[T]** - For dictionary values and array items
117
+ - Used for: values in OpenAPI fields, in `x-*` extensions, mapping dictionaries and array items
118
+ - Tracks: Only value node
119
+ - Example: Values in `Discriminator.mapping` are `ValueSource[str]`
120
+
121
+ ```python
122
+ from ruamel.yaml import YAML
123
+ from jentic.apitools.openapi.datamodels.low.v30.security_scheme import build as build_security_scheme
124
+ from jentic.apitools.openapi.datamodels.low.v30.discriminator import build as build_discriminator
125
+
126
+ # FieldSource: Fixed specification fields
127
+ yaml = YAML()
128
+ root = yaml.compose("type: http\nscheme: bearer\nbearerFormat: JWT")
129
+ security_scheme = build_security_scheme(root)
130
+
131
+ field = security_scheme.bearer_format # FieldSource[str]
132
+ print(field.value) # "JWT" - The actual value
133
+ print(field.key_node) # YAML node for "bearerFormat"
134
+ print(field.value_node) # YAML node for "JWT"
135
+
136
+ # KeySource/ValueSource: Dictionary fields (mapping, extensions)
137
+ root = yaml.compose("propertyName: petType\nmapping:\n dog: Dog\n cat: Cat")
138
+ discriminator = build_discriminator(root)
139
+
140
+ for key, value in discriminator.mapping.value.items():
141
+ print(key.value) # KeySource[str]: "dog" or "cat"
142
+ print(key.key_node) # YAML node for the key
143
+ print(value.value) # ValueSource[str]: "Dog" or "Cat"
144
+ print(value.value_node) # YAML node for the value
145
+ ```
146
+
147
+ ### Location Ranges
148
+
149
+ Access precise location ranges within the source document using start_mark and end_mark:
150
+
151
+ ```python
152
+ from ruamel.yaml import YAML
153
+ from jentic.apitools.openapi.datamodels.low.v30.security_scheme import build as build_security_scheme
154
+
155
+ yaml_content = """
156
+ type: http
157
+ scheme: bearer
158
+ bearerFormat: JWT
159
+ description: Bearer token authentication
160
+ """
161
+
162
+ yaml = YAML()
163
+ root = yaml.compose(yaml_content)
164
+ security_scheme = build_security_scheme(root)
165
+
166
+ # Access location information for any field
167
+ field = security_scheme.bearer_format
168
+
169
+ # Key location (e.g., "bearerFormat")
170
+ print(f"Key start: line {field.key_node.start_mark.line}, col {field.key_node.start_mark.column}")
171
+ print(f"Key end: line {field.key_node.end_mark.line}, col {field.key_node.end_mark.column}")
172
+
173
+ # Value location (e.g., "JWT")
174
+ print(f"Value start: line {field.value_node.start_mark.line}, col {field.value_node.start_mark.column}")
175
+ print(f"Value end: line {field.value_node.end_mark.line}, col {field.value_node.end_mark.column}")
176
+
177
+ # Full field range (from key start to value end)
178
+ start = field.key_node.start_mark
179
+ end = field.value_node.end_mark
180
+ print(f"Field range: ({start.line}:{start.column}) to ({end.line}:{end.column})")
181
+ ```
182
+
183
+ ### Invalid Data Handling
184
+
185
+ Low-level models preserve invalid data:
186
+
187
+ ```python
188
+ from ruamel.yaml import YAML
189
+ from jentic.apitools.openapi.datamodels.low.v30.security_scheme import build as build_security_scheme
190
+
191
+ yaml = YAML()
192
+ root = yaml.compose("bearerFormat: 123") # Wrong type (should be string)
193
+
194
+ security_scheme = build_security_scheme(root)
195
+ print(security_scheme.bearer_format.value) # 123 (preserved as-is)
196
+ print(type(security_scheme.bearer_format.value)) # <class 'int'>
197
+ ```
198
+
199
+ ### Error Reporting
200
+
201
+ This architecture—where the low-level model preserves data without validation and validation tools consume
202
+ that data—allows the low-level model to remain simple while enabling sophisticated validation tools to provide
203
+ user-friendly error messages with exact source locations.
204
+
205
+ ## Testing
206
+
207
+ Run the test suite:
208
+
209
+ ```bash
210
+ uv run --package jentic-openapi-datamodels pytest packages/jentic-openapi-datamodels -v
211
+ ```
@@ -0,0 +1,197 @@
1
+ from semantic_release.cli.commands.version import build_distributions
2
+
3
+ # jentic-openapi-datamodels
4
+
5
+ Low-level and high-level data models for OpenAPI specifications.
6
+
7
+ This package provides data model classes for representing OpenAPI specification objects in Python.
8
+
9
+ ## Features
10
+
11
+ **Low-Level Architecture**
12
+ - **Preserve Everything**: All data from source documents preserved exactly as-is, including invalid values
13
+ - **Zero Validation**: No validation or coercion during parsing - deferred to higher layers
14
+ - **Separation of Concerns**: Low-level model focuses on faithful representation; validation belongs elsewhere
15
+
16
+ **Source Tracking**
17
+ - **Complete Source Fidelity**: Every field tracks its exact YAML node location
18
+ - **Precise Error Reporting**: Line and column numbers via `start_mark` and `end_mark`
19
+ - **Metadata Preservation**: Full position tracking for accurate diagnostics
20
+
21
+ **Python Integration**
22
+ - **Python-Idiomatic Naming**: snake_case field names (e.g., `bearer_format`, `property_name`)
23
+ - **Spec-Aligned Mapping**: Automatic YAML name mapping (e.g., `bearerFormat` ↔ `bearer_format`)
24
+ - **Type Safety**: Full type hints with Generic types (`FieldSource[T]`, `KeySource[T]`, `ValueSource[T]`)
25
+
26
+ **Extensibility**
27
+ - **Extension Support**: Automatic extraction of OpenAPI `x-*` specification extensions
28
+ - **Unknown Field Tracking**: Capture typos and invalid fields for validation tools
29
+ - **Generic Builder Pattern**: Core `build_model()` function with object-specific builders for complex cases
30
+
31
+ **Performance**
32
+ - **Memory Efficient**: Immutable frozen dataclasses with `__slots__` for optimal memory usage
33
+ - **Shared Context**: All instances share a single YAML constructor for efficiency
34
+
35
+ **Version Support**
36
+ - **OpenAPI 2.0**: Planned for future release
37
+ - **OpenAPI 3.0.x**: Currently implemented
38
+ - **OpenAPI 3.1.x**: Planned for future release
39
+ - **OpenAPI 3.2.x**: Planned for future release
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install jentic-openapi-datamodels
45
+ ```
46
+
47
+ **Prerequisites:**
48
+ - Python 3.11+
49
+
50
+ ## Quick Start
51
+
52
+ ### Basic Usage
53
+
54
+ ```python
55
+ from ruamel.yaml import YAML
56
+ from jentic.apitools.openapi.datamodels.low.v30.security_scheme import build
57
+
58
+ # Parse YAML
59
+ yaml = YAML()
60
+ root = yaml.compose("""
61
+ type: http
62
+ scheme: bearer
63
+ bearerFormat: JWT
64
+ """)
65
+
66
+ # Build low-level model
67
+ security_scheme = build(root)
68
+
69
+ # Access via Python field names (snake_case)
70
+ print(security_scheme.bearer_format.value) # "JWT"
71
+
72
+ # Access source location information
73
+ print(security_scheme.bearer_format.key_node.value) # "bearerFormat"
74
+ print(security_scheme.bearer_format.key_node.start_mark.line) # Line number
75
+ ```
76
+
77
+ ### Field Name Mapping
78
+
79
+ YAML `camelCase` fields automatically map to Python `snake_case`:
80
+ - `bearerFormat` → `bearer_format`
81
+ - `authorizationUrl` → `authorization_url`
82
+ - `openIdConnectUrl` → `openid_connect_url`
83
+
84
+ Special cases for Python reserved keywords/special characters:
85
+ - `$ref` → `ref`
86
+ - `in` → `in_`
87
+
88
+ ### Source Tracking
89
+
90
+ The package provides three immutable wrapper types for preserving source information:
91
+
92
+ **FieldSource[T]** - For OpenAPI fields with key-value pairs
93
+ - Used for: Fixed fields (`name`, `bearer_format`) and patterned fields (status codes, path items, schema properties)
94
+ - Tracks: Both key and value nodes
95
+ - Example: `SecurityScheme.bearer_format` is `FieldSource[str]`, response status codes are `FieldSource[Response]`
96
+
97
+ **KeySource[T]** - For dictionary keys
98
+ - Used for: keys in OpenAPI fields, `x-*` extensions and mapping dictionaries
99
+ - Tracks: Only key node
100
+ - Example: Keys in `Discriminator.mapping` are `KeySource[str]`
101
+
102
+ **ValueSource[T]** - For dictionary values and array items
103
+ - Used for: values in OpenAPI fields, in `x-*` extensions, mapping dictionaries and array items
104
+ - Tracks: Only value node
105
+ - Example: Values in `Discriminator.mapping` are `ValueSource[str]`
106
+
107
+ ```python
108
+ from ruamel.yaml import YAML
109
+ from jentic.apitools.openapi.datamodels.low.v30.security_scheme import build as build_security_scheme
110
+ from jentic.apitools.openapi.datamodels.low.v30.discriminator import build as build_discriminator
111
+
112
+ # FieldSource: Fixed specification fields
113
+ yaml = YAML()
114
+ root = yaml.compose("type: http\nscheme: bearer\nbearerFormat: JWT")
115
+ security_scheme = build_security_scheme(root)
116
+
117
+ field = security_scheme.bearer_format # FieldSource[str]
118
+ print(field.value) # "JWT" - The actual value
119
+ print(field.key_node) # YAML node for "bearerFormat"
120
+ print(field.value_node) # YAML node for "JWT"
121
+
122
+ # KeySource/ValueSource: Dictionary fields (mapping, extensions)
123
+ root = yaml.compose("propertyName: petType\nmapping:\n dog: Dog\n cat: Cat")
124
+ discriminator = build_discriminator(root)
125
+
126
+ for key, value in discriminator.mapping.value.items():
127
+ print(key.value) # KeySource[str]: "dog" or "cat"
128
+ print(key.key_node) # YAML node for the key
129
+ print(value.value) # ValueSource[str]: "Dog" or "Cat"
130
+ print(value.value_node) # YAML node for the value
131
+ ```
132
+
133
+ ### Location Ranges
134
+
135
+ Access precise location ranges within the source document using start_mark and end_mark:
136
+
137
+ ```python
138
+ from ruamel.yaml import YAML
139
+ from jentic.apitools.openapi.datamodels.low.v30.security_scheme import build as build_security_scheme
140
+
141
+ yaml_content = """
142
+ type: http
143
+ scheme: bearer
144
+ bearerFormat: JWT
145
+ description: Bearer token authentication
146
+ """
147
+
148
+ yaml = YAML()
149
+ root = yaml.compose(yaml_content)
150
+ security_scheme = build_security_scheme(root)
151
+
152
+ # Access location information for any field
153
+ field = security_scheme.bearer_format
154
+
155
+ # Key location (e.g., "bearerFormat")
156
+ print(f"Key start: line {field.key_node.start_mark.line}, col {field.key_node.start_mark.column}")
157
+ print(f"Key end: line {field.key_node.end_mark.line}, col {field.key_node.end_mark.column}")
158
+
159
+ # Value location (e.g., "JWT")
160
+ print(f"Value start: line {field.value_node.start_mark.line}, col {field.value_node.start_mark.column}")
161
+ print(f"Value end: line {field.value_node.end_mark.line}, col {field.value_node.end_mark.column}")
162
+
163
+ # Full field range (from key start to value end)
164
+ start = field.key_node.start_mark
165
+ end = field.value_node.end_mark
166
+ print(f"Field range: ({start.line}:{start.column}) to ({end.line}:{end.column})")
167
+ ```
168
+
169
+ ### Invalid Data Handling
170
+
171
+ Low-level models preserve invalid data:
172
+
173
+ ```python
174
+ from ruamel.yaml import YAML
175
+ from jentic.apitools.openapi.datamodels.low.v30.security_scheme import build as build_security_scheme
176
+
177
+ yaml = YAML()
178
+ root = yaml.compose("bearerFormat: 123") # Wrong type (should be string)
179
+
180
+ security_scheme = build_security_scheme(root)
181
+ print(security_scheme.bearer_format.value) # 123 (preserved as-is)
182
+ print(type(security_scheme.bearer_format.value)) # <class 'int'>
183
+ ```
184
+
185
+ ### Error Reporting
186
+
187
+ This architecture—where the low-level model preserves data without validation and validation tools consume
188
+ that data—allows the low-level model to remain simple while enabling sophisticated validation tools to provide
189
+ user-friendly error messages with exact source locations.
190
+
191
+ ## Testing
192
+
193
+ Run the test suite:
194
+
195
+ ```bash
196
+ uv run --package jentic-openapi-datamodels pytest packages/jentic-openapi-datamodels -v
197
+ ```
@@ -1,13 +1,15 @@
1
1
  [project]
2
2
  name = "jentic-openapi-datamodels"
3
- version = "1.0.0-alpha.11"
3
+ version = "1.0.0-alpha.13"
4
4
  description = "Jentic OpenAPI Data Models"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Jentic", email = "hello@jentic.com" }]
7
7
  license = "Apache-2.0"
8
8
  license-files = ["LICENSE", "NOTICE"]
9
9
  requires-python = ">=3.11"
10
- dependencies = []
10
+ dependencies = [
11
+ "ruamel-yaml~=0.18.15"
12
+ ]
11
13
 
12
14
  [project.urls]
13
15
  Homepage = "https://github.com/jentic/jentic-openapi-tools"
@@ -0,0 +1,21 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from ruamel.yaml import YAML
4
+ from ruamel.yaml.constructor import RoundTripConstructor
5
+
6
+
7
+ __all__ = ["Context"]
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class Context:
12
+ """
13
+ Context for parsing OpenAPI documents.
14
+
15
+ Contains configuration and dependencies used during parsing operations.
16
+
17
+ Attributes:
18
+ yaml_constructor: The YAML constructor used to deserialize YAML nodes into Python objects
19
+ """
20
+
21
+ yaml_constructor: RoundTripConstructor = field(default=YAML(typ="rt", pure=True).constructor)
@@ -0,0 +1,126 @@
1
+ from ruamel import yaml
2
+
3
+ from jentic.apitools.openapi.datamodels.low.context import Context
4
+ from jentic.apitools.openapi.datamodels.low.fields import fixed_fields
5
+ from jentic.apitools.openapi.datamodels.low.sources import KeySource, ValueSource, YAMLValue
6
+
7
+
8
+ __all__ = ["extract_extension_fields", "extract_unknown_fields"]
9
+
10
+
11
+ def extract_extension_fields(
12
+ node: yaml.MappingNode, context: Context | None = None
13
+ ) -> dict[KeySource[str], ValueSource[YAMLValue]]:
14
+ """
15
+ Extract OpenAPI specification extension fields from a YAML mapping node.
16
+
17
+ Specification extension fields are any fields that start with "x-" and allow
18
+ users to add custom properties to OpenAPI definitions.
19
+
20
+ Args:
21
+ node: The YAML mapping node to extract extension fields from
22
+ context: Optional parsing context. If None, a default context will be created.
23
+
24
+ Returns:
25
+ A dictionary mapping extension field names to their values, or empty dict if no extension fields found
26
+
27
+ Example:
28
+ Given YAML like:
29
+ name: id
30
+ x-custom: value
31
+ x-internal: true
32
+
33
+ Returns:
34
+ {
35
+ KeySource(value="x-custom", key_node=...): ValueSource(value="value", value_node=...),
36
+ KeySource(value="x-internal", key_node=...): ValueSource(value=True, value_node=...)
37
+ }
38
+ """
39
+ if not isinstance(node, yaml.MappingNode):
40
+ return {}
41
+
42
+ if context is None:
43
+ context = Context()
44
+
45
+ extensions: dict[KeySource[str], ValueSource[YAMLValue]] = {}
46
+
47
+ for key_node, value_node in node.value:
48
+ # Construct the key as a Python object (should be a string)
49
+ py_key = context.yaml_constructor.construct_yaml_str(key_node)
50
+
51
+ # Check if it's an extension (starts with "x-")
52
+ if isinstance(py_key, str) and py_key.startswith("x-"):
53
+ # Construct the actual Python value from the YAML node
54
+ py_value = context.yaml_constructor.construct_object(value_node, deep=True)
55
+
56
+ key_ref = KeySource[str](value=py_key, key_node=key_node)
57
+ value_ref = ValueSource[YAMLValue](value=py_value, value_node=value_node)
58
+ extensions[key_ref] = value_ref
59
+
60
+ return extensions
61
+
62
+
63
+ def extract_unknown_fields(
64
+ node: yaml.MappingNode, dataclass_type: type, context: Context | None = None
65
+ ) -> dict[KeySource[str], ValueSource[YAMLValue]]:
66
+ """
67
+ Extract unknown fields from a YAML mapping node.
68
+
69
+ Unknown fields are fields that are not part of the OpenAPI specification
70
+ (not fixed fields of the dataclass) and are not extensions (don't start with "x-").
71
+ These are typically typos or fields from a different specification version.
72
+
73
+ Args:
74
+ node: The YAML mapping node to extract unknown fields from
75
+ dataclass_type: The dataclass type to get valid field names from
76
+ context: Optional parsing context. If None, a default context will be created.
77
+
78
+ Returns:
79
+ A dictionary mapping unknown field names to their values, or empty dict if no unknown fields found
80
+
81
+ Example:
82
+ Given YAML like:
83
+ name: id
84
+ namspace: http://example.com # typo - should be "namespace"
85
+ customField: value # unknown field
86
+ x-custom: value # extension - not unknown
87
+
88
+ With dataclass_type=XML (which has fixed fields: name, namespace, prefix, attribute, wrapped)
89
+ Returns:
90
+ {
91
+ KeySource(value="namspace", key_node=...): ValueSource(value="http://example.com", value_node=...),
92
+ KeySource(value="customField", key_node=...): ValueSource(value="value", value_node=...)
93
+ }
94
+ """
95
+ if not isinstance(node, yaml.MappingNode):
96
+ return {}
97
+
98
+ if context is None:
99
+ context = Context()
100
+
101
+ # Get valid YAML field names from the dataclass (considering yaml_name metadata)
102
+ _fixed_fields = fixed_fields(dataclass_type)
103
+ yaml_field_names = {
104
+ field.metadata.get("yaml_name", fname) for fname, field in _fixed_fields.items()
105
+ }
106
+
107
+ unknown_fields: dict[KeySource[str], ValueSource[YAMLValue]] = {}
108
+
109
+ for key_node, value_node in node.value:
110
+ # Construct the key as a Python object (should be a string)
111
+ py_key = context.yaml_constructor.construct_yaml_str(key_node)
112
+
113
+ # Check if it's an unknown field (not in valid YAML field names and not an extension)
114
+ if (
115
+ isinstance(py_key, str)
116
+ and py_key not in yaml_field_names
117
+ and not py_key.startswith("x-")
118
+ ):
119
+ # Construct the actual Python value from the YAML node
120
+ py_value = context.yaml_constructor.construct_object(value_node, deep=True)
121
+
122
+ key_ref = KeySource[str](value=py_key, key_node=key_node)
123
+ value_ref = ValueSource[YAMLValue](value=py_value, value_node=value_node)
124
+ unknown_fields[key_ref] = value_ref
125
+
126
+ return unknown_fields
@@ -0,0 +1,45 @@
1
+ from dataclasses import field, fields
2
+
3
+
4
+ __all__ = ["fixed_field", "fixed_fields", "patterned_field", "patterned_fields"]
5
+
6
+
7
+ def fixed_field(default=None, metadata=None):
8
+ """Mark a field as a fixed OpenAPI specification field."""
9
+ return field(default=default, metadata={**(metadata or {}), "fixed_field": True})
10
+
11
+
12
+ def fixed_fields(dataclass_type):
13
+ """
14
+ Get all fixed specification fields from a dataclass.
15
+
16
+ Args:
17
+ dataclass_type: The dataclass type to inspect
18
+
19
+ Returns:
20
+ A dictionary mapping field names to field objects for all fields marked with fixed_field()
21
+ """
22
+ return {f.name: f for f in fields(dataclass_type) if f.metadata.get("fixed_field")}
23
+
24
+
25
+ def patterned_field(default=None, metadata=None):
26
+ """
27
+ Mark a field as containing OpenAPI patterned fields.
28
+
29
+ Patterned fields have dynamic names that follow a specific pattern (e.g., security scheme names,
30
+ path patterns, callback expressions, HTTP status codes).
31
+ """
32
+ return field(default=default, metadata={**(metadata or {}), "patterned_field": True})
33
+
34
+
35
+ def patterned_fields(dataclass_type):
36
+ """
37
+ Get all patterned fields from a dataclass.
38
+
39
+ Args:
40
+ dataclass_type: The dataclass type to inspect
41
+
42
+ Returns:
43
+ A dictionary mapping field names to field objects for all fields marked with patterned_field()
44
+ """
45
+ return {f.name: f for f in fields(dataclass_type) if f.metadata.get("patterned_field")}