diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index dc6e9f47b..e9cb9d4b8 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -384,6 +384,7 @@ def add_parameters( # noqa: PLR0911, PLR0912 prop, new_schemas = property_from_data( name=param.name, + description=param.description, required=param.required, data=param.param_schema, schemas=schemas, @@ -567,7 +568,12 @@ def from_dict(data: Dict[str, Any], *, config: Config) -> Union["GeneratorData", schemas = Schemas() parameters = Parameters() if openapi.components and openapi.components.schemas: - schemas = build_schemas(components=openapi.components.schemas, schemas=schemas, config=config) + schemas = build_schemas( + input_schemas=openapi.components.schemas, + input_responses=openapi.components.responses, + schemas=schemas, + config=config, + ) if openapi.components and openapi.components.parameters: parameters = build_parameters( components=openapi.components.parameters, parameters=parameters, config=config diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index f434bca61..c74d22de4 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -299,7 +299,7 @@ def get_lazy_imports(self, *, prefix: str) -> Set[str]: def _string_based_property( - name: str, required: bool, data: oai.Schema, config: Config + name: str, description: Optional[str], required: bool, data: oai.Schema, config: Config ) -> Union[StringProperty, DateProperty, DateTimeProperty, FileProperty]: """Construct a Property from the type "string" """ string_format = data.schema_format @@ -311,7 +311,7 @@ def _string_based_property( default=convert("datetime.datetime", data.default), nullable=data.nullable, python_name=python_name, - description=data.description, + description=description or data.description, example=data.example, ) if string_format == "date": @@ -321,7 +321,7 @@ def _string_based_property( default=convert("datetime.date", data.default), nullable=data.nullable, python_name=python_name, - description=data.description, + description=description or data.description, example=data.example, ) if string_format == "binary": @@ -330,7 +330,7 @@ def _string_based_property( required=required, default=None, nullable=data.nullable, - python_name=python_name, + python_name=description or python_name, description=data.description, example=data.example, ) @@ -341,7 +341,7 @@ def _string_based_property( pattern=data.pattern, nullable=data.nullable, python_name=python_name, - description=data.description, + description=description or data.description, example=data.example, ) @@ -463,7 +463,14 @@ def get_enum_default(prop: EnumProperty, data: oai.Schema) -> Union[Optional[str def build_union_property( - *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str, config: Config + *, + data: oai.Schema, + name: str, + description: Optional[str], + required: bool, + schemas: Schemas, + parent_name: str, + config: Config, ) -> Tuple[Union[UnionProperty, PropertyError], Schemas]: """ Create a `UnionProperty` the right way. @@ -485,6 +492,7 @@ def build_union_property( for i, sub_prop_data in enumerate(chain(data.anyOf, data.oneOf)): sub_prop, schemas = property_from_data( name=f"{name}_type_{i}", + description=description, required=required, data=sub_prop_data, schemas=schemas, @@ -504,7 +512,7 @@ def build_union_property( inner_properties=sub_properties, nullable=data.nullable, python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), - description=data.description, + description=description or data.description, example=data.example, ), schemas, @@ -515,6 +523,7 @@ def build_list_property( *, data: oai.Schema, name: str, + description: Optional[str], required: bool, schemas: Schemas, parent_name: str, @@ -541,6 +550,7 @@ def build_list_property( return PropertyError(data=data, detail="type array must have items defined"), schemas inner_prop, schemas = property_from_data( name=f"{name}_item", + description=description, required=True, data=data.items, schemas=schemas, @@ -560,7 +570,7 @@ def build_list_property( inner_property=inner_prop, nullable=data.nullable, python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), - description=data.description, + description=description or data.description, example=data.example, ), schemas, @@ -569,6 +579,7 @@ def build_list_property( def _property_from_ref( name: str, + description: Optional[str], required: bool, parent: Union[oai.Schema, None], data: oai.Reference, @@ -589,6 +600,8 @@ def _property_from_ref( name=name, python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), ) + if description: + prop = evolve(prop, description=description) if parent: prop = evolve(prop, nullable=parent.nullable) if isinstance(prop, EnumProperty): @@ -603,6 +616,7 @@ def _property_from_ref( def _property_from_data( # noqa: PLR0911 name: str, + description: Optional[str], required: bool, data: Union[oai.Reference, oai.Schema], schemas: Schemas, @@ -615,14 +629,28 @@ def _property_from_data( # noqa: PLR0911 name = utils.remove_string_escapes(name) if isinstance(data, oai.Reference): return _property_from_ref( - name=name, required=required, parent=None, data=data, schemas=schemas, config=config, roots=roots + name=name, + description=description, + required=required, + parent=None, + data=data, + schemas=schemas, + config=config, + roots=roots, ) sub_data: List[Union[oai.Schema, oai.Reference]] = data.allOf + data.anyOf + data.oneOf # A union of a single reference should just be passed through to that reference (don't create copy class) if len(sub_data) == 1 and isinstance(sub_data[0], oai.Reference): return _property_from_ref( - name=name, required=required, parent=data, data=sub_data[0], schemas=schemas, config=config, roots=roots + name=name, + description=description, + required=required, + parent=data, + data=sub_data[0], + schemas=schemas, + config=config, + roots=roots, ) if data.enum: @@ -637,10 +665,19 @@ def _property_from_data( # noqa: PLR0911 ) if data.anyOf or data.oneOf: return build_union_property( - data=data, name=name, required=required, schemas=schemas, parent_name=parent_name, config=config + data=data, + name=name, + description=description, + required=required, + schemas=schemas, + parent_name=parent_name, + config=config, ) if data.type == oai.DataType.STRING: - return _string_based_property(name=name, required=required, data=data, config=config), schemas + return ( + _string_based_property(name=name, description=description, required=required, data=data, config=config), + schemas, + ) if data.type == oai.DataType.NUMBER: return ( FloatProperty( @@ -649,7 +686,7 @@ def _property_from_data( # noqa: PLR0911 required=required, nullable=data.nullable, python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), - description=data.description, + description=description or data.description, example=data.example, ), schemas, @@ -662,7 +699,7 @@ def _property_from_data( # noqa: PLR0911 required=required, nullable=data.nullable, python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), - description=data.description, + description=description or data.description, example=data.example, ), schemas, @@ -675,7 +712,7 @@ def _property_from_data( # noqa: PLR0911 default=convert("bool", data.default), nullable=data.nullable, python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), - description=data.description, + description=description or data.description, example=data.example, ), schemas, @@ -684,6 +721,7 @@ def _property_from_data( # noqa: PLR0911 return build_list_property( data=data, name=name, + description=description, required=required, schemas=schemas, parent_name=parent_name, @@ -695,6 +733,7 @@ def _property_from_data( # noqa: PLR0911 return build_model_property( data=data, name=name, + description=description, schemas=schemas, required=required, parent_name=parent_name, @@ -709,7 +748,7 @@ def _property_from_data( # noqa: PLR0911 nullable=False, default=None, python_name=utils.PythonIdentifier(value=name, prefix=config.field_prefix), - description=data.description, + description=description or data.description, example=data.example, ), schemas, @@ -726,6 +765,7 @@ def property_from_data( config: Config, process_properties: bool = True, roots: Optional[Set[Union[ReferencePath, utils.ClassName]]] = None, + description: Optional[str] = None, ) -> Tuple[Union[Property, PropertyError], Schemas]: """ Build a Property from an OpenAPI schema or reference. This Property represents a single input or output for a @@ -757,6 +797,7 @@ def property_from_data( try: return _property_from_data( name=name, + description=description, required=required, data=data, schemas=schemas, @@ -770,9 +811,12 @@ def property_from_data( def _create_schemas( - *, components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas, config: Config + *, + input_schemas: Dict[str, Union[oai.Reference, oai.Schema]], + schemas: Schemas, + config: Config, ) -> Schemas: - to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = components.items() + to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = input_schemas.items() still_making_progress = True errors: List[PropertyError] = [] @@ -803,6 +847,32 @@ def _create_schemas( return schemas +def _process_responses( + *, + input_responses: Dict[str, oai.Response], + schemas: Schemas, + config: Config, +) -> Schemas: + for name, data in input_responses.items(): + if not isinstance(data, oai.Response): + schemas.errors.append(PropertyError(data=data, detail="Only reference schemas are supported.")) + continue + + schema_ref_path = parse_reference_path(f"#/components/schemas/{name}") + if isinstance(schema_ref_path, ParseError): + schemas.errors.append(PropertyError(detail=schema_ref_path.detail, data=data)) + continue + response_ref_path = parse_reference_path(f"#/components/responses/{name}") + if isinstance(response_ref_path, ParseError): + schemas.errors.append(PropertyError(detail=response_ref_path.detail, data=data)) + continue + + prop = schemas.classes_by_reference.get(schema_ref_path) + if prop: + schemas = evolve(schemas, classes_by_reference={response_ref_path: prop, **schemas.classes_by_reference}) + return schemas + + def _propogate_removal(*, root: Union[ReferencePath, utils.ClassName], schemas: Schemas, error: PropertyError) -> None: if isinstance(root, utils.ClassName): schemas.classes_by_name.pop(root, None) @@ -863,10 +933,16 @@ def _process_models(*, schemas: Schemas, config: Config) -> Schemas: def build_schemas( - *, components: Dict[str, Union[oai.Reference, oai.Schema]], schemas: Schemas, config: Config + *, + input_schemas: Dict[str, Union[oai.Reference, oai.Schema]], + input_responses: Optional[Dict[str, Union[oai.Reference, oai.Schema]]], + schemas: Schemas, + config: Config, ) -> Schemas: """Get a list of Schemas from an OpenAPI dict""" - schemas = _create_schemas(components=components, schemas=schemas, config=config) + schemas = _create_schemas(input_schemas=input_schemas, schemas=schemas, config=config) + if input_responses: + schemas = _process_responses(input_responses=input_responses, schemas=schemas, config=config) schemas = _process_models(schemas=schemas, config=config) return schemas diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 28fb00b29..57c95121e 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -1,7 +1,7 @@ from __future__ import annotations from itertools import chain -from typing import ClassVar, NamedTuple +from typing import ClassVar, NamedTuple, Optional from attrs import define, evolve @@ -367,6 +367,7 @@ def build_model_property( *, data: oai.Schema, name: str, + description: Optional[str], schemas: Schemas, required: bool, parent_name: str | None, @@ -428,7 +429,7 @@ def build_model_property( relative_imports=relative_imports, lazy_imports=lazy_imports, additional_properties=additional_properties, - description=data.description or "", + description=description or data.description or "", default=None, nullable=data.nullable, required=required, diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index 046c0ca48..f7f3990df 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -161,6 +161,7 @@ def parameter_from_data( new_param = Parameter( name=name, + description=data.description, required=data.required, explode=data.explode, style=data.style, diff --git a/openapi_python_client/parser/responses.py b/openapi_python_client/parser/responses.py index 2b41eac8d..b0c709208 100644 --- a/openapi_python_client/parser/responses.py +++ b/openapi_python_client/parser/responses.py @@ -69,11 +69,20 @@ def response_from_data( response_name = f"response_{status_code}" if isinstance(data, oai.Reference): - return ( - empty_response(status_code=status_code, response_name=response_name, config=config, description=None), - schemas, + prop, schemas = property_from_data( + name=response_name, + required=True, + data=data, + schemas=schemas, + parent_name=parent_name, + config=config, ) + if isinstance(prop, PropertyError): + return prop, schemas + + return Response(status_code=status_code, prop=prop, source=None), schemas + content = data.content if not content: return ( diff --git a/pyproject.toml b/pyproject.toml index 2413ae48a..6905d59fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openapi-python-client" -version = "0.15.2" +version = "0.15.3" description = "Generate modern Python clients from OpenAPI" repository = "https://github.com/triaxtec/openapi-python-client" license = "MIT"