Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG][PYTHON] adding a requirement on a sibling field in an allOf throws AttributeError on import #13676

Closed
5 of 6 tasks
vincent-raman opened this issue Oct 12, 2022 · 8 comments · Fixed by #13790
Closed
5 of 6 tasks

Comments

@vincent-raman
Copy link

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

Adding a requirement on a sibling field in an allOf breaks the model import

openapi-generator version

6.2.0

OpenAPI declaration file content or url
openapi: 3.0.0
info:
  title: test
  version: 1.0.0
paths:
  /:
    get:
      responses:
        200:
          description: "OK"
components:
  schemas:
    Test:
      type: object
      properties:
        test:
          type: string
    TestRequired:
      allOf:
        - $ref: '#/components/schemas/Test'
        - type: object
          required: [test]
          properties:
            name:
              type: string
Generation Details

openapi-generator-cli generate -g python -i test.yaml -o out

Steps to reproduce

Generate the client then try to import the generated "TestRequired" class. It will fail with an Error AttributeError: type object 'properties' has no attribute 'test'

The generated class is the following

# coding: utf-8

"""
    test

    No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)  # noqa: E501

    The version of the OpenAPI document: 1.0.0
    Generated by: https://openapi-generator.tech
"""

from datetime import date, datetime  # noqa: F401
import decimal  # noqa: F401
import functools  # noqa: F401
import io  # noqa: F401
import re  # noqa: F401
import typing  # noqa: F401
import typing_extensions  # noqa: F401
import uuid  # noqa: F401

import frozendict  # noqa: F401

from openapi_client import schemas  # noqa: F401


class TestRequired(
    schemas.ComposedSchema,
):
    """NOTE: This class is auto generated by OpenAPI Generator.
    Ref: https://openapi-generator.tech

    Do not edit the class manually.
    """


    class MetaOapg:
        
        
        class all_of_1(
            schemas.DictSchema
        ):
        
        
            class MetaOapg:
                required = {
                    "test",
                }
                
                class properties:
                    name = schemas.StrSchema
                    __annotations__ = {
                        "name": name,
                    }
            
            test: schemas.AnyTypeSchema
            
            @typing.overload
            def __getitem__(self, name: typing_extensions.Literal["name"]) -> MetaOapg.properties.name: ...
            
            @typing.overload
            def __getitem__(self, name: str) -> schemas.UnsetAnyTypeSchema: ...
            
            def __getitem__(self, name: typing.Union[typing_extensions.Literal["name", ], str]):
                # dict_instance[name] accessor
                return super().__getitem__(name)
            
            
            @typing.overload
            def get_item_oapg(self, name: typing_extensions.Literal["name"]) -> typing.Union[MetaOapg.properties.name, schemas.Unset]: ...
            
            @typing.overload
            def get_item_oapg(self, name: str) -> typing.Union[schemas.UnsetAnyTypeSchema, schemas.Unset]: ...
            
            def get_item_oapg(self, name: typing.Union[typing_extensions.Literal["name", ], str]):
                return super().get_item_oapg(name)
            
        
            def __new__(
                cls,
                *args: typing.Union[dict, frozendict.frozendict, ],
                test: typing.Union[MetaOapg.properties.test, dict, frozendict.frozendict, str, date, datetime, uuid.UUID, int, float, decimal.Decimal, bool, None, list, tuple, bytes, io.FileIO, io.BufferedReader, ],
                name: typing.Union[MetaOapg.properties.name, str, schemas.Unset] = schemas.unset,
                _configuration: typing.Optional[schemas.Configuration] = None,
                **kwargs: typing.Union[schemas.AnyTypeSchema, dict, frozendict.frozendict, str, date, datetime, uuid.UUID, int, float, decimal.Decimal, None, list, tuple, bytes],
            ) -> 'all_of_1':
                return super().__new__(
                    cls,
                    *args,
                    test=test,
                    name=name,
                    _configuration=_configuration,
                    **kwargs,
                )
        
        @classmethod
        @functools.lru_cache()
        def all_of(cls):
            # we need this here to make our import statements work
            # we must store _composed_schemas in here so the code is only run
            # when we invoke this method. If we kept this at the class
            # level we would get an error because the class level
            # code would be run when this module is imported, and these composed
            # classes don't exist yet because their module has not finished
            # loading
            return [
                Test,
                cls.all_of_1,
            ]


    def __new__(
        cls,
        *args: typing.Union[dict, frozendict.frozendict, str, date, datetime, uuid.UUID, int, float, decimal.Decimal, bool, None, list, tuple, bytes, io.FileIO, io.BufferedReader, ],
        _configuration: typing.Optional[schemas.Configuration] = None,
        **kwargs: typing.Union[schemas.AnyTypeSchema, dict, frozendict.frozendict, str, date, datetime, uuid.UUID, int, float, decimal.Decimal, None, list, tuple, bytes],
    ) -> 'TestRequired':
        return super().__new__(
            cls,
            *args,
            _configuration=_configuration,
            **kwargs,
        )

from openapi_client.model.test import Test

the constructor of all_of_1 expects a required test argument whose typing is based on properties which doesn't contain it.

Suggest a fix

Add typing for required parameters not existing in properties as AnyTypeSchema

@spacether
Copy link
Contributor

Thanks for reporting this
I will work on a fix for this

@spacether
Copy link
Contributor

Draft fix is being worked in openapi-json-schema-tools/openapi-json-schema-generator#44

@spacether
Copy link
Contributor

spacether commented Oct 18, 2022

This was fixed in the separate repo in openapi-json-schema-tools/openapi-json-schema-generator#44. You can use it from the latest release there if you want.

Here, I will update the python generator in this repo to include this fix before the 6.2.1 release.

@tnielens
Copy link

What is the relation between this project and openapi-json-schema-generator?

@wing328
Copy link
Member

wing328 commented Oct 19, 2022

@tnielens please refer to #13501 (comment) for more information.

@spacether
Copy link
Contributor

@vincent-raman is there a regression with my fix?

@spacether
Copy link
Contributor

spacether commented Oct 20, 2022

@wing328 this is a corner case that will probably not work in most other generators because this required property has no property definition so it will not be in required vars. It is handlable if the generators use requiredVarsMap that I made and python uses.
That property was added in #13117

@vincent-raman
Copy link
Author

@vincent-raman is there a regression with my fix?

No, that was a mistake, I removed my comment, sorry. It seems working pretty well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants