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.
- jentic/apitools/openapi/datamodels/low/context.py +21 -0
- jentic/apitools/openapi/datamodels/low/extractors.py +126 -0
- jentic/apitools/openapi/datamodels/low/fields.py +45 -0
- jentic/apitools/openapi/datamodels/low/model_builder.py +113 -0
- jentic/apitools/openapi/datamodels/low/py.typed +0 -0
- jentic/apitools/openapi/datamodels/low/sources.py +89 -0
- jentic/apitools/openapi/datamodels/low/v30/__init__.py +0 -28
- jentic/apitools/openapi/datamodels/low/v30/discriminator.py +53 -78
- jentic/apitools/openapi/datamodels/low/v30/external_documentation.py +47 -61
- jentic/apitools/openapi/datamodels/low/v30/oauth_flow.py +54 -123
- jentic/apitools/openapi/datamodels/low/v30/oauth_flows.py +102 -151
- jentic/apitools/openapi/datamodels/low/v30/reference.py +43 -44
- jentic/apitools/openapi/datamodels/low/v30/schema.py +316 -607
- jentic/apitools/openapi/datamodels/low/v30/security_requirement.py +82 -72
- jentic/apitools/openapi/datamodels/low/v30/security_scheme.py +94 -286
- jentic/apitools/openapi/datamodels/low/v30/tag.py +88 -119
- jentic/apitools/openapi/datamodels/low/v30/xml.py +46 -120
- jentic_openapi_datamodels-1.0.0a13.dist-info/METADATA +211 -0
- jentic_openapi_datamodels-1.0.0a13.dist-info/RECORD +23 -0
- jentic/apitools/openapi/datamodels/low/v30/specification_object.py +0 -217
- jentic_openapi_datamodels-1.0.0a12.dist-info/METADATA +0 -52
- jentic_openapi_datamodels-1.0.0a12.dist-info/RECORD +0 -18
- /jentic/apitools/openapi/datamodels/low/{v30/py.typed → __init__.py} +0 -0
- {jentic_openapi_datamodels-1.0.0a12.dist-info → jentic_openapi_datamodels-1.0.0a13.dist-info}/WHEEL +0 -0
- {jentic_openapi_datamodels-1.0.0a12.dist-info → jentic_openapi_datamodels-1.0.0a13.dist-info}/licenses/LICENSE +0 -0
- {jentic_openapi_datamodels-1.0.0a12.dist-info → jentic_openapi_datamodels-1.0.0a13.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,132 +1,101 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
from
|
|
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.external_documentation import (
|
|
11
16
|
ExternalDocumentation,
|
|
12
17
|
)
|
|
13
|
-
from jentic.apitools.openapi.datamodels.low.v30.
|
|
18
|
+
from jentic.apitools.openapi.datamodels.low.v30.external_documentation import (
|
|
19
|
+
build as build_external_documentation,
|
|
20
|
+
)
|
|
14
21
|
|
|
15
22
|
|
|
16
|
-
__all__ = ["Tag"]
|
|
23
|
+
__all__ = ["Tag", "build"]
|
|
17
24
|
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
@dataclass(frozen=True, slots=True)
|
|
27
|
+
class Tag:
|
|
20
28
|
"""
|
|
21
|
-
|
|
29
|
+
Tag Object representation for OpenAPI 3.0.
|
|
22
30
|
|
|
23
|
-
Adds metadata to a single tag that is used by the Operation Object.
|
|
24
|
-
|
|
31
|
+
Adds metadata to a single tag that is used by the Operation Object. It is not mandatory
|
|
32
|
+
to have a Tag Object per tag defined in the Operation Object instances.
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
Attributes:
|
|
35
|
+
root_node: The top-level node representing the entire Tag object in the original source file
|
|
36
|
+
name: The name of the tag. REQUIRED.
|
|
37
|
+
description: A short description for the tag. CommonMark syntax MAY be used for rich text representation.
|
|
38
|
+
external_docs: Additional external documentation for this tag.
|
|
39
|
+
extensions: Specification extensions (x-* fields)
|
|
40
|
+
"""
|
|
27
41
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
>>> # Tag with external docs
|
|
40
|
-
>>> tag = Tag({
|
|
41
|
-
... "name": "store",
|
|
42
|
-
... "description": "Access to Petstore orders",
|
|
43
|
-
... "externalDocs": {
|
|
44
|
-
... "description": "Find out more",
|
|
45
|
-
... "url": "http://example.com"
|
|
46
|
-
... }
|
|
47
|
-
... })
|
|
48
|
-
>>> tag.external_docs.url
|
|
49
|
-
'http://example.com'
|
|
42
|
+
root_node: yaml.Node
|
|
43
|
+
name: FieldSource[str] | None = fixed_field()
|
|
44
|
+
description: FieldSource[str] | None = fixed_field()
|
|
45
|
+
external_docs: FieldSource[ExternalDocumentation] | None = fixed_field(
|
|
46
|
+
metadata={"yaml_name": "externalDocs"}
|
|
47
|
+
)
|
|
48
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def build(root: yaml.Node, context: Context | None = None) -> Tag | ValueSource[YAMLInvalidValue]:
|
|
50
52
|
"""
|
|
53
|
+
Build a Tag object from a YAML node.
|
|
54
|
+
|
|
55
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
56
|
+
model that provides complete source fidelity for inspection and validation.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
60
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
""
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"""Set the tag name."""
|
|
93
|
-
if value is None:
|
|
94
|
-
self.pop("name", None)
|
|
95
|
-
else:
|
|
96
|
-
self["name"] = value
|
|
97
|
-
|
|
98
|
-
@property
|
|
99
|
-
def description(self) -> str | None:
|
|
100
|
-
"""
|
|
101
|
-
A description for the tag.
|
|
102
|
-
|
|
103
|
-
Returns:
|
|
104
|
-
Description or None if not present
|
|
105
|
-
"""
|
|
106
|
-
return self.get("description")
|
|
107
|
-
|
|
108
|
-
@description.setter
|
|
109
|
-
def description(self, value: str | None) -> None:
|
|
110
|
-
"""Set the description."""
|
|
111
|
-
if value is None:
|
|
112
|
-
self.pop("description", None)
|
|
113
|
-
else:
|
|
114
|
-
self["description"] = value
|
|
115
|
-
|
|
116
|
-
@property
|
|
117
|
-
def external_docs(self) -> ExternalDocumentation | None:
|
|
118
|
-
"""
|
|
119
|
-
Additional external documentation for this tag.
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
ExternalDocumentation instance or None if not present
|
|
123
|
-
"""
|
|
124
|
-
return self.get("externalDocs")
|
|
125
|
-
|
|
126
|
-
@external_docs.setter
|
|
127
|
-
def external_docs(self, value: ExternalDocumentation | None) -> None:
|
|
128
|
-
"""Set the external documentation."""
|
|
129
|
-
if value is None:
|
|
130
|
-
self.pop("externalDocs", None)
|
|
131
|
-
else:
|
|
132
|
-
self["externalDocs"] = value
|
|
62
|
+
Returns:
|
|
63
|
+
A Tag object if the node is valid, or a ValueSource containing
|
|
64
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
65
|
+
and its source location for validation).
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
from ruamel.yaml import YAML
|
|
69
|
+
yaml = YAML()
|
|
70
|
+
root = yaml.compose("name: pet\\ndescription: Everything about your Pets")
|
|
71
|
+
tag = build(root)
|
|
72
|
+
assert tag.name.value == 'pet'
|
|
73
|
+
"""
|
|
74
|
+
# Initialize context once at the beginning
|
|
75
|
+
if context is None:
|
|
76
|
+
context = Context()
|
|
77
|
+
|
|
78
|
+
if not isinstance(root, yaml.MappingNode):
|
|
79
|
+
# Preserve invalid root data instead of returning None
|
|
80
|
+
value = context.yaml_constructor.construct_object(root, deep=True)
|
|
81
|
+
return ValueSource(value=value, value_node=root)
|
|
82
|
+
|
|
83
|
+
# Use build_model to handle most fields
|
|
84
|
+
tag = build_model(root, Tag, context=context)
|
|
85
|
+
|
|
86
|
+
# Manually handle special fields that build_model can't process (nested objects)
|
|
87
|
+
for key_node, value_node in root.value:
|
|
88
|
+
key = context.yaml_constructor.construct_yaml_str(key_node)
|
|
89
|
+
|
|
90
|
+
if key == "externalDocs":
|
|
91
|
+
# Handle nested ExternalDocumentation object - child builder handles invalid nodes
|
|
92
|
+
# FieldSource will auto-unwrap ValueSource if child returns it for invalid data
|
|
93
|
+
external_docs = FieldSource(
|
|
94
|
+
value=build_external_documentation(value_node, context=context),
|
|
95
|
+
key_node=key_node,
|
|
96
|
+
value_node=value_node,
|
|
97
|
+
)
|
|
98
|
+
tag = replace(tag, external_docs=external_docs)
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
return tag
|
|
@@ -1,134 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
OpenAPI 3.0.4 XML Object model.
|
|
1
|
+
from dataclasses import dataclass, field
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
"""
|
|
3
|
+
from ruamel import yaml
|
|
6
4
|
|
|
7
|
-
from jentic.apitools.openapi.datamodels.low.
|
|
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__ = ["XML"]
|
|
17
|
+
__all__ = ["XML", "build"]
|
|
11
18
|
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class XML:
|
|
14
22
|
"""
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'animal'
|
|
26
|
-
|
|
27
|
-
>>> # XML namespace
|
|
28
|
-
>>> xml = XML({
|
|
29
|
-
... "name": "Person",
|
|
30
|
-
... "namespace": "http://example.com/schema/person",
|
|
31
|
-
... "prefix": "sample"
|
|
32
|
-
... })
|
|
33
|
-
>>> xml.namespace
|
|
34
|
-
'http://example.com/schema/person'
|
|
23
|
+
XML Object representation for OpenAPI 3.0.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
root_node: The top-level node representing the entire XML object in the original source file
|
|
27
|
+
name: Name of the XML element
|
|
28
|
+
namespace: XML namespace
|
|
29
|
+
prefix: XML namespace prefix
|
|
30
|
+
attribute: Whether property is an attribute (deprecated in OpenAPI 3.2+)
|
|
31
|
+
wrapped: Whether array is wrapped
|
|
32
|
+
extensions: Specification extensions
|
|
35
33
|
"""
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"""
|
|
45
|
-
Replaces the name of the element/attribute.
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
Element/attribute name or None if not present
|
|
49
|
-
"""
|
|
50
|
-
return self.get("name")
|
|
51
|
-
|
|
52
|
-
@name.setter
|
|
53
|
-
def name(self, value: str | None) -> None:
|
|
54
|
-
"""Set the element/attribute name."""
|
|
55
|
-
if value is None:
|
|
56
|
-
self.pop("name", None)
|
|
57
|
-
else:
|
|
58
|
-
self["name"] = value
|
|
59
|
-
|
|
60
|
-
@property
|
|
61
|
-
def namespace(self) -> str | None:
|
|
62
|
-
"""
|
|
63
|
-
The URI of the namespace definition.
|
|
64
|
-
|
|
65
|
-
Returns:
|
|
66
|
-
Namespace URI or None if not present
|
|
67
|
-
"""
|
|
68
|
-
return self.get("namespace")
|
|
35
|
+
root_node: yaml.Node
|
|
36
|
+
name: FieldSource[str] | None = fixed_field()
|
|
37
|
+
namespace: FieldSource[str] | None = fixed_field()
|
|
38
|
+
prefix: FieldSource[str] | None = fixed_field()
|
|
39
|
+
attribute: FieldSource[bool] | None = fixed_field()
|
|
40
|
+
wrapped: FieldSource[bool] | None = fixed_field()
|
|
41
|
+
extensions: dict[KeySource[str], ValueSource[YAMLValue]] = field(default_factory=dict)
|
|
69
42
|
|
|
70
|
-
@namespace.setter
|
|
71
|
-
def namespace(self, value: str | None) -> None:
|
|
72
|
-
"""Set the namespace URI."""
|
|
73
|
-
if value is None:
|
|
74
|
-
self.pop("namespace", None)
|
|
75
|
-
else:
|
|
76
|
-
self["namespace"] = value
|
|
77
43
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
The prefix to be used for the name.
|
|
82
|
-
|
|
83
|
-
Returns:
|
|
84
|
-
Prefix or None if not present
|
|
85
|
-
"""
|
|
86
|
-
return self.get("prefix")
|
|
87
|
-
|
|
88
|
-
@prefix.setter
|
|
89
|
-
def prefix(self, value: str | None) -> None:
|
|
90
|
-
"""Set the prefix."""
|
|
91
|
-
if value is None:
|
|
92
|
-
self.pop("prefix", None)
|
|
93
|
-
else:
|
|
94
|
-
self["prefix"] = value
|
|
95
|
-
|
|
96
|
-
@property
|
|
97
|
-
def attribute(self) -> bool | None:
|
|
98
|
-
"""
|
|
99
|
-
Declares whether the property is an XML attribute.
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
True if the property is an XML attribute,
|
|
103
|
-
False if present and set to false,
|
|
104
|
-
or None if not present.
|
|
105
|
-
"""
|
|
106
|
-
return self.get("attribute")
|
|
107
|
-
|
|
108
|
-
@attribute.setter
|
|
109
|
-
def attribute(self, value: bool | None) -> None:
|
|
110
|
-
"""Set the attribute flag."""
|
|
111
|
-
if value is None:
|
|
112
|
-
self.pop("attribute", None)
|
|
113
|
-
else:
|
|
114
|
-
self["attribute"] = value
|
|
115
|
-
|
|
116
|
-
@property
|
|
117
|
-
def wrapped(self) -> bool | None:
|
|
118
|
-
"""
|
|
119
|
-
For arrays, wraps the array in a containing element.
|
|
44
|
+
def build(root: yaml.Node, context: Context | None = None) -> XML | ValueSource[YAMLInvalidValue]:
|
|
45
|
+
"""
|
|
46
|
+
Build an XML object from a YAML node.
|
|
120
47
|
|
|
121
|
-
|
|
48
|
+
Preserves all source data as-is, regardless of type. This is a low-level/plumbing
|
|
49
|
+
model that provides complete source fidelity for inspection and validation.
|
|
122
50
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return self.get("wrapped")
|
|
51
|
+
Args:
|
|
52
|
+
root: The YAML node to parse (should be a MappingNode)
|
|
53
|
+
context: Optional parsing context. If None, a default context will be created.
|
|
127
54
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
self["wrapped"] = value
|
|
55
|
+
Returns:
|
|
56
|
+
An XML object if the node is valid, or a ValueSource containing
|
|
57
|
+
the invalid data if the root is not a MappingNode (preserving the invalid data
|
|
58
|
+
and its source location for validation).
|
|
59
|
+
"""
|
|
60
|
+
return build_model(root, XML, context=context)
|
|
@@ -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,23 @@
|
|
|
1
|
+
jentic/apitools/openapi/datamodels/low/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
jentic/apitools/openapi/datamodels/low/context.py,sha256=pAuPf8GmdttXMEeuO4clAvTTxH7LMtEHdxoo1RpyK2c,555
|
|
3
|
+
jentic/apitools/openapi/datamodels/low/extractors.py,sha256=ACtSbRRzICi9cjLzldcwN4GDuieT_JvW81lPZ44mq6c,4954
|
|
4
|
+
jentic/apitools/openapi/datamodels/low/fields.py,sha256=g1Sta-eN4JDg_AnuJxe1MeWS3Ut81A5dw_ZIdyGrgbk,1448
|
|
5
|
+
jentic/apitools/openapi/datamodels/low/model_builder.py,sha256=NwfGtJBjYK9SJkYJBkfvgV2oxmVpZB5KIV5P-bt1B4s,4683
|
|
6
|
+
jentic/apitools/openapi/datamodels/low/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
jentic/apitools/openapi/datamodels/low/sources.py,sha256=H4I6LSn-Ry6cJageIyhCvE0H85O7Lh94gdCIm60wj0E,2924
|
|
8
|
+
jentic/apitools/openapi/datamodels/low/v30/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
jentic/apitools/openapi/datamodels/low/v30/discriminator.py,sha256=GVIyNbaoMVhkfavfFhKvlFHvwcIiSDPHKVAmgV9_7pQ,2537
|
|
10
|
+
jentic/apitools/openapi/datamodels/low/v30/external_documentation.py,sha256=5H2SeBkTH4MPakQPyy2NrSuPoh1n64OzeFVbfpyrh28,2448
|
|
11
|
+
jentic/apitools/openapi/datamodels/low/v30/oauth_flow.py,sha256=u6VnU4pMfu-Atg2Ooslp0ladGGhfw3VEjLHgMqZHRn8,2906
|
|
12
|
+
jentic/apitools/openapi/datamodels/low/v30/oauth_flows.py,sha256=gXgCmvYQ0d5v36VeGw2MqkIG20dbClfWIN7IDMq8V_c,4652
|
|
13
|
+
jentic/apitools/openapi/datamodels/low/v30/reference.py,sha256=zwz09f_sgzj5a1KV0V5IwhB313Se5JYX1pr5FaJaxZU,2134
|
|
14
|
+
jentic/apitools/openapi/datamodels/low/v30/schema.py,sha256=57LkSe1OuyQugtX5aM3UAy0qLgaOVyQzdbdYyQtDqBs,15188
|
|
15
|
+
jentic/apitools/openapi/datamodels/low/v30/security_requirement.py,sha256=beYB2TrfflvxnVO83b2iNzVKrInupJ_59zsvM8NYj-k,4032
|
|
16
|
+
jentic/apitools/openapi/datamodels/low/v30/security_scheme.py,sha256=tPOfcx0sh0w-gMMwtOxjybduRb7q-oI8PTq1SDS8ozg,4819
|
|
17
|
+
jentic/apitools/openapi/datamodels/low/v30/tag.py,sha256=FenkPaMCMAXZWJkA2heWewhxB2etKA-opuas494fUhA,3831
|
|
18
|
+
jentic/apitools/openapi/datamodels/low/v30/xml.py,sha256=1LjV-JsU5MqEFPV9e-zVKyJ9qt7QfwWjGN3oVLGxsnQ,2109
|
|
19
|
+
jentic_openapi_datamodels-1.0.0a13.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
20
|
+
jentic_openapi_datamodels-1.0.0a13.dist-info/licenses/NOTICE,sha256=pAOGW-rGw9KNc2cuuLWZkfx0GSTV4TicbgBKZSLPMIs,168
|
|
21
|
+
jentic_openapi_datamodels-1.0.0a13.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
22
|
+
jentic_openapi_datamodels-1.0.0a13.dist-info/METADATA,sha256=mV-JYCiurqc1c7SBNJ2OA4A85vhPI-NQ_iNFa3Uia1s,7439
|
|
23
|
+
jentic_openapi_datamodels-1.0.0a13.dist-info/RECORD,,
|