openapi-python-client 0.21.5__tar.gz → 0.21.6__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.
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/PKG-INFO +14 -2
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/README.md +11 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/__init__.py +5 -1
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/config.py +3 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/openapi.py +5 -2
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/__init__.py +22 -1
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/enum_property.py +6 -3
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/list_property.py +19 -3
- openapi_python_client-0.21.6/openapi_python_client/parser/properties/literal_enum_property.py +191 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/merge_properties.py +30 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/property.py +4 -0
- openapi_python_client-0.21.6/openapi_python_client/parser/properties/uuid.py +80 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/schema.py +1 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/client.py.jinja +1 -1
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/endpoint_module.py.jinja +2 -2
- openapi_python_client-0.21.6/openapi_python_client/templates/literal_enum.py.jinja +10 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/model.py.jinja +1 -1
- openapi_python_client-0.21.6/openapi_python_client/templates/property_templates/literal_enum_property.py.jinja +38 -0
- openapi_python_client-0.21.6/openapi_python_client/templates/property_templates/uuid_property.py.jinja +38 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/pyproject.toml +5 -4
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/.gitignore +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/LICENSE +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/__main__.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/cli.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/__init__.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/bodies.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/errors.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/any.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/boolean.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/const.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/date.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/datetime.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/file.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/float.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/int.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/model_property.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/none.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/protocol.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/schemas.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/string.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/properties/union.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/responses.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/py.typed +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/3.0.3.md +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/3.1.0.md +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/__init__.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/data_type.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/LICENSE +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/README.md +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/__init__.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/callback.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/components.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/contact.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/encoding.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/example.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/header.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/info.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/license.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/link.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/media_type.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/open_api.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/operation.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/parameter.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/path_item.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/paths.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/reference.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/request_body.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/response.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/responses.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/server.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/tag.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/openapi_schema_pydantic/xml.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/parameter_location.py +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/.gitignore.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/README.md.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/api_init.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/endpoint_init.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/endpoint_macros.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/errors.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/helpers.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/int_enum.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/models_init.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/package_init.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/any_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/boolean_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/const_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/date_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/datetime_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/enum_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/file_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/float_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/helpers.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/int_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/list_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/model_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/property_macros.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/property_templates/union_property.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/pyproject.toml.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/pyproject_ruff.toml.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/setup.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/str_enum.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/templates/types.py.jinja +0 -0
- {openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: openapi-python-client
|
|
3
|
-
Version: 0.21.
|
|
3
|
+
Version: 0.21.6
|
|
4
4
|
Summary: Generate modern Python clients from OpenAPI
|
|
5
5
|
Project-URL: repository, https://github.com/openapi-generators/openapi-python-client
|
|
6
6
|
Author-email: Dylan Anthony <contact@dylananthony.com>
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
20
|
Classifier: Topic :: Software Development :: Code Generators
|
|
20
21
|
Classifier: Typing :: Typed
|
|
21
22
|
Requires-Python: <4.0,>=3.8.1
|
|
@@ -26,7 +27,7 @@ Requires-Dist: jinja2<4.0.0,>=3.0.0
|
|
|
26
27
|
Requires-Dist: pydantic<3.0.0,>=2.1.1
|
|
27
28
|
Requires-Dist: python-dateutil<3.0.0,>=2.8.1
|
|
28
29
|
Requires-Dist: ruamel-yaml<0.19.0,>=0.18.6
|
|
29
|
-
Requires-Dist: ruff<0.
|
|
30
|
+
Requires-Dist: ruff<0.8,>=0.2
|
|
30
31
|
Requires-Dist: shellingham<2.0.0,>=1.3.2
|
|
31
32
|
Requires-Dist: typer<0.13,>0.6
|
|
32
33
|
Requires-Dist: typing-extensions<5.0.0,>=4.8.0
|
|
@@ -131,6 +132,17 @@ class_overrides:
|
|
|
131
132
|
|
|
132
133
|
The easiest way to find what needs to be overridden is probably to generate your client and go look at everything in the `models` folder.
|
|
133
134
|
|
|
135
|
+
### literal_enums
|
|
136
|
+
|
|
137
|
+
By default, `openapi-python-client` generates classes inheriting for `Enum` for enums. It can instead use `Literal`
|
|
138
|
+
values for enums by setting this to `true`:
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
literal_enums: true
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This is especially useful if enum values, when transformed to their Python names, end up conflicting due to case sensitivity or special symbols.
|
|
145
|
+
|
|
134
146
|
### project_name_override and package_name_override
|
|
135
147
|
|
|
136
148
|
Used to change the name of generated client library project/package. If the project name is changed but an override for the package name
|
|
@@ -97,6 +97,17 @@ class_overrides:
|
|
|
97
97
|
|
|
98
98
|
The easiest way to find what needs to be overridden is probably to generate your client and go look at everything in the `models` folder.
|
|
99
99
|
|
|
100
|
+
### literal_enums
|
|
101
|
+
|
|
102
|
+
By default, `openapi-python-client` generates classes inheriting for `Enum` for enums. It can instead use `Literal`
|
|
103
|
+
values for enums by setting this to `true`:
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
literal_enums: true
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This is especially useful if enum values, when transformed to their Python names, end up conflicting due to case sensitivity or special symbols.
|
|
110
|
+
|
|
100
111
|
### project_name_override and package_name_override
|
|
101
112
|
|
|
102
113
|
Used to change the name of generated client library project/package. If the project name is changed but an override for the package name
|
{openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/__init__.py
RENAMED
|
@@ -20,6 +20,7 @@ from openapi_python_client import utils
|
|
|
20
20
|
from .config import Config, MetaType
|
|
21
21
|
from .parser import GeneratorData, import_string_from_class
|
|
22
22
|
from .parser.errors import ErrorLevel, GeneratorError
|
|
23
|
+
from .parser.properties import LiteralEnumProperty
|
|
23
24
|
|
|
24
25
|
__version__ = version(__package__)
|
|
25
26
|
|
|
@@ -227,9 +228,12 @@ class Project:
|
|
|
227
228
|
# Generate enums
|
|
228
229
|
str_enum_template = self.env.get_template("str_enum.py.jinja")
|
|
229
230
|
int_enum_template = self.env.get_template("int_enum.py.jinja")
|
|
231
|
+
literal_enum_template = self.env.get_template("literal_enum.py.jinja")
|
|
230
232
|
for enum in self.openapi.enums:
|
|
231
233
|
module_path = models_dir / f"{enum.class_info.module_name}.py"
|
|
232
|
-
if enum
|
|
234
|
+
if isinstance(enum, LiteralEnumProperty):
|
|
235
|
+
module_path.write_text(literal_enum_template.render(enum=enum), encoding=self.config.file_encoding)
|
|
236
|
+
elif enum.value_type is int:
|
|
233
237
|
module_path.write_text(int_enum_template.render(enum=enum), encoding=self.config.file_encoding)
|
|
234
238
|
else:
|
|
235
239
|
module_path.write_text(str_enum_template.render(enum=enum), encoding=self.config.file_encoding)
|
{openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/config.py
RENAMED
|
@@ -43,6 +43,7 @@ class ConfigFile(BaseModel):
|
|
|
43
43
|
post_hooks: Optional[List[str]] = None
|
|
44
44
|
field_prefix: str = "field_"
|
|
45
45
|
http_timeout: int = 5
|
|
46
|
+
literal_enums: bool = False
|
|
46
47
|
|
|
47
48
|
@staticmethod
|
|
48
49
|
def load_from_path(path: Path) -> "ConfigFile":
|
|
@@ -70,6 +71,7 @@ class Config:
|
|
|
70
71
|
post_hooks: List[str]
|
|
71
72
|
field_prefix: str
|
|
72
73
|
http_timeout: int
|
|
74
|
+
literal_enums: bool
|
|
73
75
|
document_source: Union[Path, str]
|
|
74
76
|
file_encoding: str
|
|
75
77
|
content_type_overrides: Dict[str, str]
|
|
@@ -109,6 +111,7 @@ class Config:
|
|
|
109
111
|
post_hooks=post_hooks,
|
|
110
112
|
field_prefix=config_file.field_prefix,
|
|
111
113
|
http_timeout=config_file.http_timeout,
|
|
114
|
+
literal_enums=config_file.literal_enums,
|
|
112
115
|
document_source=document_source,
|
|
113
116
|
file_encoding=file_encoding,
|
|
114
117
|
overwrite=overwrite,
|
|
@@ -15,6 +15,7 @@ from .errors import GeneratorError, ParseError, PropertyError
|
|
|
15
15
|
from .properties import (
|
|
16
16
|
Class,
|
|
17
17
|
EnumProperty,
|
|
18
|
+
LiteralEnumProperty,
|
|
18
19
|
ModelProperty,
|
|
19
20
|
Parameters,
|
|
20
21
|
Property,
|
|
@@ -488,7 +489,7 @@ class GeneratorData:
|
|
|
488
489
|
models: Iterator[ModelProperty]
|
|
489
490
|
errors: List[ParseError]
|
|
490
491
|
endpoint_collections_by_tag: Dict[utils.PythonIdentifier, EndpointCollection]
|
|
491
|
-
enums: Iterator[EnumProperty]
|
|
492
|
+
enums: Iterator[Union[EnumProperty, LiteralEnumProperty]]
|
|
492
493
|
|
|
493
494
|
@staticmethod
|
|
494
495
|
def from_dict(data: Dict[str, Any], *, config: Config) -> Union["GeneratorData", GeneratorError]:
|
|
@@ -517,7 +518,9 @@ class GeneratorData:
|
|
|
517
518
|
data=openapi.paths, schemas=schemas, parameters=parameters, request_bodies=request_bodies, config=config
|
|
518
519
|
)
|
|
519
520
|
|
|
520
|
-
enums = (
|
|
521
|
+
enums = (
|
|
522
|
+
prop for prop in schemas.classes_by_name.values() if isinstance(prop, (EnumProperty, LiteralEnumProperty))
|
|
523
|
+
)
|
|
521
524
|
models = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, ModelProperty))
|
|
522
525
|
|
|
523
526
|
return GeneratorData(
|
|
@@ -4,6 +4,7 @@ __all__ = [
|
|
|
4
4
|
"AnyProperty",
|
|
5
5
|
"Class",
|
|
6
6
|
"EnumProperty",
|
|
7
|
+
"LiteralEnumProperty",
|
|
7
8
|
"ModelProperty",
|
|
8
9
|
"Parameters",
|
|
9
10
|
"Property",
|
|
@@ -30,6 +31,7 @@ from .file import FileProperty
|
|
|
30
31
|
from .float import FloatProperty
|
|
31
32
|
from .int import IntProperty
|
|
32
33
|
from .list_property import ListProperty
|
|
34
|
+
from .literal_enum_property import LiteralEnumProperty
|
|
33
35
|
from .model_property import ModelProperty, process_model
|
|
34
36
|
from .none import NoneProperty
|
|
35
37
|
from .property import Property
|
|
@@ -44,11 +46,12 @@ from .schemas import (
|
|
|
44
46
|
)
|
|
45
47
|
from .string import StringProperty
|
|
46
48
|
from .union import UnionProperty
|
|
49
|
+
from .uuid import UuidProperty
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
def _string_based_property(
|
|
50
53
|
name: str, required: bool, data: oai.Schema, config: Config
|
|
51
|
-
) -> StringProperty | DateProperty | DateTimeProperty | FileProperty | PropertyError:
|
|
54
|
+
) -> StringProperty | DateProperty | DateTimeProperty | FileProperty | UuidProperty | PropertyError:
|
|
52
55
|
"""Construct a Property from the type "string" """
|
|
53
56
|
string_format = data.schema_format
|
|
54
57
|
python_name = utils.PythonIdentifier(value=name, prefix=config.field_prefix)
|
|
@@ -79,6 +82,15 @@ def _string_based_property(
|
|
|
79
82
|
description=data.description,
|
|
80
83
|
example=data.example,
|
|
81
84
|
)
|
|
85
|
+
if string_format == "uuid":
|
|
86
|
+
return UuidProperty.build(
|
|
87
|
+
name=name,
|
|
88
|
+
required=required,
|
|
89
|
+
default=data.default,
|
|
90
|
+
python_name=python_name,
|
|
91
|
+
description=data.description,
|
|
92
|
+
example=data.example,
|
|
93
|
+
)
|
|
82
94
|
return StringProperty.build(
|
|
83
95
|
name=name,
|
|
84
96
|
default=data.default,
|
|
@@ -184,6 +196,15 @@ def property_from_data( # noqa: PLR0911, PLR0912
|
|
|
184
196
|
schemas,
|
|
185
197
|
)
|
|
186
198
|
if data.enum:
|
|
199
|
+
if config.literal_enums:
|
|
200
|
+
return LiteralEnumProperty.build(
|
|
201
|
+
data=data,
|
|
202
|
+
name=name,
|
|
203
|
+
required=required,
|
|
204
|
+
schemas=schemas,
|
|
205
|
+
parent_name=parent_name,
|
|
206
|
+
config=config,
|
|
207
|
+
)
|
|
187
208
|
return EnumProperty.build(
|
|
188
209
|
data=data,
|
|
189
210
|
name=name,
|
|
@@ -121,7 +121,7 @@ class EnumProperty(PropertyProtocol):
|
|
|
121
121
|
if parent_name:
|
|
122
122
|
class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}"
|
|
123
123
|
class_info = Class.from_string(string=class_name, config=config)
|
|
124
|
-
values = EnumProperty.values_from_list(value_list)
|
|
124
|
+
values = EnumProperty.values_from_list(value_list, class_info)
|
|
125
125
|
|
|
126
126
|
if class_info.name in schemas.classes_by_name:
|
|
127
127
|
existing = schemas.classes_by_name[class_info.name]
|
|
@@ -183,7 +183,7 @@ class EnumProperty(PropertyProtocol):
|
|
|
183
183
|
return imports
|
|
184
184
|
|
|
185
185
|
@staticmethod
|
|
186
|
-
def values_from_list(values: list[str] | list[int]) -> dict[str, ValueType]:
|
|
186
|
+
def values_from_list(values: list[str] | list[int], class_info: Class) -> dict[str, ValueType]:
|
|
187
187
|
"""Convert a list of values into dict of {name: value}, where value can sometimes be None"""
|
|
188
188
|
output: dict[str, ValueType] = {}
|
|
189
189
|
|
|
@@ -200,7 +200,10 @@ class EnumProperty(PropertyProtocol):
|
|
|
200
200
|
else:
|
|
201
201
|
key = f"VALUE_{i}"
|
|
202
202
|
if key in output:
|
|
203
|
-
raise ValueError(
|
|
203
|
+
raise ValueError(
|
|
204
|
+
f"Duplicate key {key} in enum {class_info.module_name}.{class_info.name}; "
|
|
205
|
+
f"consider setting literal_enums in your config"
|
|
206
|
+
)
|
|
204
207
|
sanitized_key = utils.snake_case(key).upper()
|
|
205
208
|
output[sanitized_key] = utils.remove_string_escapes(value)
|
|
206
209
|
return output
|
|
@@ -58,12 +58,28 @@ class ListProperty(PropertyProtocol):
|
|
|
58
58
|
"""
|
|
59
59
|
from . import property_from_data
|
|
60
60
|
|
|
61
|
-
if data.items is None:
|
|
62
|
-
return
|
|
61
|
+
if data.items is None and not data.prefixItems:
|
|
62
|
+
return (
|
|
63
|
+
PropertyError(
|
|
64
|
+
data=data,
|
|
65
|
+
detail="type array must have items or prefixItems defined",
|
|
66
|
+
),
|
|
67
|
+
schemas,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
items = data.prefixItems or []
|
|
71
|
+
if data.items:
|
|
72
|
+
items.append(data.items)
|
|
73
|
+
|
|
74
|
+
if len(items) == 1:
|
|
75
|
+
inner_schema = items[0]
|
|
76
|
+
else:
|
|
77
|
+
inner_schema = oai.Schema(anyOf=items)
|
|
78
|
+
|
|
63
79
|
inner_prop, schemas = property_from_data(
|
|
64
80
|
name=f"{name}_item",
|
|
65
81
|
required=True,
|
|
66
|
-
data=
|
|
82
|
+
data=inner_schema,
|
|
67
83
|
schemas=schemas,
|
|
68
84
|
parent_name=parent_name,
|
|
69
85
|
config=config,
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
__all__ = ["LiteralEnumProperty"]
|
|
4
|
+
|
|
5
|
+
from typing import Any, ClassVar, List, Union, cast
|
|
6
|
+
|
|
7
|
+
from attr import evolve
|
|
8
|
+
from attrs import define
|
|
9
|
+
|
|
10
|
+
from ... import Config, utils
|
|
11
|
+
from ... import schema as oai
|
|
12
|
+
from ...schema import DataType
|
|
13
|
+
from ..errors import PropertyError
|
|
14
|
+
from .none import NoneProperty
|
|
15
|
+
from .protocol import PropertyProtocol, Value
|
|
16
|
+
from .schemas import Class, Schemas
|
|
17
|
+
from .union import UnionProperty
|
|
18
|
+
|
|
19
|
+
ValueType = Union[str, int]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@define
|
|
23
|
+
class LiteralEnumProperty(PropertyProtocol):
|
|
24
|
+
"""A property that should use a literal enum"""
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
required: bool
|
|
28
|
+
default: Value | None
|
|
29
|
+
python_name: utils.PythonIdentifier
|
|
30
|
+
description: str | None
|
|
31
|
+
example: str | None
|
|
32
|
+
values: set[ValueType]
|
|
33
|
+
class_info: Class
|
|
34
|
+
value_type: type[ValueType]
|
|
35
|
+
|
|
36
|
+
template: ClassVar[str] = "literal_enum_property.py.jinja"
|
|
37
|
+
|
|
38
|
+
_allowed_locations: ClassVar[set[oai.ParameterLocation]] = {
|
|
39
|
+
oai.ParameterLocation.QUERY,
|
|
40
|
+
oai.ParameterLocation.PATH,
|
|
41
|
+
oai.ParameterLocation.COOKIE,
|
|
42
|
+
oai.ParameterLocation.HEADER,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def build( # noqa: PLR0911
|
|
47
|
+
cls,
|
|
48
|
+
*,
|
|
49
|
+
data: oai.Schema,
|
|
50
|
+
name: str,
|
|
51
|
+
required: bool,
|
|
52
|
+
schemas: Schemas,
|
|
53
|
+
parent_name: str,
|
|
54
|
+
config: Config,
|
|
55
|
+
) -> tuple[LiteralEnumProperty | NoneProperty | UnionProperty | PropertyError, Schemas]:
|
|
56
|
+
"""
|
|
57
|
+
Create a LiteralEnumProperty from schema data.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
data: The OpenAPI Schema which defines this enum.
|
|
61
|
+
name: The name to use for variables which receive this Enum's value (e.g. model property name)
|
|
62
|
+
required: Whether or not this Property is required in the calling context
|
|
63
|
+
schemas: The Schemas which have been defined so far (used to prevent naming collisions)
|
|
64
|
+
parent_name: The context in which this LiteralEnumProperty is defined, used to create more specific class names.
|
|
65
|
+
config: The global config for this run of the generator
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
A tuple containing either the created property or a PropertyError AND update schemas.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
enum = data.enum or [] # The outer function checks for this, but mypy doesn't know that
|
|
72
|
+
|
|
73
|
+
# OpenAPI allows for null as an enum value, but it doesn't make sense with how enums are constructed in Python.
|
|
74
|
+
# So instead, if null is a possible value, make the property nullable.
|
|
75
|
+
# Mypy is not smart enough to know that the type is right though
|
|
76
|
+
unchecked_value_list = [value for value in enum if value is not None] # type: ignore
|
|
77
|
+
|
|
78
|
+
# It's legal to have an enum that only contains null as a value, we don't bother constructing an enum for that
|
|
79
|
+
if len(unchecked_value_list) == 0:
|
|
80
|
+
return (
|
|
81
|
+
NoneProperty.build(
|
|
82
|
+
name=name,
|
|
83
|
+
required=required,
|
|
84
|
+
default="None",
|
|
85
|
+
python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix),
|
|
86
|
+
description=None,
|
|
87
|
+
example=None,
|
|
88
|
+
),
|
|
89
|
+
schemas,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
value_types = {type(value) for value in unchecked_value_list}
|
|
93
|
+
if len(value_types) > 1:
|
|
94
|
+
return PropertyError(
|
|
95
|
+
header="Enum values must all be the same type", detail=f"Got {value_types}", data=data
|
|
96
|
+
), schemas
|
|
97
|
+
value_type = next(iter(value_types))
|
|
98
|
+
if value_type not in (str, int):
|
|
99
|
+
return PropertyError(header=f"Unsupported enum type {value_type}", data=data), schemas
|
|
100
|
+
value_list = cast(
|
|
101
|
+
Union[List[int], List[str]], unchecked_value_list
|
|
102
|
+
) # We checked this with all the value_types stuff
|
|
103
|
+
|
|
104
|
+
if len(value_list) < len(enum): # Only one of the values was None, that becomes a union
|
|
105
|
+
data.oneOf = [
|
|
106
|
+
oai.Schema(type=DataType.NULL),
|
|
107
|
+
data.model_copy(update={"enum": value_list, "default": data.default}),
|
|
108
|
+
]
|
|
109
|
+
data.enum = None
|
|
110
|
+
return UnionProperty.build(
|
|
111
|
+
data=data,
|
|
112
|
+
name=name,
|
|
113
|
+
required=required,
|
|
114
|
+
schemas=schemas,
|
|
115
|
+
parent_name=parent_name,
|
|
116
|
+
config=config,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
class_name = data.title or name
|
|
120
|
+
if parent_name:
|
|
121
|
+
class_name = f"{utils.pascal_case(parent_name)}{utils.pascal_case(class_name)}"
|
|
122
|
+
class_info = Class.from_string(string=class_name, config=config)
|
|
123
|
+
values: set[str | int] = set(value_list)
|
|
124
|
+
|
|
125
|
+
if class_info.name in schemas.classes_by_name:
|
|
126
|
+
existing = schemas.classes_by_name[class_info.name]
|
|
127
|
+
if not isinstance(existing, LiteralEnumProperty) or values != existing.values:
|
|
128
|
+
return (
|
|
129
|
+
PropertyError(
|
|
130
|
+
detail=f"Found conflicting enums named {class_info.name} with incompatible values.", data=data
|
|
131
|
+
),
|
|
132
|
+
schemas,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
prop = LiteralEnumProperty(
|
|
136
|
+
name=name,
|
|
137
|
+
required=required,
|
|
138
|
+
class_info=class_info,
|
|
139
|
+
values=values,
|
|
140
|
+
value_type=value_type,
|
|
141
|
+
default=None,
|
|
142
|
+
python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix),
|
|
143
|
+
description=data.description,
|
|
144
|
+
example=data.example,
|
|
145
|
+
)
|
|
146
|
+
checked_default = prop.convert_value(data.default)
|
|
147
|
+
if isinstance(checked_default, PropertyError):
|
|
148
|
+
checked_default.data = data
|
|
149
|
+
return checked_default, schemas
|
|
150
|
+
prop = evolve(prop, default=checked_default)
|
|
151
|
+
|
|
152
|
+
schemas = evolve(schemas, classes_by_name={**schemas.classes_by_name, class_info.name: prop})
|
|
153
|
+
return prop, schemas
|
|
154
|
+
|
|
155
|
+
def convert_value(self, value: Any) -> Value | PropertyError | None:
|
|
156
|
+
if value is None or isinstance(value, Value):
|
|
157
|
+
return value
|
|
158
|
+
if isinstance(value, self.value_type):
|
|
159
|
+
if value in self.values:
|
|
160
|
+
return Value(python_code=repr(value), raw_value=value)
|
|
161
|
+
else:
|
|
162
|
+
return PropertyError(detail=f"Value {value} is not valid for enum {self.name}")
|
|
163
|
+
return PropertyError(detail=f"Cannot convert {value} to enum {self.name} of type {self.value_type}")
|
|
164
|
+
|
|
165
|
+
def get_base_type_string(self, *, quoted: bool = False) -> str:
|
|
166
|
+
return self.class_info.name
|
|
167
|
+
|
|
168
|
+
def get_base_json_type_string(self, *, quoted: bool = False) -> str:
|
|
169
|
+
return self.value_type.__name__
|
|
170
|
+
|
|
171
|
+
def get_instance_type_string(self) -> str:
|
|
172
|
+
return self.value_type.__name__
|
|
173
|
+
|
|
174
|
+
def get_imports(self, *, prefix: str) -> set[str]:
|
|
175
|
+
"""
|
|
176
|
+
Get a set of import strings that should be included when this property is used somewhere
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
prefix: A prefix to put before any relative (local) module names. This should be the number of . to get
|
|
180
|
+
back to the root of the generated client.
|
|
181
|
+
"""
|
|
182
|
+
imports = super().get_imports(prefix=prefix)
|
|
183
|
+
imports.add("from typing import cast")
|
|
184
|
+
imports.add(f"from {prefix}models.{self.class_info.module_name} import {self.class_info.name}")
|
|
185
|
+
imports.add(
|
|
186
|
+
f"from {prefix}models.{self.class_info.module_name} import check_{self.get_class_name_snake_case()}"
|
|
187
|
+
)
|
|
188
|
+
return imports
|
|
189
|
+
|
|
190
|
+
def get_class_name_snake_case(self) -> str:
|
|
191
|
+
return utils.snake_case(self.class_info.name)
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from openapi_python_client.parser.properties.date import DateProperty
|
|
4
4
|
from openapi_python_client.parser.properties.datetime import DateTimeProperty
|
|
5
5
|
from openapi_python_client.parser.properties.file import FileProperty
|
|
6
|
+
from openapi_python_client.parser.properties.literal_enum_property import LiteralEnumProperty
|
|
6
7
|
|
|
7
8
|
__all__ = ["merge_properties"]
|
|
8
9
|
|
|
@@ -53,6 +54,9 @@ def merge_properties(prop1: Property, prop2: Property) -> Property | PropertyErr
|
|
|
53
54
|
if isinstance(prop1, EnumProperty) or isinstance(prop2, EnumProperty):
|
|
54
55
|
return _merge_with_enum(prop1, prop2)
|
|
55
56
|
|
|
57
|
+
if isinstance(prop1, LiteralEnumProperty) or isinstance(prop2, LiteralEnumProperty):
|
|
58
|
+
return _merge_with_literal_enum(prop1, prop2)
|
|
59
|
+
|
|
56
60
|
if (merged := _merge_same_type(prop1, prop2)) is not None:
|
|
57
61
|
return merged
|
|
58
62
|
|
|
@@ -136,6 +140,32 @@ def _merge_with_enum(prop1: PropertyProtocol, prop2: PropertyProtocol) -> EnumPr
|
|
|
136
140
|
)
|
|
137
141
|
|
|
138
142
|
|
|
143
|
+
def _merge_with_literal_enum(prop1: PropertyProtocol, prop2: PropertyProtocol) -> LiteralEnumProperty | PropertyError:
|
|
144
|
+
if isinstance(prop1, LiteralEnumProperty) and isinstance(prop2, LiteralEnumProperty):
|
|
145
|
+
# We want the narrowest validation rules that fit both, so use whichever values list is a
|
|
146
|
+
# subset of the other.
|
|
147
|
+
if prop1.values <= prop2.values:
|
|
148
|
+
values = prop1.values
|
|
149
|
+
class_info = prop1.class_info
|
|
150
|
+
elif prop2.values <= prop1.values:
|
|
151
|
+
values = prop2.values
|
|
152
|
+
class_info = prop2.class_info
|
|
153
|
+
else:
|
|
154
|
+
return PropertyError(detail="can't redefine a literal enum property with incompatible lists of values")
|
|
155
|
+
return _merge_common_attributes(evolve(prop1, values=values, class_info=class_info), prop2)
|
|
156
|
+
|
|
157
|
+
# If enum values were specified for just one of the properties, use those.
|
|
158
|
+
enum_prop = prop1 if isinstance(prop1, LiteralEnumProperty) else cast(LiteralEnumProperty, prop2)
|
|
159
|
+
non_enum_prop = prop2 if isinstance(prop1, LiteralEnumProperty) else prop1
|
|
160
|
+
if (isinstance(non_enum_prop, IntProperty) and enum_prop.value_type is int) or (
|
|
161
|
+
isinstance(non_enum_prop, StringProperty) and enum_prop.value_type is str
|
|
162
|
+
):
|
|
163
|
+
return _merge_common_attributes(enum_prop, prop1, prop2)
|
|
164
|
+
return PropertyError(
|
|
165
|
+
detail=f"can't combine literal enum of type {enum_prop.value_type} with {non_enum_prop.get_type_string(no_optional=True)}"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
139
169
|
def _merge_common_attributes(base: PropertyT, *extend_with: PropertyProtocol) -> PropertyT | PropertyError:
|
|
140
170
|
"""Create a new instance based on base, overriding basic attributes with values from extend_with, in order.
|
|
141
171
|
|
|
@@ -14,10 +14,12 @@ from .file import FileProperty
|
|
|
14
14
|
from .float import FloatProperty
|
|
15
15
|
from .int import IntProperty
|
|
16
16
|
from .list_property import ListProperty
|
|
17
|
+
from .literal_enum_property import LiteralEnumProperty
|
|
17
18
|
from .model_property import ModelProperty
|
|
18
19
|
from .none import NoneProperty
|
|
19
20
|
from .string import StringProperty
|
|
20
21
|
from .union import UnionProperty
|
|
22
|
+
from .uuid import UuidProperty
|
|
21
23
|
|
|
22
24
|
Property: TypeAlias = Union[
|
|
23
25
|
AnyProperty,
|
|
@@ -26,6 +28,7 @@ Property: TypeAlias = Union[
|
|
|
26
28
|
DateProperty,
|
|
27
29
|
DateTimeProperty,
|
|
28
30
|
EnumProperty,
|
|
31
|
+
LiteralEnumProperty,
|
|
29
32
|
FileProperty,
|
|
30
33
|
FloatProperty,
|
|
31
34
|
IntProperty,
|
|
@@ -34,4 +37,5 @@ Property: TypeAlias = Union[
|
|
|
34
37
|
NoneProperty,
|
|
35
38
|
StringProperty,
|
|
36
39
|
UnionProperty,
|
|
40
|
+
UuidProperty,
|
|
37
41
|
]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from attr import define
|
|
7
|
+
|
|
8
|
+
from ... import schema as oai
|
|
9
|
+
from ...utils import PythonIdentifier
|
|
10
|
+
from ..errors import PropertyError
|
|
11
|
+
from .protocol import PropertyProtocol, Value
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@define
|
|
15
|
+
class UuidProperty(PropertyProtocol):
|
|
16
|
+
"""A property of type uuid.UUID"""
|
|
17
|
+
|
|
18
|
+
name: str
|
|
19
|
+
required: bool
|
|
20
|
+
default: Value | None
|
|
21
|
+
python_name: PythonIdentifier
|
|
22
|
+
description: str | None
|
|
23
|
+
example: str | None
|
|
24
|
+
|
|
25
|
+
_type_string: ClassVar[str] = "UUID"
|
|
26
|
+
_json_type_string: ClassVar[str] = "str"
|
|
27
|
+
_allowed_locations: ClassVar[set[oai.ParameterLocation]] = {
|
|
28
|
+
oai.ParameterLocation.QUERY,
|
|
29
|
+
oai.ParameterLocation.PATH,
|
|
30
|
+
oai.ParameterLocation.COOKIE,
|
|
31
|
+
oai.ParameterLocation.HEADER,
|
|
32
|
+
}
|
|
33
|
+
template: ClassVar[str] = "uuid_property.py.jinja"
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def build(
|
|
37
|
+
cls,
|
|
38
|
+
name: str,
|
|
39
|
+
required: bool,
|
|
40
|
+
default: Any,
|
|
41
|
+
python_name: PythonIdentifier,
|
|
42
|
+
description: str | None,
|
|
43
|
+
example: str | None,
|
|
44
|
+
) -> UuidProperty | PropertyError:
|
|
45
|
+
checked_default = cls.convert_value(default)
|
|
46
|
+
if isinstance(checked_default, PropertyError):
|
|
47
|
+
return checked_default
|
|
48
|
+
|
|
49
|
+
return cls(
|
|
50
|
+
name=name,
|
|
51
|
+
required=required,
|
|
52
|
+
default=checked_default,
|
|
53
|
+
python_name=python_name,
|
|
54
|
+
description=description,
|
|
55
|
+
example=example,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def convert_value(cls, value: Any) -> Value | None | PropertyError:
|
|
60
|
+
if value is None or isinstance(value, Value):
|
|
61
|
+
return value
|
|
62
|
+
if isinstance(value, str):
|
|
63
|
+
try:
|
|
64
|
+
UUID(value)
|
|
65
|
+
except ValueError:
|
|
66
|
+
return PropertyError(f"Invalid UUID value: {value}")
|
|
67
|
+
return Value(python_code=f"UUID('{value}')", raw_value=value)
|
|
68
|
+
return PropertyError(f"Invalid UUID value: {value}")
|
|
69
|
+
|
|
70
|
+
def get_imports(self, *, prefix: str) -> set[str]:
|
|
71
|
+
"""
|
|
72
|
+
Get a set of import strings that should be included when this property is used somewhere
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
prefix: A prefix to put before any relative (local) module names. This should be the number of . to get
|
|
76
|
+
back to the root of the generated client.
|
|
77
|
+
"""
|
|
78
|
+
imports = super().get_imports(prefix=prefix)
|
|
79
|
+
imports.update({"from uuid import UUID"})
|
|
80
|
+
return imports
|
|
@@ -43,6 +43,7 @@ class Schema(BaseModel):
|
|
|
43
43
|
anyOf: List[Union[Reference, "Schema"]] = Field(default_factory=list)
|
|
44
44
|
schema_not: Optional[Union[Reference, "Schema"]] = Field(default=None, alias="not")
|
|
45
45
|
items: Optional[Union[Reference, "Schema"]] = None
|
|
46
|
+
prefixItems: Optional[List[Union[Reference, "Schema"]]] = Field(default_factory=list)
|
|
46
47
|
properties: Optional[Dict[str, Union[Reference, "Schema"]]] = None
|
|
47
48
|
additionalProperties: Optional[Union[bool, Reference, "Schema"]] = None
|
|
48
49
|
description: Optional[str] = None
|
|
@@ -74,7 +74,7 @@ class Client:
|
|
|
74
74
|
{% endmacro %}{{ builders("Client") }}
|
|
75
75
|
{% macro httpx_stuff(name, custom_constructor=None) %}
|
|
76
76
|
def set_httpx_client(self, client: httpx.Client) -> "{{ name }}":
|
|
77
|
-
"""Manually the underlying httpx.Client
|
|
77
|
+
"""Manually set the underlying httpx.Client
|
|
78
78
|
|
|
79
79
|
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
|
|
80
80
|
"""
|
|
@@ -7,7 +7,7 @@ from ...client import AuthenticatedClient, Client
|
|
|
7
7
|
from ...types import Response, UNSET
|
|
8
8
|
from ... import errors
|
|
9
9
|
|
|
10
|
-
{% for relative in endpoint.relative_imports %}
|
|
10
|
+
{% for relative in endpoint.relative_imports | sort %}
|
|
11
11
|
{{ relative }}
|
|
12
12
|
{% endfor %}
|
|
13
13
|
|
|
@@ -70,7 +70,7 @@ def _get_kwargs(
|
|
|
70
70
|
|
|
71
71
|
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[{{ return_string }}]:
|
|
72
72
|
{% for response in endpoint.responses %}
|
|
73
|
-
if response.status_code ==
|
|
73
|
+
if response.status_code == {{ response.status_code.value }}:
|
|
74
74
|
{% if parsed_responses %}{% import "property_templates/" + response.prop.template as prop_template %}
|
|
75
75
|
{% if prop_template.construct %}
|
|
76
76
|
{{ prop_template.construct(response.prop, response.source.attribute) | indent(8) }}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from typing import Literal, Set, cast
|
|
2
|
+
|
|
3
|
+
{{ enum.class_info.name }} = Literal{{ "%r" | format(enum.values|list|sort) }}
|
|
4
|
+
|
|
5
|
+
{{ enum.get_class_name_snake_case() | upper }}_VALUES: Set[{{ enum.class_info.name }}] = { {% for v in enum.values|list|sort %}{{"%r"|format(v)}}, {% endfor %} }
|
|
6
|
+
|
|
7
|
+
def check_{{ enum.get_class_name_snake_case() }}(value: {{ enum.get_instance_type_string() }}) -> {{ enum.class_info.name}}:
|
|
8
|
+
if value in {{ enum.get_class_name_snake_case() | upper }}_VALUES:
|
|
9
|
+
return cast({{enum.class_info.name}}, value)
|
|
10
|
+
raise TypeError(f"Unexpected value {value!r}. Expected one of {{"{"}}{{ enum.get_class_name_snake_case() | upper }}_VALUES!r}")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{% macro construct_function(property, source) %}
|
|
2
|
+
check_{{ property.get_class_name_snake_case() }}({{ source }})
|
|
3
|
+
{% endmacro %}
|
|
4
|
+
|
|
5
|
+
{% from "property_templates/property_macros.py.jinja" import construct_template %}
|
|
6
|
+
|
|
7
|
+
{% macro construct(property, source) %}
|
|
8
|
+
{{ construct_template(construct_function, property, source) }}
|
|
9
|
+
{% endmacro %}
|
|
10
|
+
|
|
11
|
+
{% macro check_type_for_construct(property, source) %}isinstance({{ source }}, {{ property.get_instance_type_string() }}){% endmacro %}
|
|
12
|
+
|
|
13
|
+
{% macro transform(property, source, destination, declare_type=True, multipart=False) %}
|
|
14
|
+
{% set type_string = property.get_type_string(json=True) %}
|
|
15
|
+
{% if property.required %}
|
|
16
|
+
{{ destination }}{% if declare_type %}: {{ type_string }}{% endif %} = {{ source }}
|
|
17
|
+
{%- else %}
|
|
18
|
+
{{ destination }}{% if declare_type %}: {{ type_string }}{% endif %} = UNSET
|
|
19
|
+
if not isinstance({{ source }}, Unset):
|
|
20
|
+
{{ destination }} = {{ source }}
|
|
21
|
+
{% endif %}
|
|
22
|
+
{% endmacro %}
|
|
23
|
+
|
|
24
|
+
{% macro transform_multipart(property, source, destination) %}
|
|
25
|
+
{% set transformed = "(None, str(" + source + ").encode(), \"text/plain\")" %}
|
|
26
|
+
{% set type_string = "Union[Unset, Tuple[None, bytes, str]]" %}
|
|
27
|
+
{% if property.required %}
|
|
28
|
+
{{ destination }} = {{ transformed }}
|
|
29
|
+
{%- else %}
|
|
30
|
+
{{ destination }}: {{ type_string }} = UNSET
|
|
31
|
+
if not isinstance({{ source }}, Unset):
|
|
32
|
+
{{ destination }} = {{ transformed }}
|
|
33
|
+
{% endif %}
|
|
34
|
+
{% endmacro %}
|
|
35
|
+
|
|
36
|
+
{% macro transform_header(source) %}
|
|
37
|
+
str({{ source }})
|
|
38
|
+
{% endmacro %}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{% macro construct_function(property, source) %}
|
|
2
|
+
UUID({{ source }})
|
|
3
|
+
{% endmacro %}
|
|
4
|
+
|
|
5
|
+
{% from "property_templates/property_macros.py.jinja" import construct_template %}
|
|
6
|
+
|
|
7
|
+
{% macro construct(property, source) %}
|
|
8
|
+
{{ construct_template(construct_function, property, source) }}
|
|
9
|
+
{% endmacro %}
|
|
10
|
+
|
|
11
|
+
{% macro check_type_for_construct(property, source) %}isinstance({{ source }}, str){% endmacro %}
|
|
12
|
+
|
|
13
|
+
{% macro transform(property, source, destination, declare_type=True) %}
|
|
14
|
+
{% set transformed = "str(" + source + ")" %}
|
|
15
|
+
{% if property.required %}
|
|
16
|
+
{{ destination }} = {{ transformed }}
|
|
17
|
+
{%- else %}
|
|
18
|
+
{% if declare_type %}
|
|
19
|
+
{% set type_annotation = property.get_type_string(json=True) %}
|
|
20
|
+
{{ destination }}: {{ type_annotation }} = UNSET
|
|
21
|
+
{% else %}
|
|
22
|
+
{{ destination }} = UNSET
|
|
23
|
+
{% endif %}
|
|
24
|
+
if not isinstance({{ source }}, Unset):
|
|
25
|
+
{{ destination }} = {{ transformed }}
|
|
26
|
+
{%- endif %}
|
|
27
|
+
{% endmacro %}
|
|
28
|
+
|
|
29
|
+
{% macro transform_multipart(property, source, destination) %}
|
|
30
|
+
{% if property.required %}
|
|
31
|
+
{{ destination }} = str({{ source }})
|
|
32
|
+
{%- else %}
|
|
33
|
+
{% set type_annotation = property.get_type_string(json=True) | replace("str", "bytes") %}
|
|
34
|
+
{{ destination }}: {{ type_annotation }} = UNSET
|
|
35
|
+
if not isinstance({{ source }}, Unset):
|
|
36
|
+
{{ destination }} = str({{ source }})
|
|
37
|
+
{%- endif %}
|
|
38
|
+
{% endmacro %}
|
|
@@ -14,11 +14,11 @@ dependencies = [
|
|
|
14
14
|
"python-dateutil>=2.8.1,<3.0.0",
|
|
15
15
|
"httpx>=0.20.0,<0.28.0",
|
|
16
16
|
"ruamel.yaml>=0.18.6,<0.19.0",
|
|
17
|
-
"ruff>=0.2,<0.
|
|
17
|
+
"ruff>=0.2,<0.8",
|
|
18
18
|
"typing-extensions>=4.8.0,<5.0.0",
|
|
19
19
|
]
|
|
20
20
|
name = "openapi-python-client"
|
|
21
|
-
version = "0.21.
|
|
21
|
+
version = "0.21.6"
|
|
22
22
|
description = "Generate modern Python clients from OpenAPI"
|
|
23
23
|
keywords = [
|
|
24
24
|
"OpenAPI",
|
|
@@ -35,6 +35,7 @@ classifiers = [
|
|
|
35
35
|
"Programming Language :: Python :: 3.10",
|
|
36
36
|
"Programming Language :: Python :: 3.11",
|
|
37
37
|
"Programming Language :: Python :: 3.12",
|
|
38
|
+
"Programming Language :: Python :: 3.13",
|
|
38
39
|
"Topic :: Software Development :: Code Generators",
|
|
39
40
|
"Typing :: Typed",
|
|
40
41
|
]
|
|
@@ -59,7 +60,7 @@ exclude = [
|
|
|
59
60
|
|
|
60
61
|
[tool.ruff.lint]
|
|
61
62
|
select = ["E", "F", "I", "UP", "B", "PL", "RUF"]
|
|
62
|
-
ignore = ["E501", "PLR0913"]
|
|
63
|
+
ignore = ["E501", "PLR0913", "PLR2004"]
|
|
63
64
|
|
|
64
65
|
[tool.ruff.lint.per-file-ignores]
|
|
65
66
|
"openapi_python_client/cli.py" = ["B008"]
|
|
@@ -114,7 +115,7 @@ mypy = "mypy openapi_python_client"
|
|
|
114
115
|
check = { composite = ["lint", "format", "mypy", "test"] }
|
|
115
116
|
regen = {composite = ["regen_e2e", "regen_integration"]}
|
|
116
117
|
e2e = "pytest openapi_python_client end_to_end_tests/test_end_to_end.py"
|
|
117
|
-
re = {composite = ["regen_e2e", "e2e"]}
|
|
118
|
+
re = {composite = ["regen_e2e", "e2e --snapshot-update"]}
|
|
118
119
|
regen_e2e = "python -m end_to_end_tests.regen_golden_record"
|
|
119
120
|
|
|
120
121
|
[tool.pdm.scripts.test]
|
|
File without changes
|
|
File without changes
|
{openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/bodies.py
RENAMED
|
File without changes
|
{openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/parser/errors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/py.typed
RENAMED
|
File without changes
|
{openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/3.0.3.md
RENAMED
|
File without changes
|
{openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/schema/3.1.0.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openapi_python_client-0.21.5 → openapi_python_client-0.21.6}/openapi_python_client/utils.py
RENAMED
|
File without changes
|