openapi-python-client 0.21.3__tar.gz → 0.21.5__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.3 → openapi_python_client-0.21.5}/.gitignore +3 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/PKG-INFO +1 -1
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/bodies.py +1 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/openapi.py +2 -2
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/__init__.py +16 -8
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/any.py +6 -2
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/boolean.py +3 -3
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/const.py +7 -9
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/date.py +1 -1
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/datetime.py +1 -1
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/enum_property.py +2 -2
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/float.py +3 -3
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/int.py +10 -6
- openapi_python_client-0.21.5/openapi_python_client/parser/properties/merge_properties.py +168 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/model_property.py +19 -68
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/none.py +1 -1
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/protocol.py +12 -4
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/schemas.py +3 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/string.py +1 -5
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/schema.py +31 -4
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/model.py.jinja +2 -0
- openapi_python_client-0.21.5/openapi_python_client/templates/property_templates/const_property.py.jinja +5 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/pyproject.toml +4 -3
- openapi_python_client-0.21.3/openapi_python_client/templates/property_templates/const_property.py.jinja +0 -5
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/LICENSE +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/README.md +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/__init__.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/__main__.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/cli.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/config.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/__init__.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/errors.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/file.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/list_property.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/property.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/properties/union.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/parser/responses.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/py.typed +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/3.0.3.md +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/3.1.0.md +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/__init__.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/data_type.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/LICENSE +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/README.md +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/__init__.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/callback.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/components.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/contact.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/encoding.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/example.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/header.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/info.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/license.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/link.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/media_type.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/open_api.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/operation.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/parameter.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/path_item.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/paths.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/reference.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/request_body.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/response.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/responses.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/server.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/tag.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/openapi_schema_pydantic/xml.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/parameter_location.py +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/.gitignore.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/README.md.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/api_init.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/client.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/endpoint_init.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/endpoint_macros.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/endpoint_module.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/errors.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/helpers.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/int_enum.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/models_init.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/package_init.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/any_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/boolean_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/date_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/datetime_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/enum_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/file_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/float_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/helpers.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/int_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/list_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/model_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/property_macros.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/property_templates/union_property.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/pyproject.toml.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/pyproject_ruff.toml.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/setup.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/str_enum.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/templates/types.py.jinja +0 -0
- {openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/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.5
|
|
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>
|
|
@@ -155,7 +155,7 @@ class Endpoint:
|
|
|
155
155
|
ParseError(
|
|
156
156
|
detail=(
|
|
157
157
|
f"Invalid response status code {code} (not a valid HTTP "
|
|
158
|
-
f"status code), response will be
|
|
158
|
+
f"status code), response will be omitted from generated "
|
|
159
159
|
f"client"
|
|
160
160
|
)
|
|
161
161
|
)
|
|
@@ -175,7 +175,7 @@ class Endpoint:
|
|
|
175
175
|
ParseError(
|
|
176
176
|
detail=(
|
|
177
177
|
f"Cannot parse response for status code {status_code}{detail_suffix}, "
|
|
178
|
-
f"response will be
|
|
178
|
+
f"response will be omitted from generated client"
|
|
179
179
|
),
|
|
180
180
|
data=response.data,
|
|
181
181
|
)
|
|
@@ -83,7 +83,6 @@ def _string_based_property(
|
|
|
83
83
|
name=name,
|
|
84
84
|
default=data.default,
|
|
85
85
|
required=required,
|
|
86
|
-
pattern=data.pattern,
|
|
87
86
|
python_name=python_name,
|
|
88
87
|
description=data.description,
|
|
89
88
|
example=data.example,
|
|
@@ -126,7 +125,7 @@ def _property_from_ref(
|
|
|
126
125
|
return prop, schemas
|
|
127
126
|
|
|
128
127
|
|
|
129
|
-
def property_from_data( # noqa: PLR0911
|
|
128
|
+
def property_from_data( # noqa: PLR0911, PLR0912
|
|
130
129
|
name: str,
|
|
131
130
|
required: bool,
|
|
132
131
|
data: oai.Reference | oai.Schema,
|
|
@@ -153,7 +152,7 @@ def property_from_data( # noqa: PLR0911
|
|
|
153
152
|
sub_data: list[oai.Schema | oai.Reference] = data.allOf + data.anyOf + data.oneOf
|
|
154
153
|
# A union of a single reference should just be passed through to that reference (don't create copy class)
|
|
155
154
|
if len(sub_data) == 1 and isinstance(sub_data[0], oai.Reference):
|
|
156
|
-
|
|
155
|
+
prop, schemas = _property_from_ref(
|
|
157
156
|
name=name,
|
|
158
157
|
required=required,
|
|
159
158
|
parent=data,
|
|
@@ -162,6 +161,16 @@ def property_from_data( # noqa: PLR0911
|
|
|
162
161
|
config=config,
|
|
163
162
|
roots=roots,
|
|
164
163
|
)
|
|
164
|
+
# We won't be generating a separate Python class for this schema - references to it will just use
|
|
165
|
+
# the class for the schema it's referencing - so we don't add it to classes_by_name; but we do
|
|
166
|
+
# add it to models_to_process, if it's a model, because its properties still need to be resolved.
|
|
167
|
+
if isinstance(prop, ModelProperty):
|
|
168
|
+
schemas = evolve(
|
|
169
|
+
schemas,
|
|
170
|
+
models_to_process=[*schemas.models_to_process, prop],
|
|
171
|
+
)
|
|
172
|
+
return prop, schemas
|
|
173
|
+
|
|
165
174
|
if data.type == oai.DataType.BOOLEAN:
|
|
166
175
|
return (
|
|
167
176
|
BooleanProperty.build(
|
|
@@ -271,7 +280,7 @@ def property_from_data( # noqa: PLR0911
|
|
|
271
280
|
AnyProperty.build(
|
|
272
281
|
name=name,
|
|
273
282
|
required=required,
|
|
274
|
-
default=
|
|
283
|
+
default=data.default,
|
|
275
284
|
python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix),
|
|
276
285
|
description=data.description,
|
|
277
286
|
example=data.example,
|
|
@@ -341,7 +350,7 @@ def _process_model_errors(
|
|
|
341
350
|
|
|
342
351
|
|
|
343
352
|
def _process_models(*, schemas: Schemas, config: Config) -> Schemas:
|
|
344
|
-
to_process =
|
|
353
|
+
to_process = schemas.models_to_process
|
|
345
354
|
still_making_progress = True
|
|
346
355
|
final_model_errors: list[tuple[ModelProperty, PropertyError]] = []
|
|
347
356
|
latest_model_errors: list[tuple[ModelProperty, PropertyError]] = []
|
|
@@ -368,12 +377,11 @@ def _process_models(*, schemas: Schemas, config: Config) -> Schemas:
|
|
|
368
377
|
continue
|
|
369
378
|
schemas = schemas_or_err
|
|
370
379
|
still_making_progress = True
|
|
371
|
-
to_process =
|
|
380
|
+
to_process = next_round
|
|
372
381
|
|
|
373
382
|
final_model_errors.extend(latest_model_errors)
|
|
374
383
|
errors = _process_model_errors(final_model_errors, schemas=schemas)
|
|
375
|
-
schemas
|
|
376
|
-
return schemas
|
|
384
|
+
return evolve(schemas, errors=[*schemas.errors, *errors], models_to_process=to_process)
|
|
377
385
|
|
|
378
386
|
|
|
379
387
|
def build_schemas(
|
|
@@ -33,9 +33,13 @@ class AnyProperty(PropertyProtocol):
|
|
|
33
33
|
|
|
34
34
|
@classmethod
|
|
35
35
|
def convert_value(cls, value: Any) -> Value | None:
|
|
36
|
-
|
|
36
|
+
from .string import StringProperty
|
|
37
|
+
|
|
38
|
+
if value is None:
|
|
37
39
|
return value
|
|
38
|
-
|
|
40
|
+
if isinstance(value, str):
|
|
41
|
+
return StringProperty.convert_value(value)
|
|
42
|
+
return Value(python_code=str(value), raw_value=value)
|
|
39
43
|
|
|
40
44
|
name: str
|
|
41
45
|
required: bool
|
|
@@ -59,9 +59,9 @@ class BooleanProperty(PropertyProtocol):
|
|
|
59
59
|
return value
|
|
60
60
|
if isinstance(value, str):
|
|
61
61
|
if value.lower() == "true":
|
|
62
|
-
return Value("True")
|
|
62
|
+
return Value(python_code="True", raw_value=value)
|
|
63
63
|
elif value.lower() == "false":
|
|
64
|
-
return Value("False")
|
|
64
|
+
return Value(python_code="False", raw_value=value)
|
|
65
65
|
if isinstance(value, bool):
|
|
66
|
-
return Value(str(value))
|
|
66
|
+
return Value(python_code=str(value), raw_value=value)
|
|
67
67
|
return PropertyError(f"Invalid boolean value: {value}")
|
|
@@ -27,7 +27,7 @@ class ConstProperty(PropertyProtocol):
|
|
|
27
27
|
def build(
|
|
28
28
|
cls,
|
|
29
29
|
*,
|
|
30
|
-
const: str | int,
|
|
30
|
+
const: str | int | float | bool,
|
|
31
31
|
default: Any,
|
|
32
32
|
name: str,
|
|
33
33
|
python_name: PythonIdentifier,
|
|
@@ -63,13 +63,13 @@ class ConstProperty(PropertyProtocol):
|
|
|
63
63
|
return prop
|
|
64
64
|
|
|
65
65
|
def convert_value(self, value: Any) -> Value | None | PropertyError:
|
|
66
|
-
if isinstance(value, Value):
|
|
67
|
-
return value
|
|
68
66
|
value = self._convert_value(value)
|
|
69
67
|
if value is None:
|
|
70
68
|
return value
|
|
71
69
|
if value != self.value:
|
|
72
|
-
return PropertyError(
|
|
70
|
+
return PropertyError(
|
|
71
|
+
detail=f"Invalid value for const {self.name}; {value.raw_value} != {self.value.raw_value}"
|
|
72
|
+
)
|
|
73
73
|
return value
|
|
74
74
|
|
|
75
75
|
@staticmethod
|
|
@@ -85,11 +85,9 @@ class ConstProperty(PropertyProtocol):
|
|
|
85
85
|
def _convert_value(value: Any) -> Value | None:
|
|
86
86
|
if value is None or isinstance(value, Value):
|
|
87
87
|
return value
|
|
88
|
-
if isinstance(value, Value):
|
|
89
|
-
return value # pragma: no cover
|
|
90
88
|
if isinstance(value, str):
|
|
91
89
|
return StringProperty.convert_value(value)
|
|
92
|
-
return Value(str(value))
|
|
90
|
+
return Value(python_code=str(value), raw_value=value)
|
|
93
91
|
|
|
94
92
|
def get_type_string(
|
|
95
93
|
self,
|
|
@@ -99,7 +97,7 @@ class ConstProperty(PropertyProtocol):
|
|
|
99
97
|
multipart: bool = False,
|
|
100
98
|
quoted: bool = False,
|
|
101
99
|
) -> str:
|
|
102
|
-
lit = f"Literal[{self.value}]"
|
|
100
|
+
lit = f"Literal[{self.value.python_code}]"
|
|
103
101
|
if not no_optional and not self.required:
|
|
104
102
|
return f"Union[{lit}, Unset]"
|
|
105
103
|
return lit
|
|
@@ -115,6 +113,6 @@ class ConstProperty(PropertyProtocol):
|
|
|
115
113
|
if self.required:
|
|
116
114
|
return {"from typing import Literal"}
|
|
117
115
|
return {
|
|
118
|
-
"from typing import Literal, Union",
|
|
116
|
+
"from typing import Literal, Union, cast",
|
|
119
117
|
f"from {prefix}types import UNSET, Unset",
|
|
120
118
|
}
|
|
@@ -57,7 +57,7 @@ class DateProperty(PropertyProtocol):
|
|
|
57
57
|
isoparse(value).date() # make sure it's a valid value
|
|
58
58
|
except ValueError as e:
|
|
59
59
|
return PropertyError(f"Invalid date: {e}")
|
|
60
|
-
return Value(f"isoparse({value!r}).date()")
|
|
60
|
+
return Value(python_code=f"isoparse({value!r}).date()", raw_value=value)
|
|
61
61
|
return PropertyError(f"Cannot convert {value} to a date")
|
|
62
62
|
|
|
63
63
|
def get_imports(self, *, prefix: str) -> set[str]:
|
|
@@ -59,7 +59,7 @@ class DateTimeProperty(PropertyProtocol):
|
|
|
59
59
|
isoparse(value) # make sure it's a valid value
|
|
60
60
|
except ValueError as e:
|
|
61
61
|
return PropertyError(f"Invalid datetime: {e}")
|
|
62
|
-
return Value(f"isoparse({value!r})")
|
|
62
|
+
return Value(python_code=f"isoparse({value!r})", raw_value=value)
|
|
63
63
|
return PropertyError(f"Cannot convert {value} to a datetime")
|
|
64
64
|
|
|
65
65
|
def get_imports(self, *, prefix: str) -> set[str]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
__all__ = ["EnumProperty"]
|
|
3
|
+
__all__ = ["EnumProperty", "ValueType"]
|
|
4
4
|
|
|
5
5
|
from typing import Any, ClassVar, List, Union, cast
|
|
6
6
|
|
|
@@ -159,7 +159,7 @@ class EnumProperty(PropertyProtocol):
|
|
|
159
159
|
if isinstance(value, self.value_type):
|
|
160
160
|
inverse_values = {v: k for k, v in self.values.items()}
|
|
161
161
|
try:
|
|
162
|
-
return Value(f"{self.class_info.name}.{inverse_values[value]}")
|
|
162
|
+
return Value(python_code=f"{self.class_info.name}.{inverse_values[value]}", raw_value=value)
|
|
163
163
|
except KeyError:
|
|
164
164
|
return PropertyError(detail=f"Value {value} is not valid for enum {self.name}")
|
|
165
165
|
return PropertyError(detail=f"Cannot convert {value} to enum {self.name} of type {self.value_type}")
|
|
@@ -61,11 +61,11 @@ class FloatProperty(PropertyProtocol):
|
|
|
61
61
|
if isinstance(value, str):
|
|
62
62
|
try:
|
|
63
63
|
parsed = float(value)
|
|
64
|
-
return Value(str(parsed))
|
|
64
|
+
return Value(python_code=str(parsed), raw_value=value)
|
|
65
65
|
except ValueError:
|
|
66
66
|
return PropertyError(f"Invalid float value: {value}")
|
|
67
67
|
if isinstance(value, float):
|
|
68
|
-
return Value(str(value))
|
|
68
|
+
return Value(python_code=str(value), raw_value=value)
|
|
69
69
|
if isinstance(value, int) and not isinstance(value, bool):
|
|
70
|
-
return Value(str(float(value)))
|
|
70
|
+
return Value(python_code=str(float(value)), raw_value=value)
|
|
71
71
|
return PropertyError(f"Cannot convert {value} to a float")
|
|
@@ -58,12 +58,16 @@ class IntProperty(PropertyProtocol):
|
|
|
58
58
|
def convert_value(cls, value: Any) -> Value | None | PropertyError:
|
|
59
59
|
if value is None or isinstance(value, Value):
|
|
60
60
|
return value
|
|
61
|
-
|
|
61
|
+
converted = value
|
|
62
|
+
if isinstance(converted, str):
|
|
62
63
|
try:
|
|
63
|
-
|
|
64
|
+
converted = float(converted)
|
|
64
65
|
except ValueError:
|
|
65
|
-
return PropertyError(f"Invalid int value: {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
return PropertyError(f"Invalid int value: {converted}")
|
|
67
|
+
if isinstance(converted, float):
|
|
68
|
+
as_int = int(converted)
|
|
69
|
+
if converted == as_int:
|
|
70
|
+
converted = as_int
|
|
71
|
+
if isinstance(converted, int) and not isinstance(converted, bool):
|
|
72
|
+
return Value(python_code=str(converted), raw_value=value)
|
|
69
73
|
return PropertyError(f"Invalid int value: {value}")
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from openapi_python_client.parser.properties.date import DateProperty
|
|
4
|
+
from openapi_python_client.parser.properties.datetime import DateTimeProperty
|
|
5
|
+
from openapi_python_client.parser.properties.file import FileProperty
|
|
6
|
+
|
|
7
|
+
__all__ = ["merge_properties"]
|
|
8
|
+
|
|
9
|
+
from typing import TypeVar, cast
|
|
10
|
+
|
|
11
|
+
from attr import evolve
|
|
12
|
+
|
|
13
|
+
from ..errors import PropertyError
|
|
14
|
+
from . import FloatProperty
|
|
15
|
+
from .any import AnyProperty
|
|
16
|
+
from .enum_property import EnumProperty
|
|
17
|
+
from .int import IntProperty
|
|
18
|
+
from .list_property import ListProperty
|
|
19
|
+
from .property import Property
|
|
20
|
+
from .protocol import PropertyProtocol
|
|
21
|
+
from .string import StringProperty
|
|
22
|
+
|
|
23
|
+
PropertyT = TypeVar("PropertyT", bound=PropertyProtocol)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
STRING_WITH_FORMAT_TYPES = (DateProperty, DateTimeProperty, FileProperty)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def merge_properties(prop1: Property, prop2: Property) -> Property | PropertyError: # noqa: PLR0911
|
|
30
|
+
"""Attempt to create a new property that incorporates the behavior of both.
|
|
31
|
+
|
|
32
|
+
This is used when merging schemas with allOf, when two schemas define a property with the same name.
|
|
33
|
+
|
|
34
|
+
OpenAPI defines allOf in terms of validation behavior: the input must pass the validation rules
|
|
35
|
+
defined in all the listed schemas. Our task here is slightly more difficult, since we must end
|
|
36
|
+
up with a single Property object that will be used to generate a single class property in the
|
|
37
|
+
generated code. Due to limitations of our internal model, this may not be possible for some
|
|
38
|
+
combinations of property attributes that OpenAPI supports (for instance, we have no way to represent
|
|
39
|
+
a string property that must match two different regexes).
|
|
40
|
+
|
|
41
|
+
Properties can also have attributes that do not represent validation rules, such as "description"
|
|
42
|
+
and "example". OpenAPI does not define any overriding/aggregation rules for these in allOf. The
|
|
43
|
+
implementation here is, assuming prop1 and prop2 are in the same order that the schemas were in the
|
|
44
|
+
allOf, any such attributes that prop2 specifies will override the ones from prop1.
|
|
45
|
+
"""
|
|
46
|
+
if isinstance(prop2, AnyProperty):
|
|
47
|
+
return _merge_common_attributes(prop1, prop2)
|
|
48
|
+
|
|
49
|
+
if isinstance(prop1, AnyProperty):
|
|
50
|
+
# Use the base type of `prop2`, but keep the override order
|
|
51
|
+
return _merge_common_attributes(prop2, prop1, prop2)
|
|
52
|
+
|
|
53
|
+
if isinstance(prop1, EnumProperty) or isinstance(prop2, EnumProperty):
|
|
54
|
+
return _merge_with_enum(prop1, prop2)
|
|
55
|
+
|
|
56
|
+
if (merged := _merge_same_type(prop1, prop2)) is not None:
|
|
57
|
+
return merged
|
|
58
|
+
|
|
59
|
+
if (merged := _merge_numeric(prop1, prop2)) is not None:
|
|
60
|
+
return merged
|
|
61
|
+
|
|
62
|
+
if (merged := _merge_string_with_format(prop1, prop2)) is not None:
|
|
63
|
+
return merged
|
|
64
|
+
|
|
65
|
+
return PropertyError(
|
|
66
|
+
detail=f"{prop1.get_type_string(no_optional=True)} can't be merged with {prop2.get_type_string(no_optional=True)}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _merge_same_type(prop1: Property, prop2: Property) -> Property | None | PropertyError:
|
|
71
|
+
if type(prop1) is not type(prop2):
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
if prop1 == prop2:
|
|
75
|
+
# It's always OK to redefine a property with everything exactly the same
|
|
76
|
+
return prop1
|
|
77
|
+
|
|
78
|
+
if isinstance(prop1, ListProperty) and isinstance(prop2, ListProperty):
|
|
79
|
+
inner_property = merge_properties(prop1.inner_property, prop2.inner_property) # type: ignore
|
|
80
|
+
if isinstance(inner_property, PropertyError):
|
|
81
|
+
return PropertyError(detail=f"can't merge list properties: {inner_property.detail}")
|
|
82
|
+
prop1.inner_property = inner_property
|
|
83
|
+
|
|
84
|
+
# For all other property types, there aren't any special attributes that affect validation, so just
|
|
85
|
+
# apply the rules for common attributes like "description".
|
|
86
|
+
return _merge_common_attributes(prop1, prop2)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _merge_string_with_format(prop1: Property, prop2: Property) -> Property | None | PropertyError:
|
|
90
|
+
"""Merge a string that has no format with a string that has a format"""
|
|
91
|
+
# Here we need to use the DateProperty/DateTimeProperty/FileProperty as the base so that we preserve
|
|
92
|
+
# its class, but keep the correct override order for merging the attributes.
|
|
93
|
+
if isinstance(prop1, StringProperty) and isinstance(prop2, STRING_WITH_FORMAT_TYPES):
|
|
94
|
+
# Use the more specific class as a base, but keep the correct override order
|
|
95
|
+
return _merge_common_attributes(prop2, prop1, prop2)
|
|
96
|
+
elif isinstance(prop2, StringProperty) and isinstance(prop1, STRING_WITH_FORMAT_TYPES):
|
|
97
|
+
return _merge_common_attributes(prop1, prop2)
|
|
98
|
+
else:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _merge_numeric(prop1: Property, prop2: Property) -> IntProperty | None | PropertyError:
|
|
103
|
+
"""Merge IntProperty with FloatProperty"""
|
|
104
|
+
if isinstance(prop1, IntProperty) and isinstance(prop2, (IntProperty, FloatProperty)):
|
|
105
|
+
return _merge_common_attributes(prop1, prop2)
|
|
106
|
+
elif isinstance(prop2, IntProperty) and isinstance(prop1, (IntProperty, FloatProperty)):
|
|
107
|
+
# Use the IntProperty as a base since it's more restrictive, but keep the correct override order
|
|
108
|
+
return _merge_common_attributes(prop2, prop1, prop2)
|
|
109
|
+
else:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _merge_with_enum(prop1: PropertyProtocol, prop2: PropertyProtocol) -> EnumProperty | PropertyError:
|
|
114
|
+
if isinstance(prop1, EnumProperty) and isinstance(prop2, EnumProperty):
|
|
115
|
+
# We want the narrowest validation rules that fit both, so use whichever values list is a
|
|
116
|
+
# subset of the other.
|
|
117
|
+
if _values_are_subset(prop1, prop2):
|
|
118
|
+
values = prop1.values
|
|
119
|
+
class_info = prop1.class_info
|
|
120
|
+
elif _values_are_subset(prop2, prop1):
|
|
121
|
+
values = prop2.values
|
|
122
|
+
class_info = prop2.class_info
|
|
123
|
+
else:
|
|
124
|
+
return PropertyError(detail="can't redefine an enum property with incompatible lists of values")
|
|
125
|
+
return _merge_common_attributes(evolve(prop1, values=values, class_info=class_info), prop2)
|
|
126
|
+
|
|
127
|
+
# If enum values were specified for just one of the properties, use those.
|
|
128
|
+
enum_prop = prop1 if isinstance(prop1, EnumProperty) else cast(EnumProperty, prop2)
|
|
129
|
+
non_enum_prop = prop2 if isinstance(prop1, EnumProperty) else prop1
|
|
130
|
+
if (isinstance(non_enum_prop, IntProperty) and enum_prop.value_type is int) or (
|
|
131
|
+
isinstance(non_enum_prop, StringProperty) and enum_prop.value_type is str
|
|
132
|
+
):
|
|
133
|
+
return _merge_common_attributes(enum_prop, prop1, prop2)
|
|
134
|
+
return PropertyError(
|
|
135
|
+
detail=f"can't combine enum of type {enum_prop.value_type} with {non_enum_prop.get_type_string(no_optional=True)}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _merge_common_attributes(base: PropertyT, *extend_with: PropertyProtocol) -> PropertyT | PropertyError:
|
|
140
|
+
"""Create a new instance based on base, overriding basic attributes with values from extend_with, in order.
|
|
141
|
+
|
|
142
|
+
For "default", "description", and "example", a non-None value overrides any value from a previously
|
|
143
|
+
specified property. The behavior is similar to using the spread operator with dictionaries, except
|
|
144
|
+
that None means "not specified".
|
|
145
|
+
|
|
146
|
+
For "required", any True value overrides all other values (a property that was previously required
|
|
147
|
+
cannot become optional).
|
|
148
|
+
"""
|
|
149
|
+
current = base
|
|
150
|
+
for override in extend_with:
|
|
151
|
+
if override.default is not None:
|
|
152
|
+
override_default = current.convert_value(override.default.raw_value)
|
|
153
|
+
else:
|
|
154
|
+
override_default = None
|
|
155
|
+
if isinstance(override_default, PropertyError):
|
|
156
|
+
return override_default
|
|
157
|
+
current = evolve(
|
|
158
|
+
current, # type: ignore # can't prove that every property type is an attrs class, but it is
|
|
159
|
+
required=current.required or override.required,
|
|
160
|
+
default=override_default or current.default,
|
|
161
|
+
description=override.description or current.description,
|
|
162
|
+
example=override.example or current.example,
|
|
163
|
+
)
|
|
164
|
+
return current
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _values_are_subset(prop1: EnumProperty, prop2: EnumProperty) -> bool:
|
|
168
|
+
return set(prop1.values.items()) <= set(prop2.values.items())
|
|
@@ -10,7 +10,6 @@ from ... import schema as oai
|
|
|
10
10
|
from ...utils import PythonIdentifier
|
|
11
11
|
from ..errors import ParseError, PropertyError
|
|
12
12
|
from .any import AnyProperty
|
|
13
|
-
from .enum_property import EnumProperty
|
|
14
13
|
from .protocol import PropertyProtocol, Value
|
|
15
14
|
from .schemas import Class, ReferencePath, Schemas, parse_reference_path
|
|
16
15
|
|
|
@@ -119,7 +118,11 @@ class ModelProperty(PropertyProtocol):
|
|
|
119
118
|
)
|
|
120
119
|
return error, schemas
|
|
121
120
|
|
|
122
|
-
schemas = evolve(
|
|
121
|
+
schemas = evolve(
|
|
122
|
+
schemas,
|
|
123
|
+
classes_by_name={**schemas.classes_by_name, class_info.name: prop},
|
|
124
|
+
models_to_process=[*schemas.models_to_process, prop],
|
|
125
|
+
)
|
|
123
126
|
return prop, schemas
|
|
124
127
|
|
|
125
128
|
@classmethod
|
|
@@ -216,59 +219,6 @@ class ModelProperty(PropertyProtocol):
|
|
|
216
219
|
from .property import Property # noqa: E402
|
|
217
220
|
|
|
218
221
|
|
|
219
|
-
def _values_are_subset(first: EnumProperty, second: EnumProperty) -> bool:
|
|
220
|
-
return set(first.values.items()) <= set(second.values.items())
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def _types_are_subset(first: EnumProperty, second: Property) -> bool:
|
|
224
|
-
from . import IntProperty, StringProperty
|
|
225
|
-
|
|
226
|
-
if first.value_type is int and isinstance(second, IntProperty):
|
|
227
|
-
return True
|
|
228
|
-
if first.value_type is str and isinstance(second, StringProperty):
|
|
229
|
-
return True
|
|
230
|
-
return False
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def _enum_subset(first: Property, second: Property) -> EnumProperty | None:
|
|
234
|
-
"""Return the EnumProperty that is the subset of the other, if possible."""
|
|
235
|
-
|
|
236
|
-
if isinstance(first, EnumProperty):
|
|
237
|
-
if isinstance(second, EnumProperty):
|
|
238
|
-
if _values_are_subset(first, second):
|
|
239
|
-
return first
|
|
240
|
-
if _values_are_subset(second, first):
|
|
241
|
-
return second
|
|
242
|
-
return None
|
|
243
|
-
return first if _types_are_subset(first, second) else None
|
|
244
|
-
|
|
245
|
-
if isinstance(second, EnumProperty) and _types_are_subset(second, first):
|
|
246
|
-
return second
|
|
247
|
-
return None
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def _merge_properties(first: Property, second: Property) -> Property | PropertyError:
|
|
251
|
-
required = first.required or second.required
|
|
252
|
-
|
|
253
|
-
err = None
|
|
254
|
-
|
|
255
|
-
if first.__class__ == second.__class__:
|
|
256
|
-
first = evolve(first, required=required)
|
|
257
|
-
second = evolve(second, required=required)
|
|
258
|
-
if first == second:
|
|
259
|
-
return first
|
|
260
|
-
err = PropertyError(header="Cannot merge properties", detail="Properties has conflicting values")
|
|
261
|
-
|
|
262
|
-
enum_subset = _enum_subset(first, second)
|
|
263
|
-
if enum_subset is not None:
|
|
264
|
-
return evolve(enum_subset, required=required)
|
|
265
|
-
|
|
266
|
-
return err or PropertyError(
|
|
267
|
-
header="Cannot merge properties",
|
|
268
|
-
detail=f"{first.__class__}, {second.__class__}Properties have incompatible types",
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
|
|
272
222
|
def _resolve_naming_conflict(first: Property, second: Property, config: Config) -> PropertyError | None:
|
|
273
223
|
first.set_python_name(first.name, config=config, skip_snake_case=True)
|
|
274
224
|
second.set_python_name(second.name, config=config, skip_snake_case=True)
|
|
@@ -297,6 +247,7 @@ def _process_properties( # noqa: PLR0912, PLR0911
|
|
|
297
247
|
roots: set[ReferencePath | utils.ClassName],
|
|
298
248
|
) -> _PropertyData | PropertyError:
|
|
299
249
|
from . import property_from_data
|
|
250
|
+
from .merge_properties import merge_properties
|
|
300
251
|
|
|
301
252
|
properties: dict[str, Property] = {}
|
|
302
253
|
relative_imports: set[str] = set()
|
|
@@ -307,26 +258,26 @@ def _process_properties( # noqa: PLR0912, PLR0911
|
|
|
307
258
|
nonlocal properties
|
|
308
259
|
|
|
309
260
|
name_conflict = properties.get(new_prop.name)
|
|
310
|
-
|
|
311
|
-
if isinstance(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
)
|
|
315
|
-
return merged_prop_or_error
|
|
261
|
+
merged_prop = merge_properties(name_conflict, new_prop) if name_conflict else new_prop
|
|
262
|
+
if isinstance(merged_prop, PropertyError):
|
|
263
|
+
merged_prop.header = f"Found conflicting properties named {new_prop.name} when creating {class_name}"
|
|
264
|
+
return merged_prop
|
|
316
265
|
|
|
317
266
|
for other_prop in properties.values():
|
|
318
|
-
if other_prop.name ==
|
|
267
|
+
if other_prop.name == merged_prop.name:
|
|
319
268
|
continue # Same property, probably just got merged
|
|
320
|
-
if other_prop.python_name !=
|
|
269
|
+
if other_prop.python_name != merged_prop.python_name:
|
|
321
270
|
continue
|
|
322
|
-
naming_error = _resolve_naming_conflict(
|
|
271
|
+
naming_error = _resolve_naming_conflict(merged_prop, other_prop, config)
|
|
323
272
|
if naming_error is not None:
|
|
324
273
|
return naming_error
|
|
325
274
|
|
|
326
|
-
properties[
|
|
275
|
+
properties[merged_prop.name] = merged_prop
|
|
327
276
|
return None
|
|
328
277
|
|
|
329
|
-
unprocessed_props
|
|
278
|
+
unprocessed_props: list[tuple[str, oai.Reference | oai.Schema]] = (
|
|
279
|
+
list(data.properties.items()) if data.properties else []
|
|
280
|
+
)
|
|
330
281
|
for sub_prop in data.allOf:
|
|
331
282
|
if isinstance(sub_prop, oai.Reference):
|
|
332
283
|
ref_path = parse_reference_path(sub_prop.ref)
|
|
@@ -348,10 +299,10 @@ def _process_properties( # noqa: PLR0912, PLR0911
|
|
|
348
299
|
return err
|
|
349
300
|
schemas.add_dependencies(ref_path=ref_path, roots=roots)
|
|
350
301
|
else:
|
|
351
|
-
unprocessed_props.
|
|
302
|
+
unprocessed_props.extend(sub_prop.properties.items() if sub_prop.properties else [])
|
|
352
303
|
required_set.update(sub_prop.required or [])
|
|
353
304
|
|
|
354
|
-
for key, value in unprocessed_props
|
|
305
|
+
for key, value in unprocessed_props:
|
|
355
306
|
prop_required = key in required_set
|
|
356
307
|
prop_or_error: Property | (PropertyError | None)
|
|
357
308
|
prop_or_error, schemas = property_from_data(
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
__all__ = ["PropertyProtocol", "Value"]
|
|
4
4
|
|
|
5
5
|
from abc import abstractmethod
|
|
6
|
+
from dataclasses import dataclass
|
|
6
7
|
from typing import TYPE_CHECKING, Any, ClassVar, Protocol, TypeVar
|
|
7
8
|
|
|
8
9
|
from ... import Config
|
|
@@ -16,8 +17,15 @@ else:
|
|
|
16
17
|
ModelProperty = "ModelProperty"
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
@dataclass
|
|
21
|
+
class Value:
|
|
22
|
+
"""
|
|
23
|
+
Some literal values in OpenAPI documents (like defaults) have to be converted into Python code safely
|
|
24
|
+
(with string escaping, for example). We still keep the `raw_value` around for merging `allOf`.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
python_code: str
|
|
28
|
+
raw_value: Any
|
|
21
29
|
|
|
22
30
|
|
|
23
31
|
PropertyType = TypeVar("PropertyType", bound="PropertyProtocol")
|
|
@@ -148,7 +156,7 @@ class PropertyProtocol(Protocol):
|
|
|
148
156
|
"""How this should be declared in a dataclass"""
|
|
149
157
|
default: str | None
|
|
150
158
|
if self.default is not None:
|
|
151
|
-
default = self.default
|
|
159
|
+
default = self.default.python_code
|
|
152
160
|
elif not self.required:
|
|
153
161
|
default = "UNSET"
|
|
154
162
|
else:
|
|
@@ -162,7 +170,7 @@ class PropertyProtocol(Protocol):
|
|
|
162
170
|
"""Returns property docstring"""
|
|
163
171
|
doc = f"{self.python_name} ({self.get_type_string()}): {self.description or ''}"
|
|
164
172
|
if self.default:
|
|
165
|
-
doc += f" Default: {self.default}."
|
|
173
|
+
doc += f" Default: {self.default.python_code}."
|
|
166
174
|
if self.example:
|
|
167
175
|
doc += f" Example: {self.example}."
|
|
168
176
|
return doc
|
|
@@ -22,8 +22,10 @@ from ...utils import ClassName, PythonIdentifier
|
|
|
22
22
|
from ..errors import ParameterError, ParseError, PropertyError
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING: # pragma: no cover
|
|
25
|
+
from .model_property import ModelProperty
|
|
25
26
|
from .property import Property
|
|
26
27
|
else:
|
|
28
|
+
ModelProperty = "ModelProperty"
|
|
27
29
|
Property = "Property"
|
|
28
30
|
|
|
29
31
|
|
|
@@ -77,6 +79,7 @@ class Schemas:
|
|
|
77
79
|
classes_by_reference: Dict[ReferencePath, Property] = field(factory=dict)
|
|
78
80
|
dependencies: Dict[ReferencePath, Set[Union[ReferencePath, ClassName]]] = field(factory=dict)
|
|
79
81
|
classes_by_name: Dict[ClassName, Property] = field(factory=dict)
|
|
82
|
+
models_to_process: List[ModelProperty] = field(factory=list)
|
|
80
83
|
errors: List[ParseError] = field(factory=list)
|
|
81
84
|
|
|
82
85
|
def add_dependencies(self, ref_path: ReferencePath, roots: Set[Union[ReferencePath, ClassName]]) -> None:
|
|
@@ -21,8 +21,6 @@ class StringProperty(PropertyProtocol):
|
|
|
21
21
|
python_name: PythonIdentifier
|
|
22
22
|
description: str | None
|
|
23
23
|
example: str | None
|
|
24
|
-
max_length: int | None = None
|
|
25
|
-
pattern: str | None = None
|
|
26
24
|
_type_string: ClassVar[str] = "str"
|
|
27
25
|
_json_type_string: ClassVar[str] = "str"
|
|
28
26
|
_allowed_locations: ClassVar[set[oai.ParameterLocation]] = {
|
|
@@ -41,7 +39,6 @@ class StringProperty(PropertyProtocol):
|
|
|
41
39
|
python_name: PythonIdentifier,
|
|
42
40
|
description: str | None,
|
|
43
41
|
example: str | None,
|
|
44
|
-
pattern: str | None = None,
|
|
45
42
|
) -> StringProperty | PropertyError:
|
|
46
43
|
checked_default = cls.convert_value(default)
|
|
47
44
|
return cls(
|
|
@@ -51,7 +48,6 @@ class StringProperty(PropertyProtocol):
|
|
|
51
48
|
python_name=python_name,
|
|
52
49
|
description=description,
|
|
53
50
|
example=example,
|
|
54
|
-
pattern=pattern,
|
|
55
51
|
)
|
|
56
52
|
|
|
57
53
|
@classmethod
|
|
@@ -69,4 +65,4 @@ class StringProperty(PropertyProtocol):
|
|
|
69
65
|
return value
|
|
70
66
|
if not isinstance(value, str):
|
|
71
67
|
value = str(value)
|
|
72
|
-
return Value(repr(utils.remove_string_escapes(value)))
|
|
68
|
+
return Value(python_code=repr(utils.remove_string_escapes(value)), raw_value=value)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import Any, Dict, List, Optional, Union
|
|
2
2
|
|
|
3
|
-
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, model_validator
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr, model_validator
|
|
4
4
|
|
|
5
5
|
from ..data_type import DataType
|
|
6
6
|
from .discriminator import Discriminator
|
|
@@ -23,9 +23,9 @@ class Schema(BaseModel):
|
|
|
23
23
|
title: Optional[str] = None
|
|
24
24
|
multipleOf: Optional[float] = Field(default=None, gt=0.0)
|
|
25
25
|
maximum: Optional[float] = None
|
|
26
|
-
exclusiveMaximum: Optional[bool] = None
|
|
26
|
+
exclusiveMaximum: Optional[Union[bool, float]] = None
|
|
27
27
|
minimum: Optional[float] = None
|
|
28
|
-
exclusiveMinimum: Optional[bool] = None
|
|
28
|
+
exclusiveMinimum: Optional[Union[bool, float]] = None
|
|
29
29
|
maxLength: Optional[int] = Field(default=None, ge=0)
|
|
30
30
|
minLength: Optional[int] = Field(default=None, ge=0)
|
|
31
31
|
pattern: Optional[str] = None
|
|
@@ -36,7 +36,7 @@ class Schema(BaseModel):
|
|
|
36
36
|
minProperties: Optional[int] = Field(default=None, ge=0)
|
|
37
37
|
required: Optional[List[str]] = Field(default=None, min_length=1)
|
|
38
38
|
enum: Union[None, List[Any]] = Field(default=None, min_length=1)
|
|
39
|
-
const: Union[None, StrictStr, StrictInt] = None
|
|
39
|
+
const: Union[None, StrictStr, StrictInt, StrictFloat, StrictBool] = None
|
|
40
40
|
type: Union[DataType, List[DataType], None] = Field(default=None)
|
|
41
41
|
allOf: List[Union[Reference, "Schema"]] = Field(default_factory=list)
|
|
42
42
|
oneOf: List[Union[Reference, "Schema"]] = Field(default_factory=list)
|
|
@@ -160,6 +160,33 @@ class Schema(BaseModel):
|
|
|
160
160
|
},
|
|
161
161
|
)
|
|
162
162
|
|
|
163
|
+
@model_validator(mode="after")
|
|
164
|
+
def handle_exclusive_min_max(self) -> "Schema":
|
|
165
|
+
"""
|
|
166
|
+
Convert exclusiveMinimum/exclusiveMaximum between OpenAPI v3.0 (bool) and v3.1 (numeric).
|
|
167
|
+
"""
|
|
168
|
+
# Handle exclusiveMinimum
|
|
169
|
+
if isinstance(self.exclusiveMinimum, bool) and self.minimum is not None:
|
|
170
|
+
if self.exclusiveMinimum:
|
|
171
|
+
self.exclusiveMinimum = self.minimum
|
|
172
|
+
self.minimum = None
|
|
173
|
+
else:
|
|
174
|
+
self.exclusiveMinimum = None
|
|
175
|
+
elif isinstance(self.exclusiveMinimum, float):
|
|
176
|
+
self.minimum = None
|
|
177
|
+
|
|
178
|
+
# Handle exclusiveMaximum
|
|
179
|
+
if isinstance(self.exclusiveMaximum, bool) and self.maximum is not None:
|
|
180
|
+
if self.exclusiveMaximum:
|
|
181
|
+
self.exclusiveMaximum = self.maximum
|
|
182
|
+
self.maximum = None
|
|
183
|
+
else:
|
|
184
|
+
self.exclusiveMaximum = None
|
|
185
|
+
elif isinstance(self.exclusiveMaximum, float):
|
|
186
|
+
self.maximum = None
|
|
187
|
+
|
|
188
|
+
return self
|
|
189
|
+
|
|
163
190
|
@model_validator(mode="after")
|
|
164
191
|
def handle_nullable(self) -> "Schema":
|
|
165
192
|
"""Convert the old 3.0 `nullable` property into the new 3.1 style"""
|
|
@@ -138,6 +138,7 @@ return field_dict
|
|
|
138
138
|
{% for lazy_import in model.lazy_imports %}
|
|
139
139
|
{{ lazy_import }}
|
|
140
140
|
{% endfor %}
|
|
141
|
+
{% if (model.required_properties or model.optional_properties or model.additional_properties) %}
|
|
141
142
|
d = src_dict.copy()
|
|
142
143
|
{% for property in model.required_properties + model.optional_properties %}
|
|
143
144
|
{% if property.required %}
|
|
@@ -153,6 +154,7 @@ return field_dict
|
|
|
153
154
|
{% endif %}
|
|
154
155
|
|
|
155
156
|
{% endfor %}
|
|
157
|
+
{% endif %}
|
|
156
158
|
{{ module_name }} = cls(
|
|
157
159
|
{% for property in model.required_properties + model.optional_properties %}
|
|
158
160
|
{{ property.python_name }}={{ property.python_name }},
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{% macro construct(property, source) %}
|
|
2
|
+
{{ property.python_name }} = cast({{ property.get_type_string() }} , {{ source }})
|
|
3
|
+
if {{ property.python_name }} != {{ property.value.python_code }}{% if not property.required %}and not isinstance({{ property.python_name }}, Unset){% endif %}:
|
|
4
|
+
raise ValueError(f"{{ property.name }} must match const {{ property.value.python_code }}, got '{{'{' + property.python_name + '}' }}'")
|
|
5
|
+
{%- endmacro %}
|
|
@@ -18,7 +18,7 @@ dependencies = [
|
|
|
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.5"
|
|
22
22
|
description = "Generate modern Python clients from OpenAPI"
|
|
23
23
|
keywords = [
|
|
24
24
|
"OpenAPI",
|
|
@@ -63,9 +63,10 @@ ignore = ["E501", "PLR0913"]
|
|
|
63
63
|
|
|
64
64
|
[tool.ruff.lint.per-file-ignores]
|
|
65
65
|
"openapi_python_client/cli.py" = ["B008"]
|
|
66
|
+
"tests/*" = ["PLR2004"]
|
|
66
67
|
|
|
67
68
|
[tool.coverage.run]
|
|
68
|
-
omit = ["openapi_python_client/templates/*"]
|
|
69
|
+
omit = ["openapi_python_client/__main__.py", "openapi_python_client/templates/*"]
|
|
69
70
|
|
|
70
71
|
[tool.mypy]
|
|
71
72
|
plugins = ["pydantic.mypy"]
|
|
@@ -129,7 +130,7 @@ composite = ["test --cov openapi_python_client tests --cov-report=term-missing"]
|
|
|
129
130
|
|
|
130
131
|
[tool.pdm.scripts.regen_integration]
|
|
131
132
|
shell = """
|
|
132
|
-
openapi-python-client
|
|
133
|
+
openapi-python-client generate --overwrite --url https://raw.githubusercontent.com/openapi-generators/openapi-test-server/main/openapi.json --config integration-tests/config.yaml --meta none --output-path integration-tests/integration_tests \
|
|
133
134
|
"""
|
|
134
135
|
|
|
135
136
|
[build-system]
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
{% macro construct(property, source) %}
|
|
2
|
-
{{ property.python_name }} = cast({{ property.get_type_string() }} , {{ source }})
|
|
3
|
-
if {{ property.python_name }} != {{ property.value }}{% if not property.required %}and not isinstance({{ property.python_name }}, Unset){% endif %}:
|
|
4
|
-
raise ValueError(f"{{ property.name }} must match const {{ property.value }}, got '{{'{' + property.python_name + '}' }}'")
|
|
5
|
-
{%- endmacro %}
|
|
File without changes
|
|
File without changes
|
{openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/__init__.py
RENAMED
|
File without changes
|
{openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/config.py
RENAMED
|
File without changes
|
|
File without changes
|
{openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/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
|
{openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/py.typed
RENAMED
|
File without changes
|
{openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/schema/3.0.3.md
RENAMED
|
File without changes
|
{openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/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
|
|
File without changes
|
{openapi_python_client-0.21.3 → openapi_python_client-0.21.5}/openapi_python_client/utils.py
RENAMED
|
File without changes
|