jentic-openapi-datamodels 1.0.0a17__tar.gz → 1.0.0a19__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 (79) hide show
  1. jentic_openapi_datamodels-1.0.0a19/PKG-INFO +372 -0
  2. jentic_openapi_datamodels-1.0.0a19/README.md +358 -0
  3. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/pyproject.toml +1 -1
  4. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/extractors.py +3 -3
  5. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/__init__.py +76 -0
  6. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/builders/__init__.py +312 -0
  7. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/callback.py +131 -0
  8. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/components.py +236 -0
  9. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/contact.py +4 -10
  10. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/discriminator.py +4 -9
  11. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/encoding.py +81 -0
  12. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/example.py +91 -0
  13. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/external_documentation.py +4 -10
  14. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/header.py +120 -0
  15. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/info.py +14 -23
  16. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/license.py +4 -10
  17. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/link.py +141 -0
  18. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/media_type.py +110 -0
  19. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/oauth_flow.py +4 -10
  20. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/oauth_flows.py +7 -15
  21. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/openapi.py +149 -0
  22. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/operation.py +134 -0
  23. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/parameter.py +123 -0
  24. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/path_item.py +125 -0
  25. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/paths.py +108 -0
  26. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/reference.py +5 -9
  27. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/request_body.py +108 -0
  28. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/response.py +104 -0
  29. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/responses.py +109 -0
  30. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/schema.py +81 -97
  31. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/security_requirement.py +14 -9
  32. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/security_scheme.py +42 -22
  33. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v30/server.py +111 -0
  34. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/server_variable.py +4 -10
  35. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/tag.py +8 -46
  36. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/v30/xml.py +4 -10
  37. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/__init__.py +77 -0
  38. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/builders/__init__.py +347 -0
  39. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/callback.py +131 -0
  40. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/components.py +240 -0
  41. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/contact.py +61 -0
  42. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/discriminator.py +62 -0
  43. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/encoding.py +81 -0
  44. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/example.py +91 -0
  45. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/external_documentation.py +59 -0
  46. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/header.py +120 -0
  47. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/info.py +116 -0
  48. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/license.py +61 -0
  49. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/link.py +141 -0
  50. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/media_type.py +110 -0
  51. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/oauth_flow.py +65 -0
  52. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/oauth_flows.py +108 -0
  53. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/openapi.py +168 -0
  54. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/operation.py +133 -0
  55. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/parameter.py +123 -0
  56. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/path_item.py +125 -0
  57. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/paths.py +108 -0
  58. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/reference.py +65 -0
  59. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/request_body.py +108 -0
  60. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/response.py +104 -0
  61. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/responses.py +109 -0
  62. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/schema.py +498 -0
  63. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/security_requirement.py +106 -0
  64. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/security_scheme.py +129 -0
  65. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/server.py +111 -0
  66. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/server_variable.py +70 -0
  67. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/tag.py +63 -0
  68. jentic_openapi_datamodels-1.0.0a19/src/jentic/apitools/openapi/datamodels/low/v31/xml.py +54 -0
  69. jentic_openapi_datamodels-1.0.0a17/PKG-INFO +0 -211
  70. jentic_openapi_datamodels-1.0.0a17/README.md +0 -197
  71. jentic_openapi_datamodels-1.0.0a17/src/jentic/apitools/openapi/datamodels/low/model_builder.py +0 -129
  72. jentic_openapi_datamodels-1.0.0a17/src/jentic/apitools/openapi/datamodels/low/v30/__init__.py +0 -0
  73. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/LICENSE +0 -0
  74. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/NOTICE +0 -0
  75. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/__init__.py +0 -0
  76. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/context.py +0 -0
  77. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/fields.py +0 -0
  78. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/py.typed +0 -0
  79. {jentic_openapi_datamodels-1.0.0a17 → jentic_openapi_datamodels-1.0.0a19}/src/jentic/apitools/openapi/datamodels/low/sources.py +0 -0
