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

Fix openapi_types generation error in Python Flask's models #1256

Merged
merged 1 commit into from
Nov 15, 2018

Conversation

lspvic
Copy link
Contributor

@lspvic lspvic commented Oct 17, 2018

PR checklist

  • Read the contribution guidelines.
  • Ran the shell script under ./bin/ to update Petstore sample so that CIs can verify the change. (For instance, only need to run ./bin/{LANG}-petstore.sh and ./bin/security/{LANG}-petstore.sh if updating the {LANG} (e.g. php, ruby, python, etc) code generator or {LANG} client's mustache templates). Windows batch files can be found in .\bin\windows\.
  • Filed the PR against the correct branch: master, 3.4.x, 4.0.x. Default: master.
  • Copied the technical committee to review the pull request if your PR is targeting a particular programming language.

Description of the PR

The generate model attribute openapi_types should be a dict map to data type(str, int) or typing List[str], not the string of them ('str', 'int' or 'List[str]')

Currently, generated codes are like those:

        self.openapi_types = {
            'answer': 'str',
            'status': 'str'
        }

@taxpon (2017/07) @frol (2017/07) @mbohlool (2017/07) @cbornet (2017/09) @kenjones-cisco (2017/11)

@wing328
Copy link
Member

wing328 commented Oct 17, 2018

@lspvic thanks for the PR. What issue did you run into when having the value as string?

I believe openapi_types is for deserialization (from JSON back to Python object)

@lspvic
Copy link
Contributor Author

lspvic commented Oct 17, 2018

Look at the deserialization functions in the file

def deserialize_model(data, klass):
    """Deserializes list or dict to model.

    :param data: dict, list.
    :type data: dict | list
    :param klass: class literal.
    :return: model object.
    """
    instance = klass()    # kclass must not be a str, it should be a type

    if not instance.openapi_types:
        return data

    for attr, attr_type in six.iteritems(instance.openapi_types):
        # attr_type is from openapi_types
        if data is not None \
                and instance.attribute_map[attr] in data \
                and isinstance(data, (list, dict)):
            value = data[instance.attribute_map[attr]]
            setattr(instance, attr, _deserialize(value, attr_type))
            # attr_type passes to _deserialize

    return instance

def _deserialize(data, klass):
    """Deserializes dict, list, str into an object.

    :param data: dict, list or str.
    :param klass: class literal, or string of class name.

    :return: object.
    """
    if data is None:
        return None

    # if kclass is a str, none of the conditions satisfied and it finally goes to else block
    if klass in six.integer_types or klass in (float, str, bool):
        return _deserialize_primitive(data, klass)
    elif klass == object:
        return _deserialize_object(data)
    elif klass == datetime.date:
        return deserialize_date(data)
    elif klass == datetime.datetime:
        return deserialize_datetime(data)
    elif type(klass) == typing.GenericMeta:
        if klass.__extra__ == list:
            return _deserialize_list(data, klass.__args__[0])
        if klass.__extra__ == dict:
            return _deserialize_dict(data, klass.__args__[1])
    else:
        return deserialize_model(data, klass)

@wing328
Copy link
Member

wing328 commented Nov 4, 2018

@lspvic what error message did you get? we do have tests to cover the deserialization, e.g. https://github.com/OpenAPITools/openapi-generator/blob/master/samples/client/petstore/python/tests/test_pet_api.py#L186

@lspvic
Copy link
Contributor Author

lspvic commented Nov 4, 2018

@wing328 The error is for generated server code, flaskConnexion , The test in the generated server code doesn't have called any deserialization, It just called the controller method only returned 'do some magic', e.g,

https://github.com/OpenAPITools/openapi-generator/blob/master/samples/server/petstore/flaskConnexion/openapi_server/test/test_store_controller.py

I am really quite sure that's an error.

@wing328
Copy link
Member

wing328 commented Nov 4, 2018

@lspvic ah ok. Sorry I misunderstood it's an issue with the Python client. Let me review your change again.

@cbornet
Copy link
Member

cbornet commented Nov 4, 2018

FYI, this change was introduced by #946 . I think it has been done to prevent circular imports but if because of this deserialization doesn't work, it means the fix was bad...

@rparini
Copy link
Contributor

rparini commented Nov 7, 2018

I ran into this issue today so I can give an example of the error. Using openapi-generator 3.3.2, I have the generated Model InlineObject1 whose __init__ looks like this:

class InlineObject1(Model):
    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

    Do not edit the class manually.
    """

    def __init__(self, name=None):  # noqa: E501
        """InlineObject1 - a model defined in OpenAPI

        :param name: The name of this InlineObject1.  # noqa: E501
        :type name: str
        """
        self.openapi_types = {
            'name': 'str'
        }

        self.attribute_map = {
            'name': 'name'
        }

        self._name = name

and in default_controller.py I have

def data_post(inline_object1=None):  # noqa: E501
    """
    :param inline_object1: 
    :type inline_object1: dict | bytes

    :rtype: str
    """
    if connexion.request.is_json:
        inline_object1 = InlineObject1.from_dict(connexion.request.get_json())  # noqa: E501
    return 200

Then in my tests I have

    def test_data_post(self):
        """Test case for data_post
        """
        inline_object1 = InlineObject1(name='testName')
        
        response = self.client.open(
            '/v0/data',
            method='POST',
            data=json.dumps(inline_object1),
            content_type='application/json')

        self.assert200(response, 'Response body is : ' + response.data.decode('utf-8'))

While getting the response during the test I get the error

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/usr/local/lib/python3.6/site-packages/connexion/decorators/decorator.py", line 73, in wrapper
    response = function(request)
  File "/usr/local/lib/python3.6/site-packages/connexion/decorators/uri_parsing.py", line 132, in wrapper
    response = function(request)
  File "/usr/local/lib/python3.6/site-packages/connexion/decorators/validation.py", line 165, in wrapper
    response = function(request)
  File "/usr/local/lib/python3.6/site-packages/connexion/decorators/decorator.py", line 44, in wrapper
    response = function(request)
  File "/usr/local/lib/python3.6/site-packages/connexion/decorators/parameter.py", line 126, in wrapper
    return function(**kwargs)
  File "/usr/src/app/apiserver/controllers/default_controller.py", line 468, in data_post
    inline_object1 = InlineObject1.from_dict(connexion.request.get_json())  # noqa: E501
  File "/usr/src/app/apiserver/models/inline_object1.py", line 53, in from_dict
    return util.deserialize_model(dikt, cls)
  File "/usr/src/app/apiserver/util.py", line 116, in deserialize_model
    setattr(instance, attr, _deserialize(value, attr_type))
  File "/usr/src/app/apiserver/util.py", line 32, in _deserialize
    return deserialize_model(data, klass)
  File "/usr/src/app/apiserver/util.py", line 106, in deserialize_model
    instance = klass()
TypeError: 'str' object is not callable

Which, as @lspvic describes, is because klass in the deserialize_model function is a string with the value 'str' rather than the python type str (ie type(klass) is <class 'str'> but should be <class 'type'>). Changing self.openapi_types in InlineObject1.__init__ to map to str fixed this issue for me

        self.openapi_types = {
            'name': str
        }

@wing328 wing328 added this to the 3.3.4 milestone Nov 15, 2018
@wing328
Copy link
Member

wing328 commented Nov 15, 2018

@lspvic thanks for the fix and sorry for taking a bit long to review.

@rparini thanks for testing the change.

@wing328 wing328 merged commit dc3a3dd into OpenAPITools:master Nov 15, 2018
@wing328 wing328 changed the title Fix openapi_types generation error Fix openapi_types generation error In Python Flask's models Nov 15, 2018
@wing328 wing328 changed the title Fix openapi_types generation error In Python Flask's models Fix openapi_types generation error in Python Flask's models Nov 15, 2018
@cbornet
Copy link
Member

cbornet commented Nov 15, 2018

Yes, but now, don't you have missing imports for nested models ?

@cbornet
Copy link
Member

cbornet commented Nov 15, 2018

Eg:

        self.openapi_types = {
            'foo': Foo
        }

You would need to import models.Foo

@cbornet
Copy link
Member

cbornet commented Nov 15, 2018

And if you import yourself, you get the circular import issue which resulted in the wrong fix in the beginning

@wing328
Copy link
Member

wing328 commented Nov 16, 2018

@cbornet please open an issue to track it. I think I can come up with a solution to fix the circular import.

@wing328
Copy link
Member

wing328 commented Dec 4, 2018

@lspvic thanks for the PR, which has been included in the v3.3.4 release: https://twitter.com/oas_generator/status/1068772409795207168

@ericraymond
Copy link

Yes, but now, don't you have missing imports for nested models ?

Running into the missing imports issue in 3.3.4. Is this tracked in an issue? Resolved?

@wing328
Copy link
Member

wing328 commented Dec 23, 2018

@ericraymond please open an issue for tracking.

@wing328
Copy link
Member

wing328 commented Dec 27, 2018

FYI. #1758 has been merged into master to avoid self-reference import.

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

Successfully merging this pull request may close these issues.

5 participants