When generating a python client api from a swagger specification containing a cross reference, i.e. type A references type B and type B references type A - the resulting python client cannot be used.
2.3.1
{
"swagger": "2.0",
"paths": {},
"definitions": {
"MyType1": {
"properties": {
"friend": {
"$ref": "#/definitions/MyType2"
}
}
},
"MyType2": {
"properties": {
"friend": {
"$ref": "#/definitions/MyType1"
}
}
}
}
}
java -jar swagger-codegen-cli.jar generate -i python-swagger-bug.json -l python -o python-bug
java -jar swagger-codegen-cli.jar generate -i python-swagger-bug.json -l python -o python-bug
cd python-bug
python
import swagger_client
gives an ImportError:
ImportError: cannot import name 'MyType1'
Probably related to the javascript issue with circular references
https://github.com/swagger-api/swagger-codegen/issues/8037
The MyType1 python file refers to MyType2 which in turn refers to MyType1 (which hasn't been initialized). Removing the import in MyType2 to MyType1 fixes the problem.
I suppose the code generator should keep track of the order of the imports in the dependencies and prevent the circular imports.
Same for me. I define a tree structure with unknown depth, using a circular reference (yaml file):
ActivityExecution:
type: "object"
properties:
childActivities:
type: "array"
items:
$ref: "#/definitions/ActivityExecution"
....
This is supposed to be a valid OpenAPI 2.0. The generated server code (Python-Flask ) contains the following erroneous file:
# swagger_server/models/activity_execution.py
# ...
from swagger_server.models.activity_execution import ActivityExecution # noqa: F401,E501
# ...
class ActivityExecution(Model):
# ...
def __init__(self, child_activities: List[ActivityExecution]=None): # noqa: E501
# ...
The import fails because the file tries to import itself, while class ActivityExecution class is not yet defined . Anyway, this is doomed to failure because the type hints in the __init__ method refers to ActivityExecution, which is not yet defined (it is _being_ defined actually). I think that you need a "forward reference", as explained here.
The import fails because the file tries to import itself, while class ActivityExecution class is not yet defined . Anyway, this is doomed to failure because the type hints in the init method refers to ActivityExecution, which is not yet defined (it is _being_ defined actually). I think that you need a "forward reference", as explained here.
Alternatively, a single import statement could be used at the top:
import swagger_server.models.activity_execution
and where ActivityExecution is used it should become
def __init__(self, child_activities: List[swagger_server.models.activity_executionActivityExecution]=None): # noqa: E501
Would this work?
@dtrieschnigg: Good idea but it won't :(. I've tried also other flavors of import but these fail also. I think that there is anyway a design problem _in Python_ trying to refer to a class that is not yet defined; forward reference technique is definitely required.
@piedenis in your case wouldn't this already work?
from typing import List
# no imports whatsoever
class ActivityExecution():
def __init__(self, children: List['ActivityExecution']):
pass
@dtrieschnigg: Yes. But what you put is just the first step to do in the process of tweaking the code generated by swagger-codegen. I had to replace ActivityExecution by 'ActivityExecution' at several other places in the ActivityExecution class. Also, I've seen that this is not enough because it creates a typing.ForwardRef instance, which is not handled by the framework when it tries to deserialize actual data, AFAIK. Then, I've made this hack in util.py:
def _deserialize(data, klass):
...
elif isinstance(klass,typing.ForwardRef):
from models import ActivityExecution
return deserialize_model(data, klass._evaluate(globals(),locals()))
This replaces the ForwardRef into the actual ActivityExecution class. This is quite ugly but it works!
I presume that codegen developers could find a neater solution.
For the __init__ you describe, you could use the quoted ActivityExecution and for the other references (which are /in/ a method definition) you could use global references.
Something like
import swagger_server.models
And in the methods using the classes
swagger_server.models.ClassName
This does prevent a circular reference.
You are working on the generated python server code? (I am trying to get the client-code running)
@wing328 did you just send me an e-mail about this issue?
Hi do we have expected timeline for this? also bumped into the same issue. Can help if needed.
@dtrieschnigg Sorry for this (very) late answer! Thanks for the trick! BTW, yes, I use the generated server code only.
@dtrieschnigg when I try to do like you suggested, I get an error:
AttributeError: module 'swagger_server' has no attribute 'models'
p.s. This method does work for one model and does not for another.
The object in swagger:
User:
type: "object"
properties:
id:
type: "integer"
format: "int64"
name:
type: "string"
manager:
$ref: "/definitions/User"
And I have a few objects with such a refenence.
@dtrieschnigg when I try to do like you suggested, I get an error:
AttributeError: module 'swagger_server' has no attribute 'models'
Do you have some kind of namespacing in your models? Can you check the generated code? Are you generating the client or the server?
Ok, moving to https://github.com/openapitools/openapi-generator solved my problems....
Most helpful comment
Same for me. I define a tree structure with unknown depth, using a circular reference (yaml file):
This is supposed to be a valid OpenAPI 2.0. The generated server code (Python-Flask ) contains the following erroneous file:
The import fails because the file tries to import itself, while class ActivityExecution class is not yet defined . Anyway, this is doomed to failure because the type hints in the __init__ method refers to ActivityExecution, which is not yet defined (it is _being_ defined actually). I think that you need a "forward reference", as explained here.