@@ -0,0 +1,372 @@
1
+ Metadata-Version: 2.4
2
+ Name: jentic-openapi-datamodels
3
+ Version: 1.0.0a19
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
+ # jentic-openapi-datamodels
16
+
17
+ Low-level data models for OpenAPI specifications.
18
+
19
+ This package provides data model classes for representing OpenAPI specification objects in Python.
20
+
21
+ ## Features
22
+
23
+ **Low-Level Architecture**
24
+ - **Preserve Everything**: All data from source documents preserved exactly as-is, including invalid values
25
+ - **Zero Validation**: No validation or coercion during parsing - deferred to higher layers
26
+ - **Separation of Concerns**: Low-level model focuses on faithful representation; validation belongs elsewhere
27
+
28
+ **Source Tracking**
29
+ - **Complete Source Fidelity**: Every field tracks its exact YAML node location
30
+ - **Precise Error Reporting**: Line and column numbers via `start_mark` and `end_mark`
31
+ - **Metadata Preservation**: Full position tracking for accurate diagnostics
32
+
33
+ **Python Integration**
34
+ - **Python-Idiomatic Naming**: snake_case field names (e.g., `bearer_format`, `property_name`)
35
+ - **Spec-Aligned Mapping**: Automatic YAML name mapping (e.g., `bearerFormat` ↔ `bearer_format`)
36
+ - **Type Safety**: Full type hints with Generic types (`FieldSource[T]`, `KeySource[T]`, `ValueSource[T]`)
37
+
38
+ **Extensibility**
39
+ - **Extension Support**: Automatic extraction of OpenAPI `x-*` specification extensions
40
+ - **Unknown Field Tracking**: Capture typos and invalid fields for validation tools
41
+ - **Generic Builder Pattern**: Core `build_model()` function with object-specific builders for complex cases
42
+
43
+ **Performance**
44
+ - **Memory Efficient**: Immutable frozen dataclasses with `__slots__` for optimal memory usage
45
+ - **Shared Context**: All instances share a single YAML constructor for efficiency
46
+
47
+ **Version Support**
48
+ - **OpenAPI 2.0**: Planned for future release
49
+ - **OpenAPI 3.0.x**: Fully implemented
50
+ - **OpenAPI 3.1.x**: Fully implemented with JSON Schema 2020-12 support
51
+ - **OpenAPI 3.2.x**: Planned for future release
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install jentic-openapi-datamodels
57
+ ```
58
+
59
+ **Prerequisites:**
60
+ - Python 3.11+
61
+
62
+ ## Quick Start
63
+
64
+ ### Parsing OpenAPI 3.0 Documents
65
+
66
+ The main use case is parsing complete OpenAPI Documents:
67
+
68
+ ```python
69
+ from ruamel.yaml import YAML
70
+ from jentic.apitools.openapi.datamodels.low.v30 import build
71
+
72
+ # Parse OpenAPI document
73
+ yaml = YAML()
74
+ root = yaml.compose("""
75
+ openapi: 3.0.4
76
+ info:
77
+ title: Pet Store API
78
+ version: 1.0.0
79
+ paths:
80
+ /pets:
81
+ get:
82
+ summary: List all pets
83
+ responses:
84
+ '200':
85
+ description: A list of pets
86
+ """)
87
+
88
+ # Build OpenAPI document model
89
+ openapi_doc = build(root)
90
+
91
+ # Access document fields via Python naming (snake_case)
92
+ print(openapi_doc.openapi.value) # "3.0.4"
93
+ print(openapi_doc.info.value.title.value) # "Pet Store API"
94
+ print(openapi_doc.info.value.version.value) # "1.0.0"
95
+
96
+ # Access nested fields with full type safety
97
+ for path_key, path_item in openapi_doc.paths.value.path_items.items():
98
+ print(f"Path: {path_key.value}") # "/pets"
99
+ if path_item.value.get:
100
+ operation = path_item.value.get.value
101
+ print(f" Summary: {operation.summary.value}") # "List all pets"
102
+ ```
103
+
104
+ ### Parsing OpenAPI 3.1 Documents with JSON Schema 2020-12
105
+
106
+ OpenAPI 3.1 fully supports JSON Schema 2020-12, including advanced features like boolean schemas, conditional validation and vocabulary declarations:
107
+
108
+ ```python
109
+ from ruamel.yaml import YAML
110
+ from jentic.apitools.openapi.datamodels.low.v31 import build
111
+
112
+ # Parse OpenAPI 3.1 document with JSON Schema 2020-12 features
113
+ yaml = YAML()
114
+ root = yaml.compose("""
115
+ openapi: 3.1.2
116
+ info:
117
+ title: Pet Store API
118
+ version: 1.0.0
119
+ paths:
120
+ /pets:
121
+ get:
122
+ responses:
123
+ '200':
124
+ description: Pet list
125
+ content:
126
+ application/json:
127
+ schema:
128
+ type: array
129
+ prefixItems:
130
+ - type: string
131
+ - type: integer
132
+ items: false
133
+ contains:
134
+ type: object
135
+ required: [id]
136
+ """)
137
+
138
+ openapi_doc = build(root)
139
+
140
+ # Access JSON Schema 2020-12 features
141
+ schema = openapi_doc.paths.value.path_items["/pets"].value.get.value.responses.value["200"].value.content.value["application/json"].value.schema
142
+ print(schema.prefix_items.value[0].type.value) # "string"
143
+ print(schema.items.value) # False (boolean schema)
144
+ print(schema.contains.value.required.value[0].value) # "id"
145
+ ```
146
+
147
+ ### Parsing Individual Spec Objects
148
+
149
+ You can also parse individual OpenAPI specification objects:
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
+ # Parse a Security Scheme object
156
+ yaml = YAML()
157
+ root = yaml.compose("""
158
+ type: http
159
+ scheme: bearer
160
+ bearerFormat: JWT
161
+ """)
162
+
163
+ security_scheme = build_security_scheme(root)
164
+
165
+ # Access via Python field names (snake_case)
166
+ print(security_scheme.bearer_format.value) # "JWT"
167
+
168
+ # Access source location information
169
+ print(security_scheme.bearer_format.key_node.value) # "bearerFormat"
170
+ print(security_scheme.bearer_format.key_node.start_mark.line) # Line number
171
+ ```
172
+
173
+ You can also parse OpenAPI 3.1 Schema objects with JSON Schema 2020-12 features:
174
+
175
+ ```python
176
+ from ruamel.yaml import YAML
177
+ from jentic.apitools.openapi.datamodels.low.v31.schema import build as build_schema
178
+
179
+ # Parse a Schema object with JSON Schema 2020-12 features
180
+ yaml = YAML()
181
+ root = yaml.compose("""
182
+ type: object
183
+ properties:
184
+ id:
185
+ type: integer
186
+ tags:
187
+ type: array
188
+ prefixItems:
189
+ - type: string
190
+ - type: string
191
+ items: false
192
+ patternProperties:
193
+ "^x-":
194
+ type: string
195
+ unevaluatedProperties: false
196
+ if:
197
+ properties:
198
+ premium:
199
+ const: true
200
+ then:
201
+ required: [support_tier]
202
+ """)
203
+
204
+ schema = build_schema(root)
205
+
206
+ # Access JSON Schema 2020-12 fields via Python naming (snake_case)
207
+ print(schema.properties.value["id"].type.value) # "integer"
208
+ print(schema.pattern_properties.value["^x-"].type.value) # "string"
209
+ print(schema.unevaluated_properties.value) # False
210
+ print(schema.prefix_items.value[0].type.value) # "string"
211
+
212
+ # Access conditional schema fields
213
+ print(schema.if_.value.properties.value["premium"].const.value) # True
214
+ print(schema.then_.value.required.value[0].value) # "support_tier"
215
+
216
+ # Access source location information
217
+ print(schema.type.key_node.start_mark.line) # Line number for "type" key
218
+ ```
219
+
220
+ ### Field Name Mapping
221
+
222
+ YAML `camelCase` fields automatically map to Python `snake_case`:
223
+ - `bearerFormat` → `bearer_format`
224
+ - `authorizationUrl` → `authorization_url`
225
+ - `openIdConnectUrl` → `openid_connect_url`
226
+
227
+ Special cases for Python reserved keywords and `$` fields:
228
+
229
+ - `in` → `in_`
230
+ - `if` → `if_`
231
+ - `then` → `then_`
232
+ - `else` → `else_`
233
+ - `not` → `not_`
234
+ - `$ref` → `ref_`
235
+ - `$id` → `id_`
236
+ - `$schema` → `schema_`
237
+
238
+ ### Source Tracking
239
+
240
+ The package provides three immutable wrapper types for preserving source information:
241
+
242
+ **FieldSource[T]** - For OpenAPI fields with key-value pairs
243
+ - Used for: Fixed fields (`name`, `bearer_format`) and patterned fields (status codes, path items, schema properties)
244
+ - Tracks: Both key and value nodes
245
+ - Example: `SecurityScheme.bearer_format` is `FieldSource[str]`, response status codes are `FieldSource[Response]`
246
+
247
+ **KeySource[T]** - For dictionary keys
248
+ - Used for: keys in OpenAPI fields, `x-*` extensions and mapping dictionaries
249
+ - Tracks: Only key node
250
+ - Example: Keys in `Discriminator.mapping` are `KeySource[str]`
251
+
252
+ **ValueSource[T]** - For dictionary values and array items
253
+ - Used for: values in OpenAPI fields, in `x-*` extensions, mapping dictionaries and array items
254
+ - Tracks: Only value node
255
+ - Example: Values in `Discriminator.mapping` are `ValueSource[str]`
256
+
257
+ ```python
258
+ from ruamel.yaml import YAML
259
+ from jentic.apitools.openapi.datamodels.low.v30 import build
260
+
261
+ # FieldSource: Fixed specification fields in OpenAPI document
262
+ yaml = YAML()
263
+ root = yaml.compose("""
264
+ openapi: 3.0.4
265
+ info:
266
+ title: Pet Store API
267
+ version: 1.0.0
268
+ paths: {}
269
+ """)
270
+ openapi_doc = build(root)
271
+
272
+ field = openapi_doc.info.value.title # FieldSource[str]
273
+ print(field.value) # "Pet Store API" - The actual value
274
+ print(field.key_node) # YAML node for "title"
275
+ print(field.value_node) # YAML node for "Pet Store API"
276
+
277
+ # KeySource/ValueSource: Dictionary fields (extensions, mapping)
278
+ # Extensions in OpenAPI objects use KeySource/ValueSource
279
+ root = yaml.compose("""
280
+ openapi: 3.0.4
281
+ info:
282
+ title: API
283
+ version: 1.0.0
284
+ x-custom: value
285
+ x-another: data
286
+ paths: {}
287
+ """)
288
+ openapi_doc = build(root)
289
+
290
+ for key, value in openapi_doc.info.value.extensions.items():
291
+ print(key.value) # KeySource[str]: "x-custom" or "x-another"
292
+ print(key.key_node) # YAML node for the extension key
293
+ print(value.value) # ValueSource: "value" or "data"
294
+ print(value.value_node) # YAML node for the extension value
295
+ ```
296
+
297
+ ### Location Ranges
298
+
299
+ Access precise location ranges within the source document using start_mark and end_mark:
300
+
301
+ ```python
302
+ from ruamel.yaml import YAML
303
+ from jentic.apitools.openapi.datamodels.low.v30 import build
304
+
305
+ yaml_content = """
306
+ openapi: 3.0.4
307
+ info:
308
+ title: Pet Store API
309
+ version: 1.0.0
310
+ description: A sample Pet Store API
311
+ paths: {}
312
+ """
313
+
314
+ yaml = YAML()
315
+ root = yaml.compose(yaml_content)
316
+ openapi_doc = build(root)
317
+
318
+ # Access location information for any field
319
+ field = openapi_doc.info.value.title
320
+
321
+ # Key location (e.g., "title")
322
+ print(f"Key start: line {field.key_node.start_mark.line}, col {field.key_node.start_mark.column}")
323
+ print(f"Key end: line {field.key_node.end_mark.line}, col {field.key_node.end_mark.column}")
324
+
325
+ # Value location (e.g., "Pet Store API")
326
+ print(f"Value start: line {field.value_node.start_mark.line}, col {field.value_node.start_mark.column}")
327
+ print(f"Value end: line {field.value_node.end_mark.line}, col {field.value_node.end_mark.column}")
328
+
329
+ # Full field range (from key start to value end)
330
+ start = field.key_node.start_mark
331
+ end = field.value_node.end_mark
332
+ print(f"Field range: ({start.line}:{start.column}) to ({end.line}:{end.column})")
333
+ ```
334
+
335
+ ### Invalid Data Handling
336
+
337
+ Low-level models preserve invalid data without validation:
338
+
339
+ ```python
340
+ from ruamel.yaml import YAML
341
+ from jentic.apitools.openapi.datamodels.low.v30 import build
342
+
343
+ yaml = YAML()
344
+ root = yaml.compose("""
345
+ openapi: 3.0.4
346
+ info:
347
+ title: 123 # Intentionally wrong type for demonstration (should be string)
348
+ version: 1.0.0
349
+ paths: {}
350
+ """)
351
+
352
+ openapi_doc = build(root)
353
+ print(openapi_doc.info.value.title.value) # 123 (preserved as-is)
354
+ print(type(openapi_doc.info.value.title.value)) # <class 'int'>
355
+
356
+ # Invalid data is preserved with full source tracking for validation tools
357
+ print(openapi_doc.info.value.title.value_node.start_mark.line) # Line number
358
+ ```
359
+
360
+ ### Error Reporting
361
+
362
+ This architecture—where the low-level model preserves data without validation and validation tools consume
363
+ that data—allows the low-level model to remain simple while enabling sophisticated validation tools to provide
364
+ user-friendly error messages with exact source locations.
365
+
366
+ ## Testing
367
+
368
+ Run the test suite:
369
+
370
+ ```bash
371
+ uv run --package jentic-openapi-datamodels pytest packages/jentic-openapi-datamodels -v
372
+ ```