openapi-python-client 0.28.4__py3-none-any.whl → 0.29.1__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.
- openapi_python_client/__init__.py +26 -17
- openapi_python_client/cli.py +1 -2
- openapi_python_client/config.py +5 -3
- openapi_python_client/parser/bodies.py +14 -23
- openapi_python_client/parser/openapi.py +58 -48
- openapi_python_client/parser/properties/__init__.py +34 -33
- openapi_python_client/parser/properties/any.py +11 -10
- openapi_python_client/parser/properties/boolean.py +13 -13
- openapi_python_client/parser/properties/const.py +11 -10
- openapi_python_client/parser/properties/date.py +15 -14
- openapi_python_client/parser/properties/datetime.py +15 -14
- openapi_python_client/parser/properties/enum_property.py +23 -20
- openapi_python_client/parser/properties/file.py +11 -10
- openapi_python_client/parser/properties/float.py +13 -13
- openapi_python_client/parser/properties/int.py +11 -11
- openapi_python_client/parser/properties/list_property.py +22 -22
- openapi_python_client/parser/properties/literal_enum_property.py +20 -20
- openapi_python_client/parser/properties/merge_properties.py +9 -6
- openapi_python_client/parser/properties/model_property.py +34 -33
- openapi_python_client/parser/properties/none.py +11 -11
- openapi_python_client/parser/properties/protocol.py +42 -32
- openapi_python_client/parser/properties/schemas.py +30 -23
- openapi_python_client/parser/properties/string.py +10 -11
- openapi_python_client/parser/properties/union.py +24 -22
- openapi_python_client/parser/properties/uuid.py +11 -11
- openapi_python_client/parser/responses.py +30 -23
- openapi_python_client/schema/__init__.py +2 -1
- openapi_python_client/schema/data_type.py +2 -2
- openapi_python_client/schema/openapi_schema_pydantic/callback.py +3 -1
- openapi_python_client/schema/openapi_schema_pydantic/components.py +10 -9
- openapi_python_client/schema/openapi_schema_pydantic/contact.py +5 -3
- openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +4 -2
- openapi_python_client/schema/openapi_schema_pydantic/encoding.py +4 -3
- openapi_python_client/schema/openapi_schema_pydantic/example.py +5 -3
- openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +4 -2
- openapi_python_client/schema/openapi_schema_pydantic/header.py +20 -6
- openapi_python_client/schema/openapi_schema_pydantic/info.py +5 -4
- openapi_python_client/schema/openapi_schema_pydantic/license.py +4 -2
- openapi_python_client/schema/openapi_schema_pydantic/link.py +5 -4
- openapi_python_client/schema/openapi_schema_pydantic/media_type.py +3 -2
- openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +6 -4
- openapi_python_client/schema/openapi_schema_pydantic/open_api.py +5 -3
- openapi_python_client/schema/openapi_schema_pydantic/operation.py +6 -5
- openapi_python_client/schema/openapi_schema_pydantic/parameter.py +6 -5
- openapi_python_client/schema/openapi_schema_pydantic/path_item.py +5 -3
- openapi_python_client/schema/openapi_schema_pydantic/paths.py +2 -1
- openapi_python_client/schema/openapi_schema_pydantic/reference.py +3 -1
- openapi_python_client/schema/openapi_schema_pydantic/request_body.py +3 -2
- openapi_python_client/schema/openapi_schema_pydantic/response.py +5 -4
- openapi_python_client/schema/openapi_schema_pydantic/responses.py +2 -1
- openapi_python_client/schema/openapi_schema_pydantic/schema.py +7 -6
- openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py +3 -1
- openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +8 -7
- openapi_python_client/schema/openapi_schema_pydantic/server.py +4 -3
- openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +5 -3
- openapi_python_client/schema/openapi_schema_pydantic/tag.py +3 -2
- openapi_python_client/schema/openapi_schema_pydantic/xml.py +5 -3
- openapi_python_client/schema/parameter_location.py +7 -22
- openapi_python_client/schema/ref.py +52 -0
- openapi_python_client/schema/untrusted_string.py +56 -0
- openapi_python_client/strings.py +245 -0
- openapi_python_client/templates/README.md.jinja +3 -3
- openapi_python_client/templates/client.py.jinja +5 -5
- openapi_python_client/templates/endpoint_macros.py.jinja +18 -18
- openapi_python_client/templates/endpoint_module.py.jinja +20 -14
- openapi_python_client/templates/helpers.jinja +3 -7
- openapi_python_client/templates/literal_enum.py.jinja +3 -3
- openapi_python_client/templates/model.py.jinja +18 -18
- openapi_python_client/templates/package_init.py.jinja +2 -2
- openapi_python_client/templates/property_templates/const_property.py.jinja +3 -3
- openapi_python_client/templates/property_templates/date_property.py.jinja +2 -2
- openapi_python_client/templates/property_templates/datetime_property.py.jinja +2 -2
- openapi_python_client/templates/property_templates/enum_property.py.jinja +1 -1
- openapi_python_client/templates/property_templates/file_property.py.jinja +1 -1
- openapi_python_client/templates/property_templates/list_property.py.jinja +3 -3
- openapi_python_client/templates/property_templates/literal_enum_property.py.jinja +2 -2
- openapi_python_client/templates/property_templates/model_property.py.jinja +1 -1
- openapi_python_client/templates/property_templates/property_macros.py.jinja +1 -1
- openapi_python_client/templates/property_templates/union_property.py.jinja +9 -9
- openapi_python_client/templates/property_templates/uuid_property.py.jinja +1 -1
- openapi_python_client/templates/pyproject_pdm.toml.jinja +4 -5
- openapi_python_client/templates/pyproject_poetry.toml.jinja +4 -5
- openapi_python_client/templates/pyproject_uv.toml.jinja +5 -6
- openapi_python_client/templates/setup.py.jinja +4 -4
- openapi_python_client/templates/str_enum.py.jinja +2 -2
- {openapi_python_client-0.28.4.dist-info → openapi_python_client-0.29.1.dist-info}/METADATA +6 -9
- openapi_python_client-0.29.1.dist-info/RECORD +115 -0
- {openapi_python_client-0.28.4.dist-info → openapi_python_client-0.29.1.dist-info}/WHEEL +1 -1
- openapi_python_client/utils.py +0 -122
- openapi_python_client-0.28.4.dist-info/RECORD +0 -113
- {openapi_python_client-0.28.4.dist-info → openapi_python_client-0.29.1.dist-info}/entry_points.txt +0 -0
- {openapi_python_client-0.28.4.dist-info → openapi_python_client-0.29.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,14 +3,14 @@ from __future__ import annotations
|
|
|
3
3
|
from itertools import chain
|
|
4
4
|
from typing import Any, ClassVar, NamedTuple
|
|
5
5
|
|
|
6
|
-
from attrs import define, evolve
|
|
6
|
+
from attrs import define, evolve, field
|
|
7
7
|
|
|
8
|
-
from ... import Config,
|
|
8
|
+
from ... import Config, strings
|
|
9
9
|
from ... import schema as oai
|
|
10
|
-
from ...
|
|
10
|
+
from ...strings import PythonIdentifier
|
|
11
11
|
from ..errors import ParseError, PropertyError
|
|
12
12
|
from .any import AnyProperty
|
|
13
|
-
from .protocol import PropertyProtocol, Value
|
|
13
|
+
from .protocol import PropertyProtocol, Value, convert_example
|
|
14
14
|
from .schemas import Class, ReferencePath, Schemas, parse_reference_path
|
|
15
15
|
|
|
16
16
|
|
|
@@ -18,15 +18,15 @@ from .schemas import Class, ReferencePath, Schemas, parse_reference_path
|
|
|
18
18
|
class ModelProperty(PropertyProtocol):
|
|
19
19
|
"""A property which refers to another Schema"""
|
|
20
20
|
|
|
21
|
-
name:
|
|
21
|
+
name: oai.UntrustedString
|
|
22
22
|
required: bool
|
|
23
23
|
default: Value | None
|
|
24
|
-
python_name:
|
|
25
|
-
example:
|
|
24
|
+
python_name: strings.PythonIdentifier
|
|
25
|
+
example: oai.UntrustedString | None = field(converter=convert_example)
|
|
26
26
|
class_info: Class
|
|
27
27
|
data: oai.Schema
|
|
28
|
-
description:
|
|
29
|
-
roots: set[ReferencePath |
|
|
28
|
+
description: oai.UntrustedString | None
|
|
29
|
+
roots: set[ReferencePath | strings.ClassName]
|
|
30
30
|
required_properties: list[Property] | None
|
|
31
31
|
optional_properties: list[Property] | None
|
|
32
32
|
relative_imports: set[str] | None
|
|
@@ -43,13 +43,13 @@ class ModelProperty(PropertyProtocol):
|
|
|
43
43
|
cls,
|
|
44
44
|
*,
|
|
45
45
|
data: oai.Schema,
|
|
46
|
-
name:
|
|
46
|
+
name: oai.UntrustedString,
|
|
47
47
|
schemas: Schemas,
|
|
48
48
|
required: bool,
|
|
49
49
|
parent_name: str | None,
|
|
50
50
|
config: Config,
|
|
51
51
|
process_properties: bool,
|
|
52
|
-
roots: set[ReferencePath |
|
|
52
|
+
roots: set[ReferencePath | strings.ClassName],
|
|
53
53
|
) -> tuple[ModelProperty | PropertyError, Schemas]:
|
|
54
54
|
"""
|
|
55
55
|
A single ModelProperty from its OAI data
|
|
@@ -65,12 +65,13 @@ class ModelProperty(PropertyProtocol):
|
|
|
65
65
|
roots: Set of strings that identify schema objects on which the new ModelProperty will depend
|
|
66
66
|
process_properties: Determines whether the new ModelProperty will be initialized with property data
|
|
67
67
|
"""
|
|
68
|
+
class_string: str | oai.UntrustedString
|
|
68
69
|
if not config.use_path_prefixes_for_title_model_names and data.title:
|
|
69
70
|
class_string = data.title
|
|
70
71
|
else:
|
|
71
72
|
title = data.title or name
|
|
72
73
|
if parent_name:
|
|
73
|
-
class_string = f"{
|
|
74
|
+
class_string = f"{strings.pascal_case(parent_name)}{strings.pascal_case(title)}"
|
|
74
75
|
else:
|
|
75
76
|
class_string = title
|
|
76
77
|
class_info = Class.from_string(string=class_string, config=config)
|
|
@@ -92,7 +93,7 @@ class ModelProperty(PropertyProtocol):
|
|
|
92
93
|
relative_imports = property_data.relative_imports
|
|
93
94
|
lazy_imports = property_data.lazy_imports
|
|
94
95
|
for root in roots:
|
|
95
|
-
if isinstance(root,
|
|
96
|
+
if isinstance(root, strings.ClassName):
|
|
96
97
|
continue
|
|
97
98
|
schemas.add_dependencies(root, {class_info.name})
|
|
98
99
|
|
|
@@ -105,11 +106,11 @@ class ModelProperty(PropertyProtocol):
|
|
|
105
106
|
relative_imports=relative_imports,
|
|
106
107
|
lazy_imports=lazy_imports,
|
|
107
108
|
additional_properties=additional_properties,
|
|
108
|
-
description=data.description
|
|
109
|
+
description=data.description,
|
|
109
110
|
default=None,
|
|
110
111
|
required=required,
|
|
111
112
|
name=name,
|
|
112
|
-
python_name=
|
|
113
|
+
python_name=strings.PythonIdentifier(value=name, prefix=config.field_prefix),
|
|
113
114
|
example=data.example,
|
|
114
115
|
)
|
|
115
116
|
if class_info.name in schemas.classes_by_name:
|
|
@@ -126,7 +127,7 @@ class ModelProperty(PropertyProtocol):
|
|
|
126
127
|
return prop, schemas
|
|
127
128
|
|
|
128
129
|
@classmethod
|
|
129
|
-
def convert_value(cls, value: Any) -> Value |
|
|
130
|
+
def convert_value(cls, value: Any) -> Value | PropertyError | None:
|
|
130
131
|
if value is not None:
|
|
131
132
|
return PropertyError(detail="ModelProperty cannot have a default value") # pragma: no cover
|
|
132
133
|
return None
|
|
@@ -140,8 +141,8 @@ class ModelProperty(PropertyProtocol):
|
|
|
140
141
|
"""Constructs a self import statement from this ModelProperty's attributes"""
|
|
141
142
|
return f"models.{self.class_info.module_name} import {self.class_info.name}"
|
|
142
143
|
|
|
143
|
-
def get_base_type_string(self) ->
|
|
144
|
-
return self.class_info.name
|
|
144
|
+
def get_base_type_string(self) -> strings.PythonCode:
|
|
145
|
+
return strings.PythonCode(self.class_info.name)
|
|
145
146
|
|
|
146
147
|
def get_imports(self, *, prefix: str) -> set[str]:
|
|
147
148
|
"""
|
|
@@ -188,9 +189,9 @@ class ModelProperty(PropertyProtocol):
|
|
|
188
189
|
self,
|
|
189
190
|
no_optional: bool = False,
|
|
190
191
|
json: bool = False,
|
|
191
|
-
) ->
|
|
192
|
+
) -> strings.PythonCode:
|
|
192
193
|
"""
|
|
193
|
-
Get a
|
|
194
|
+
Get a Python code representation of type that should be used when declaring this property
|
|
194
195
|
|
|
195
196
|
Args:
|
|
196
197
|
no_optional: Do not include Optional or Unset even if the value is optional (needed for isinstance checks)
|
|
@@ -203,7 +204,7 @@ class ModelProperty(PropertyProtocol):
|
|
|
203
204
|
|
|
204
205
|
if no_optional or self.required:
|
|
205
206
|
return type_string
|
|
206
|
-
return f"{type_string} | Unset"
|
|
207
|
+
return strings.PythonCode(f"{type_string.as_unembedded_code()} | Unset")
|
|
207
208
|
|
|
208
209
|
|
|
209
210
|
from .property import Property # noqa: E402
|
|
@@ -232,14 +233,14 @@ def _process_properties( # noqa: PLR0912, PLR0911
|
|
|
232
233
|
*,
|
|
233
234
|
data: oai.Schema,
|
|
234
235
|
schemas: Schemas,
|
|
235
|
-
class_name:
|
|
236
|
+
class_name: strings.ClassName,
|
|
236
237
|
config: Config,
|
|
237
|
-
roots: set[ReferencePath |
|
|
238
|
+
roots: set[ReferencePath | strings.ClassName],
|
|
238
239
|
) -> _PropertyData | PropertyError:
|
|
239
240
|
from . import property_from_data # noqa: PLC0415
|
|
240
241
|
from .merge_properties import merge_properties # noqa: PLC0415
|
|
241
242
|
|
|
242
|
-
properties: dict[
|
|
243
|
+
properties: dict[oai.UntrustedString, Property] = {}
|
|
243
244
|
relative_imports: set[str] = set()
|
|
244
245
|
lazy_imports: set[str] = set()
|
|
245
246
|
required_set = set(data.required or [])
|
|
@@ -265,7 +266,7 @@ def _process_properties( # noqa: PLR0912, PLR0911
|
|
|
265
266
|
properties[merged_prop.name] = merged_prop
|
|
266
267
|
return None
|
|
267
268
|
|
|
268
|
-
unprocessed_props: list[tuple[
|
|
269
|
+
unprocessed_props: list[tuple[oai.UntrustedString, oai.Reference | oai.Schema]] = (
|
|
269
270
|
list(data.properties.items()) if data.properties else []
|
|
270
271
|
)
|
|
271
272
|
for sub_prop in data.allOf:
|
|
@@ -275,7 +276,7 @@ def _process_properties( # noqa: PLR0912, PLR0911
|
|
|
275
276
|
return PropertyError(detail=ref_path.detail, data=sub_prop)
|
|
276
277
|
sub_model = schemas.classes_by_reference.get(ref_path)
|
|
277
278
|
if sub_model is None:
|
|
278
|
-
return PropertyError(f"Reference {sub_prop.ref} not found")
|
|
279
|
+
return PropertyError(f"Reference {sub_prop.ref.get_untrusted_value()} not found")
|
|
279
280
|
if not isinstance(sub_model, ModelProperty):
|
|
280
281
|
return PropertyError("Cannot take allOf a non-object")
|
|
281
282
|
# Properties of allOf references first should be processed first
|
|
@@ -335,10 +336,10 @@ def _process_properties( # noqa: PLR0912, PLR0911
|
|
|
335
336
|
|
|
336
337
|
|
|
337
338
|
ANY_ADDITIONAL_PROPERTY = AnyProperty.build(
|
|
338
|
-
name="additional",
|
|
339
|
+
name=oai.UntrustedString("additional"),
|
|
339
340
|
required=True,
|
|
340
341
|
default=None,
|
|
341
|
-
description=
|
|
342
|
+
description=None,
|
|
342
343
|
python_name=PythonIdentifier(value="additional", prefix=""),
|
|
343
344
|
example=None,
|
|
344
345
|
)
|
|
@@ -348,10 +349,10 @@ def _get_additional_properties(
|
|
|
348
349
|
*,
|
|
349
350
|
schema_additional: None | (bool | (oai.Reference | oai.Schema)),
|
|
350
351
|
schemas: Schemas,
|
|
351
|
-
class_name:
|
|
352
|
+
class_name: strings.ClassName,
|
|
352
353
|
config: Config,
|
|
353
|
-
roots: set[ReferencePath |
|
|
354
|
-
) -> tuple[Property |
|
|
354
|
+
roots: set[ReferencePath | strings.ClassName],
|
|
355
|
+
) -> tuple[Property | PropertyError | None, Schemas]:
|
|
355
356
|
from . import property_from_data # noqa: PLC0415
|
|
356
357
|
|
|
357
358
|
if schema_additional is None:
|
|
@@ -367,7 +368,7 @@ def _get_additional_properties(
|
|
|
367
368
|
return ANY_ADDITIONAL_PROPERTY, schemas
|
|
368
369
|
|
|
369
370
|
additional_properties, schemas = property_from_data(
|
|
370
|
-
name="AdditionalProperty",
|
|
371
|
+
name=oai.UntrustedString("AdditionalProperty"),
|
|
371
372
|
required=True, # in the sense that if present in the dict will not be None
|
|
372
373
|
data=schema_additional,
|
|
373
374
|
schemas=schemas,
|
|
@@ -384,7 +385,7 @@ def _process_property_data(
|
|
|
384
385
|
schemas: Schemas,
|
|
385
386
|
class_info: Class,
|
|
386
387
|
config: Config,
|
|
387
|
-
roots: set[ReferencePath |
|
|
388
|
+
roots: set[ReferencePath | strings.ClassName],
|
|
388
389
|
) -> tuple[tuple[_PropertyData, Property | None] | PropertyError, Schemas]:
|
|
389
390
|
property_data = _process_properties(
|
|
390
391
|
data=data, schemas=schemas, class_name=class_info.name, config=config, roots=roots
|
|
@@ -2,24 +2,24 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, ClassVar
|
|
4
4
|
|
|
5
|
-
from attr import define
|
|
5
|
+
from attr import define, field
|
|
6
6
|
|
|
7
7
|
from ... import schema as oai
|
|
8
|
-
from ...
|
|
8
|
+
from ...strings import PythonCode, PythonIdentifier
|
|
9
9
|
from ..errors import PropertyError
|
|
10
|
-
from .protocol import PropertyProtocol, Value
|
|
10
|
+
from .protocol import PropertyProtocol, Value, convert_example
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@define
|
|
14
14
|
class NoneProperty(PropertyProtocol):
|
|
15
15
|
"""A property that can only be None"""
|
|
16
16
|
|
|
17
|
-
name:
|
|
17
|
+
name: oai.UntrustedString
|
|
18
18
|
required: bool
|
|
19
19
|
default: Value | None
|
|
20
20
|
python_name: PythonIdentifier
|
|
21
|
-
description:
|
|
22
|
-
example:
|
|
21
|
+
description: oai.UntrustedString | None
|
|
22
|
+
example: oai.UntrustedString | None = field(converter=convert_example)
|
|
23
23
|
|
|
24
24
|
_allowed_locations: ClassVar[set[oai.ParameterLocation]] = {
|
|
25
25
|
oai.ParameterLocation.QUERY,
|
|
@@ -32,12 +32,12 @@ class NoneProperty(PropertyProtocol):
|
|
|
32
32
|
@classmethod
|
|
33
33
|
def build(
|
|
34
34
|
cls,
|
|
35
|
-
name:
|
|
35
|
+
name: oai.UntrustedString,
|
|
36
36
|
required: bool,
|
|
37
37
|
default: Any,
|
|
38
38
|
python_name: PythonIdentifier,
|
|
39
|
-
description:
|
|
40
|
-
example:
|
|
39
|
+
description: oai.UntrustedString | None,
|
|
40
|
+
example: Any,
|
|
41
41
|
) -> NoneProperty | PropertyError:
|
|
42
42
|
checked_default = cls.convert_value(default)
|
|
43
43
|
if isinstance(checked_default, PropertyError):
|
|
@@ -52,10 +52,10 @@ class NoneProperty(PropertyProtocol):
|
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
@classmethod
|
|
55
|
-
def convert_value(cls, value: Any) -> Value |
|
|
55
|
+
def convert_value(cls, value: Any) -> Value | PropertyError | None:
|
|
56
56
|
if value is None or isinstance(value, Value):
|
|
57
57
|
return value
|
|
58
58
|
if isinstance(value, str):
|
|
59
59
|
if value == "None":
|
|
60
|
-
return Value(python_code=value, raw_value=value)
|
|
60
|
+
return Value(python_code=PythonCode(value), raw_value=value)
|
|
61
61
|
return PropertyError(f"Value {value} is not valid, only None is allowed")
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
__all__ = ["PropertyProtocol", "Value"]
|
|
3
|
+
__all__ = ["PropertyProtocol", "Value", "convert_example"]
|
|
4
4
|
|
|
5
5
|
from abc import abstractmethod
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import Any, ClassVar, Protocol, TypeVar
|
|
8
8
|
|
|
9
9
|
from ... import Config
|
|
10
10
|
from ... import schema as oai
|
|
11
|
-
from ...
|
|
11
|
+
from ...strings import PythonCode, PythonIdentifier, safe_for_docstring
|
|
12
12
|
from ..errors import ParseError, PropertyError
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
|
|
15
|
+
def convert_example(example: Any) -> oai.UntrustedString | None:
|
|
16
|
+
"""
|
|
17
|
+
Convert a raw OpenAPI `example` (which can be anything) into an `UntrustedString` (or None).
|
|
18
|
+
|
|
19
|
+
Examples only ever appear in docstrings, so non-string examples are stringified. Wrapping ensures a
|
|
20
|
+
template which uses an example without escaping renders a safe-but-broken value instead of an exploit.
|
|
21
|
+
"""
|
|
22
|
+
if example is None or isinstance(example, oai.UntrustedString):
|
|
23
|
+
return example
|
|
24
|
+
if isinstance(example, str):
|
|
25
|
+
return oai.UntrustedString(example)
|
|
26
|
+
return oai.UntrustedString(str(example))
|
|
18
27
|
|
|
19
28
|
|
|
20
29
|
@dataclass
|
|
@@ -24,7 +33,7 @@ class Value:
|
|
|
24
33
|
(with string escaping, for example). We still keep the `raw_value` around for merging `allOf`.
|
|
25
34
|
"""
|
|
26
35
|
|
|
27
|
-
python_code:
|
|
36
|
+
python_code: PythonCode
|
|
28
37
|
raw_value: Any
|
|
29
38
|
|
|
30
39
|
|
|
@@ -45,7 +54,7 @@ class PropertyProtocol(Protocol):
|
|
|
45
54
|
ValidationError: Raised when the default value fails to be converted to the expected type
|
|
46
55
|
"""
|
|
47
56
|
|
|
48
|
-
name:
|
|
57
|
+
name: oai.UntrustedString
|
|
49
58
|
required: bool
|
|
50
59
|
_type_string: ClassVar[str] = ""
|
|
51
60
|
_json_type_string: ClassVar[str] = "" # Type of the property after JSON serialization
|
|
@@ -56,26 +65,26 @@ class PropertyProtocol(Protocol):
|
|
|
56
65
|
}
|
|
57
66
|
default: Value | None
|
|
58
67
|
python_name: PythonIdentifier
|
|
59
|
-
description:
|
|
60
|
-
example:
|
|
68
|
+
description: oai.UntrustedString | None
|
|
69
|
+
example: oai.UntrustedString | None
|
|
61
70
|
|
|
62
71
|
template: ClassVar[str] = "any_property.py.jinja"
|
|
63
72
|
json_is_dict: ClassVar[bool] = False
|
|
64
73
|
|
|
65
74
|
@abstractmethod
|
|
66
|
-
def convert_value(self, value: Any) -> Value |
|
|
75
|
+
def convert_value(self, value: Any) -> Value | PropertyError | None:
|
|
67
76
|
"""Convert a string value to a Value object"""
|
|
68
77
|
raise NotImplementedError() # pragma: no cover
|
|
69
78
|
|
|
70
79
|
def validate_location(self, location: oai.ParameterLocation) -> ParseError | None:
|
|
71
80
|
"""Returns an error if this type of property is not allowed in the given location"""
|
|
72
81
|
if location not in self._allowed_locations:
|
|
73
|
-
return ParseError(detail=f"{self.get_type_string()} is not allowed in {location}")
|
|
82
|
+
return ParseError(detail=f"{self.get_type_string().as_unembedded_code()} is not allowed in {location}")
|
|
74
83
|
if location == oai.ParameterLocation.PATH and not self.required:
|
|
75
84
|
return ParseError(detail="Path parameter must be required")
|
|
76
85
|
return None
|
|
77
86
|
|
|
78
|
-
def set_python_name(self, new_name:
|
|
87
|
+
def set_python_name(self, new_name: oai.UntrustedString, config: Config, skip_snake_case: bool = False) -> None:
|
|
79
88
|
"""Mutates this Property to set a new python_name.
|
|
80
89
|
|
|
81
90
|
Required to mutate due to how Properties are stored and the difficulty of updating them in-dict.
|
|
@@ -88,21 +97,21 @@ class PropertyProtocol(Protocol):
|
|
|
88
97
|
PythonIdentifier(value=new_name, prefix=config.field_prefix, skip_snake_case=skip_snake_case),
|
|
89
98
|
)
|
|
90
99
|
|
|
91
|
-
def get_base_type_string(self) ->
|
|
92
|
-
"""Get the
|
|
93
|
-
return self._type_string
|
|
100
|
+
def get_base_type_string(self) -> PythonCode:
|
|
101
|
+
"""Get the code describing the Python type of this property. Base types no require quoting."""
|
|
102
|
+
return PythonCode(self._type_string)
|
|
94
103
|
|
|
95
|
-
def get_base_json_type_string(self) ->
|
|
96
|
-
"""Get the
|
|
97
|
-
return self._json_type_string
|
|
104
|
+
def get_base_json_type_string(self) -> PythonCode:
|
|
105
|
+
"""Get the code describing the JSON type of this property. Base types no require quoting."""
|
|
106
|
+
return PythonCode(self._json_type_string)
|
|
98
107
|
|
|
99
108
|
def get_type_string(
|
|
100
109
|
self,
|
|
101
110
|
no_optional: bool = False,
|
|
102
111
|
json: bool = False,
|
|
103
|
-
) ->
|
|
112
|
+
) -> PythonCode:
|
|
104
113
|
"""
|
|
105
|
-
Get a
|
|
114
|
+
Get a Python code representation of type that should be used when declaring this property
|
|
106
115
|
|
|
107
116
|
Args:
|
|
108
117
|
no_optional: Do not include Optional or Unset even if the value is optional (needed for isinstance checks)
|
|
@@ -115,10 +124,10 @@ class PropertyProtocol(Protocol):
|
|
|
115
124
|
|
|
116
125
|
if no_optional or self.required:
|
|
117
126
|
return type_string
|
|
118
|
-
return f"{type_string} | Unset"
|
|
127
|
+
return PythonCode(f"{type_string.as_unembedded_code()} | Unset")
|
|
119
128
|
|
|
120
|
-
def get_instance_type_string(self) ->
|
|
121
|
-
"""Get a
|
|
129
|
+
def get_instance_type_string(self) -> PythonCode:
|
|
130
|
+
"""Get a Python code representation of runtime type that should be used for `isinstance` checks"""
|
|
122
131
|
return self.get_type_string(no_optional=True)
|
|
123
132
|
|
|
124
133
|
# noinspection PyUnusedLocal
|
|
@@ -144,25 +153,26 @@ class PropertyProtocol(Protocol):
|
|
|
144
153
|
"""
|
|
145
154
|
return set()
|
|
146
155
|
|
|
147
|
-
def to_string(self) ->
|
|
156
|
+
def to_string(self) -> PythonCode:
|
|
148
157
|
"""How this should be declared in a dataclass"""
|
|
149
158
|
default: str | None
|
|
150
159
|
if self.default is not None:
|
|
151
|
-
default = self.default.python_code
|
|
160
|
+
default = self.default.python_code.as_unembedded_code()
|
|
152
161
|
elif not self.required:
|
|
153
162
|
default = "UNSET"
|
|
154
163
|
else:
|
|
155
164
|
default = None
|
|
156
165
|
|
|
166
|
+
declaration = f"{self.python_name}: {self.get_type_string().as_unembedded_code()}"
|
|
157
167
|
if default is not None:
|
|
158
|
-
|
|
159
|
-
return
|
|
168
|
+
declaration += f" = {default}"
|
|
169
|
+
return PythonCode(declaration)
|
|
160
170
|
|
|
161
171
|
def to_docstring(self) -> str:
|
|
162
172
|
"""Returns property docstring"""
|
|
163
|
-
doc = f"{self.python_name} ({self.get_type_string()}): {self.description or ''}"
|
|
173
|
+
doc = f"{self.python_name} ({safe_for_docstring(self.get_type_string())}): {safe_for_docstring(self.description or '')}"
|
|
164
174
|
if self.default:
|
|
165
|
-
doc += f" Default: {self.default.python_code}."
|
|
175
|
+
doc += f" Default: {safe_for_docstring(self.default.python_code)}."
|
|
166
176
|
if self.example:
|
|
167
|
-
doc += f" Example: {self.example}."
|
|
177
|
+
doc += f" Example: {safe_for_docstring(self.example)}."
|
|
168
178
|
return doc
|
|
@@ -10,7 +10,7 @@ __all__ = [
|
|
|
10
10
|
"update_schemas_with_data",
|
|
11
11
|
]
|
|
12
12
|
|
|
13
|
-
from typing import TYPE_CHECKING, NewType
|
|
13
|
+
from typing import TYPE_CHECKING, NewType
|
|
14
14
|
from urllib.parse import urlparse
|
|
15
15
|
|
|
16
16
|
from attrs import define, evolve, field
|
|
@@ -18,7 +18,9 @@ from attrs import define, evolve, field
|
|
|
18
18
|
from ... import Config
|
|
19
19
|
from ... import schema as oai
|
|
20
20
|
from ...schema.openapi_schema_pydantic import Parameter
|
|
21
|
-
from ...
|
|
21
|
+
from ...schema.ref import Ref
|
|
22
|
+
from ...schema.untrusted_string import UntrustedString
|
|
23
|
+
from ...strings import ClassName, PythonIdentifier
|
|
22
24
|
from ..errors import ParameterError, ParseError, PropertyError
|
|
23
25
|
|
|
24
26
|
if TYPE_CHECKING: # pragma: no cover
|
|
@@ -29,10 +31,10 @@ else:
|
|
|
29
31
|
Property = "Property"
|
|
30
32
|
|
|
31
33
|
|
|
32
|
-
ReferencePath = NewType("ReferencePath",
|
|
34
|
+
ReferencePath = NewType("ReferencePath", UntrustedString)
|
|
33
35
|
|
|
34
36
|
|
|
35
|
-
def parse_reference_path(ref_path_raw:
|
|
37
|
+
def parse_reference_path(ref_path_raw: Ref) -> ReferencePath | ParseError:
|
|
36
38
|
"""
|
|
37
39
|
Takes a raw string provided in a `$ref` and turns it into a validated `_ReferencePath` or a `ParseError` if
|
|
38
40
|
validation fails.
|
|
@@ -40,17 +42,17 @@ def parse_reference_path(ref_path_raw: str) -> ReferencePath | ParseError:
|
|
|
40
42
|
See Also:
|
|
41
43
|
- https://swagger.io/docs/specification/using-ref/
|
|
42
44
|
"""
|
|
43
|
-
parsed = urlparse(ref_path_raw)
|
|
45
|
+
parsed = urlparse(ref_path_raw.get_untrusted_value())
|
|
44
46
|
if parsed.scheme or parsed.path:
|
|
45
|
-
return ParseError(detail=f"Remote references such as {
|
|
46
|
-
return
|
|
47
|
+
return ParseError(detail=f"Remote references such as {parsed} are not supported yet.")
|
|
48
|
+
return ReferencePath(UntrustedString(parsed.fragment))
|
|
47
49
|
|
|
48
50
|
|
|
49
|
-
def get_reference_simple_name(ref_path:
|
|
51
|
+
def get_reference_simple_name(ref_path: Ref | UntrustedString) -> UntrustedString:
|
|
50
52
|
"""
|
|
51
53
|
Takes a path like `/components/schemas/NameOfThing` and returns a string like `NameOfThing`.
|
|
52
54
|
"""
|
|
53
|
-
return ref_path.rsplit("/", maxsplit=1)[-1]
|
|
55
|
+
return UntrustedString(ref_path.get_untrusted_value().rsplit("/", maxsplit=1)[-1])
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
@define
|
|
@@ -61,10 +63,10 @@ class Class:
|
|
|
61
63
|
module_name: PythonIdentifier
|
|
62
64
|
|
|
63
65
|
@staticmethod
|
|
64
|
-
def from_string(*, string: str, config: Config) -> "Class":
|
|
66
|
+
def from_string(*, string: str | UntrustedString, config: Config) -> "Class":
|
|
65
67
|
"""Get a Class from an arbitrary string"""
|
|
66
|
-
|
|
67
|
-
class_name = ClassName(
|
|
68
|
+
untrusted_class_name = get_reference_simple_name(Ref(string)) # Get rid of ref path stuff
|
|
69
|
+
class_name = ClassName(untrusted_class_name, config.field_prefix)
|
|
68
70
|
override = config.class_overrides.get(class_name)
|
|
69
71
|
|
|
70
72
|
if override is not None and override.class_name is not None:
|
|
@@ -134,12 +136,15 @@ def update_schemas_with_data(
|
|
|
134
136
|
)
|
|
135
137
|
|
|
136
138
|
if isinstance(prop, PropertyError):
|
|
137
|
-
|
|
138
|
-
prop.header = f"Unable to parse schema {ref_path}"
|
|
139
|
-
if isinstance(prop.data, oai.Reference) and prop.data.ref.endswith(
|
|
140
|
-
|
|
139
|
+
detail = f"{prop.header}: {prop.detail}" if prop.detail is not None else prop.header
|
|
140
|
+
prop.header = f"Unable to parse schema {ref_path.get_untrusted_value()}"
|
|
141
|
+
if isinstance(prop.data, oai.Reference) and prop.data.ref.endswith(
|
|
142
|
+
ref_path.get_untrusted_value()
|
|
143
|
+
): # pragma: nocover
|
|
144
|
+
detail += (
|
|
141
145
|
"\n\nRecursive and circular references are not supported directly in an array schema's 'items' section"
|
|
142
146
|
)
|
|
147
|
+
prop.detail = detail
|
|
143
148
|
return prop
|
|
144
149
|
|
|
145
150
|
schemas = evolve(schemas, classes_by_reference={ref_path: prop, **schemas.classes_by_reference})
|
|
@@ -157,7 +162,7 @@ class Parameters:
|
|
|
157
162
|
|
|
158
163
|
def parameter_from_data(
|
|
159
164
|
*,
|
|
160
|
-
name:
|
|
165
|
+
name: UntrustedString,
|
|
161
166
|
data: oai.Reference | oai.Parameter,
|
|
162
167
|
parameters: Parameters,
|
|
163
168
|
config: Config,
|
|
@@ -171,10 +176,10 @@ def parameter_from_data(
|
|
|
171
176
|
return ParameterError("Parameter has no schema"), parameters
|
|
172
177
|
|
|
173
178
|
new_param = Parameter(
|
|
174
|
-
name=name,
|
|
179
|
+
name=name.get_untrusted_value(),
|
|
175
180
|
required=data.required,
|
|
176
181
|
explode=data.explode,
|
|
177
|
-
style=data.style,
|
|
182
|
+
style=data.style.get_untrusted_value() if data.style is not None else None,
|
|
178
183
|
param_schema=data.param_schema,
|
|
179
184
|
param_in=data.param_in,
|
|
180
185
|
)
|
|
@@ -203,10 +208,12 @@ def update_parameters_with_data(
|
|
|
203
208
|
"""
|
|
204
209
|
param, parameters = parameter_from_data(data=data, name=data.name, parameters=parameters, config=config)
|
|
205
210
|
|
|
206
|
-
if isinstance(param, ParameterError):
|
|
211
|
+
if isinstance(param, ParameterError): # pragma: no cover
|
|
207
212
|
param.detail = f"{param.header}: {param.detail}"
|
|
208
|
-
param.header = f"Unable to parse parameter {ref_path}"
|
|
209
|
-
if isinstance(param.data, oai.Reference) and param.data.ref.endswith(
|
|
213
|
+
param.header = f"Unable to parse parameter {ref_path.get_untrusted_value()}"
|
|
214
|
+
if isinstance(param.data, oai.Reference) and param.data.ref.endswith(
|
|
215
|
+
ref_path.get_untrusted_value()
|
|
216
|
+
): # pragma: nocover
|
|
210
217
|
param.detail += (
|
|
211
218
|
"\n\nRecursive and circular references are not supported. "
|
|
212
219
|
"See https://github.com/openapi-generators/openapi-python-client/issues/466"
|
|
@@ -245,5 +252,5 @@ def parameter_from_reference(
|
|
|
245
252
|
|
|
246
253
|
_resolved_parameter_class = parameters.classes_by_reference.get(ref_path, None)
|
|
247
254
|
if _resolved_parameter_class is None:
|
|
248
|
-
return ParameterError(detail=f"Reference `{ref_path}` not found.")
|
|
255
|
+
return ParameterError(detail=f"Reference `{ref_path.get_untrusted_value()}` not found.")
|
|
249
256
|
return _resolved_parameter_class
|
|
@@ -2,25 +2,24 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, ClassVar, overload
|
|
4
4
|
|
|
5
|
-
from attr import define
|
|
5
|
+
from attr import define, field
|
|
6
6
|
|
|
7
7
|
from ... import schema as oai
|
|
8
|
-
from ... import
|
|
9
|
-
from ...utils import PythonIdentifier
|
|
8
|
+
from ...strings import PythonCode, PythonIdentifier, remove_string_escapes
|
|
10
9
|
from ..errors import PropertyError
|
|
11
|
-
from .protocol import PropertyProtocol, Value
|
|
10
|
+
from .protocol import PropertyProtocol, Value, convert_example
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
@define
|
|
15
14
|
class StringProperty(PropertyProtocol):
|
|
16
15
|
"""A property of type str"""
|
|
17
16
|
|
|
18
|
-
name:
|
|
17
|
+
name: oai.UntrustedString
|
|
19
18
|
required: bool
|
|
20
19
|
default: Value | None
|
|
21
20
|
python_name: PythonIdentifier
|
|
22
|
-
description:
|
|
23
|
-
example:
|
|
21
|
+
description: oai.UntrustedString | None
|
|
22
|
+
example: oai.UntrustedString | None = field(converter=convert_example)
|
|
24
23
|
_type_string: ClassVar[str] = "str"
|
|
25
24
|
_json_type_string: ClassVar[str] = "str"
|
|
26
25
|
_allowed_locations: ClassVar[set[oai.ParameterLocation]] = {
|
|
@@ -33,12 +32,12 @@ class StringProperty(PropertyProtocol):
|
|
|
33
32
|
@classmethod
|
|
34
33
|
def build(
|
|
35
34
|
cls,
|
|
36
|
-
name:
|
|
35
|
+
name: oai.UntrustedString,
|
|
37
36
|
required: bool,
|
|
38
37
|
default: Any,
|
|
39
38
|
python_name: PythonIdentifier,
|
|
40
|
-
description:
|
|
41
|
-
example:
|
|
39
|
+
description: oai.UntrustedString | None,
|
|
40
|
+
example: Any,
|
|
42
41
|
) -> StringProperty | PropertyError:
|
|
43
42
|
checked_default = cls.convert_value(default)
|
|
44
43
|
return cls(
|
|
@@ -65,4 +64,4 @@ class StringProperty(PropertyProtocol):
|
|
|
65
64
|
return value
|
|
66
65
|
if not isinstance(value, str):
|
|
67
66
|
value = str(value)
|
|
68
|
-
return Value(python_code=repr(
|
|
67
|
+
return Value(python_code=PythonCode(repr(remove_string_escapes(value))), raw_value=value)
|