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] Generated client breaks on to_dict() method #10186

Closed
5 of 6 tasks
valmoz opened this issue Aug 18, 2021 · 7 comments · Fixed by #11234
Closed
5 of 6 tasks

[BUG] [PYTHON] Generated client breaks on to_dict() method #10186

valmoz opened this issue Aug 18, 2021 · 7 comments · Fixed by #11234

Comments

@valmoz
Copy link
Contributor

valmoz commented Aug 18, 2021

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

The .to_dict() function causes "AttributeError: 'dict' object has no attribute" error.

openapi-generator version

5.2.1

OpenAPI declaration file content or url

I used $ref with external files:
https://gist.github.com/Valmoz/16503c5ce996d27982722959733b6955
the paths are:

  • openapi.yaml
  • models/responses/UserInfoResponse.yaml
  • models/schemas/User.yaml
Generation Details

openapi-generator-cli generate -i openapi.yaml -g python -o generated/openapi/python/

Steps to reproduce

I tried to use the generated client from a dedicated python script:

from openapi_client.model.user_info_response import UserInfoResponse
from openapi_client.api import user_api

configuration = openapi_client.Configuration(
    host = 'http://api-v2.example.it'
)
configuration.access_token = access_token

# Enter a context with an instance of the API client
with openapi_client.ApiClient(configuration) as api_client:
    # Create an instance of the API class
    api_instance = user_api.UserApi(api_client)
    
    try:
        # Get User Info
        api_response: UserInfoResponse = api_instance.get_user_info()
        return api_response.to_dict()
    except openapi_client.ApiException as e:
        print("Exception when calling UserApi->get_user_info: %s\n" % e)

When this code is executed, (more precisely: the to_dict() function) I obtain this error:
AttributeError: 'dict' object has no attribute '_composed_schemas'

Manually modifying the generated model_utils.py file at line 1634 (see next session), I was able to fix this error, but then I obtained another one:
AttributeError: 'dict' object has no attribute '_data_store'

Modifying also line 1640, I am able to retrieve:

{
  "data": {
    "email": "[email protected]", 
    "first_name": "Matteo", 
    "hash": "44444", 
    "id": 2, 
    "last_name": "Rossi", 
    "name": "Matteo Rossi", 
    "picture": null
  }, 
  "email_confirmation_state": {
    "need_confirmation": false
  }, 
  "info": {
    "need_marketing_consents_confirmation": false, 
    "need_password_change": false, 
    "need_terms_of_service_confirmation": false
  }
}
Related issues/PRs

I didn't find any...

Suggest a fix

it seems possible to avoid the issues checking if model_instance contains the missing keys.

I modified the generated model_utils.py line 1634 as follows:

if '_composed_schemas' in model_instance and model_instance._composed_schemas:
    ...

For the second error, I wrapped the for statement at line 1640 in an if statement as follows:

if '_data_store' in model_instance:
    for attr, value in model_instance._data_store.items():
        ...

N.B.: I also noticed that lines 1652-1664 have been indented with one space less than the rest of the file (3 instead of 4)...

@valmoz
Copy link
Contributor Author

valmoz commented Aug 18, 2021

I think that it should be enough to modify the following file:
modules/openapi-generator/src/main/resources/python/model_utils.mustache lines 1336-1342

@spacether
Copy link
Contributor

We welcome PRs solving this issue.
The source line mentioned in this issue no longer exists. My guess is that the author was writing about the model_to_dict method. One should loop over the composed instances if they exist because they contain information on how to go from python naming back to json naming. If we just grabbed one of those composed instances _data_store it would contain all of the key value pairs but the model would not know how to convert all of those keys back into the json named context.
Also, it looks like our code handles dicts in this case:

So it's unclear if this issue still exists.
If anyone has this issue, please submit a full working spec. The above example lacks the needed schemas to prove this issue.

@bflaton
Copy link
Contributor

bflaton commented Sep 28, 2021

OK, so I faced the issue this week, but I'm still investigating if the .yml i used as input for the code generator was well designed, or if the generator is really the one to blame. I'll leave another message as soon as I have figured that out, but in the meantime I'll give some feedback based on analysis of the generated code:

  • at first it seemed like a good idea to check for existence of an attribute, before accessing it (the error mentioned in this issue report indicates that the generated code tries to access the _composed_schemas attribute, while it doesn't exist
  • however, when digging a bit deeper, the _composed_schemas attribute should exist for any model that is not a primitive type, or a SimpleModel. So, it could very well be that no checks should be implemented, but rather the control logic in model_utils.py should be improved.

For now, I'll first try to rule out .yml issues (shit in, shit out rule always applies, right ;-))

@spacether
Copy link
Contributor

spacether commented Oct 4, 2021

_composed_schemas attribute should exist for any model that is not a primitive type, or a SimpleModel

I don't think that's true. My understanding is that _composed_schemas only exists in composed schema instances. It does not exist for ModelNormal instances (object based models).

@bflaton
Copy link
Contributor

bflaton commented Oct 4, 2021

I decided to try and produce some sort of failing test first (in the openapi generator python test cases). My long shot would be to call a to_dict() in one of the existing test cases that should cover the more "complex" datatypes produced by the code generator. However, a lot of the test cases I've found actually have empty bodies (e.g. in test_mixed_properties_and_additional_properties_class.py, test_grandparent_animal.py, test_parent_pet.py, etc). When I take some code from the archive history, I cannot seem to get that working (something to do with a raised Error about a missing discriminator).

This makes it quite hard for me to judge if the flow described in the original ticket (calling to_dict() on some response) is actually covered in an existing test case or not. Maybe I should be looking at the more "integration level" test cases. I.e. the ones that mimic actual client-server interactions. I'll try that next.

@felixvd
Copy link

felixvd commented Nov 29, 2021

I am running into this issue and starting to debug it. My current assumption is that inline objects that are not defined at the top level (but instead at a lower level of the tree) have no model generated for them, and then become dicts instead of ModelComposed objects.

I am writing the OpenAPI spec for an existing API, and when I define parts of the response I receive from the server simply as type: object, the error seems to appear. If I write a schema for that part and link it, it disappears (or rather it reappears for a different undefined object). I assume that this happens because the part of the response that previously caused the error is parsed as a ModelComposed when I write it out as a schema, whereas before there was no Model for it.

That's my working assumption anyway.

edit: I worked around it by completing our schema, so won't have cycles to debug this more.

@spacether spacether linked a pull request Jan 11, 2022 that will close this issue
5 tasks
@ghost
Copy link

ghost commented Jan 14, 2022

I also produced this with version 5.2.1. I upgrade to 5.3.1 and the error was immediately obvious to me. I had set a property as either (integer, nullable) but was receiving a string. At least in my case, it wasn't a bug, just good o'l user error and an un-helpful error message.

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

Successfully merging a pull request may close this issue.

4 participants