-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
[Python] Adds allOf/oneOf/anyOf composed models #4446
[Python] Adds allOf/oneOf/anyOf composed models #4446
Conversation
5bd3c25
to
8047162
Compare
Very excited to see this go in! Is there anything I can do to help it along? |
Just waiting on a review and approval on this PR |
Python team:
|
991d2b3
to
b523f4b
Compare
b523f4b
to
5aff153
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, I think this looks good. I'm not current with dynamic programming paradigms in Python, so I can't comment on the design decisions. It's similar to what I've seen in Ruby and PHP code that I've worked on, though. The "safe import" is similar to things I've seen in node.js. It will be good to get this merged and get community feedback.
As an experimental generator, I think it's safe to merge into any version. However, the README_common
rename will affect the stable python generators, and would need to be changed back before merge (see inline comment). The rest of my comments are trivial code quality suggestions.
@@ -65,6 +65,9 @@ public PythonClientExperimentalCodegen() { | |||
modelTemplateFiles.remove("model.mustache"); | |||
modelTemplateFiles.put("python-experimental/model.mustache", ".py"); | |||
|
|||
modelTestTemplateFiles.remove("model_test.mustache", ".py"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side note (not blocking for this PR): we should probably add a helper to remove by template name. For modelTestTemplateFiles
, I think it's pretty simple, but the supportingFiles.remove
calls later in this file might fail on hash conflicts or if a property is added to SupportingFile
without updating hash/equals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like a good idea. Let's keep that as a future improvement in a separate PR.
...erator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java
Outdated
Show resolved
Hide resolved
...erator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java
Outdated
Show resolved
Hide resolved
...erator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java
Outdated
Show resolved
Hide resolved
...erator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java
Outdated
Show resolved
Hide resolved
...erator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java
Outdated
Show resolved
Hide resolved
...erator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java
Outdated
Show resolved
Hide resolved
...erator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java
Show resolved
Hide resolved
...erator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java
Outdated
Show resolved
Hide resolved
modules/openapi-generator/src/main/resources/python/README.mustache
Outdated
Show resolved
Hide resolved
@spacether I just tried this PR out and I ran into the following issue. When specifying a discriminator property that is defined in the superclass, during deserialization, the client is unable to find the mentioned method in the list of attributes. Would there be a simple way to search through that list of attributes down to the root type? Example of a failing schema: SuperClass:
type: object
properties:
myTypeDiscriminator:
type: string
SubClass:
type: object
properties:
myTypeDiscriminator:
type: string
allOf:
- $ref: '#/components/schemas/SuperClass'
SubSubClass:
type: object
allOf:
- $ref: '#/components/schemas/SubClass'
discriminator:
propertyName: 'myType'
mapping:
... The issue is that when SubSubClass attempts to resolve the myType property, it only looks in its own attribute map, and doesn't seem to look in the properties pulled in from the super. Let me know if this isn't clear enough. Certainly not a blocker - just wanted to mention this functionality is lacking. |
@ethan92429 thank you for letting me know about this. |
@ethan92429 I got ancestor inheritance working by changing my mustache template vars variables into requiredVars and optionalVars. In the test folder I added a Child composed schema model showing it working at:
@ethan92429 Can you explain your example more? I don't understand it. |
Hi @spacether glad to hear that you got this working - just to clarify the example that I couldn't get to work (I'm trying again with the example specified in the OAS) components:
schemas:
Animal:
type: object
required:
- pet_type
properties:
pet_type:
type: string
Pet:
type: object
allOf:
- $ref: '#/components/schemas/Animal'
discriminator:
propertyName: pet_type
mapping:
cachorro: Dog
Cat:
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
# all other properties specific to a `Cat`
properties:
name:
type: string
Dog:
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
# all other properties specific to a `Dog`
properties:
bark:
type: string
Lizard:
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
# all other properties specific to a `Lizard`
properties:
lovesRocks:
type: boolean Basically, I have one single property that will be used as a discriminator defined in the base class of my deeply nested polymorphic tree (pet_type on Animal), and I need all the polymorphic children (Pet) to use that property as their discriminator, but the Pet model can't find it because it is an inherited property. Put simply, I need inherited properties to be considered as valid discriminator mappers if they are included in allOf. Does that clarify? I was able to actually bypass this by just including pet_type in every model that I export, but it is a bit verbose. |
@ethan92429 your example is not working because of a bug in swagger-parser. I have opened a ticket to track that bug here: swagger-api/swagger-parser#1269
Then your example should work. |
…rialize_lizard, adds setting discriminator using allOf schema
888c2e0
to
f5fec62
Compare
LOGGER.debug("discriminator is set to null (not correctly set earlier): {}", name); | ||
m.discriminator = createDiscriminator(name, schema); | ||
m.discriminator = createDiscriminator(name, innerSchema); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per here: https://swagger.io/specification/#discriminatorObject
We should be able to set the discriminator on the parent composed schema, but that's not yet working because of this bug in swagger parser: swagger-api/swagger-parser#1269
In place of having discriminator at the root level of a composed schema, we should be able to use it from our definition in our allOf schemas.
I updated this code to set the discriminator on self if one of its ancestor allOf schemas defines discriminator.
Please see my ParentPet class which demonstrates this usage at: https://github.com/spacether/openapi-generator/blob/allof_anyof_oneof_addition/modules/openapi-generator/src/test/resources/2_0/python-client-experimental/petstore-with-fake-endpoints-models-for-testing.yaml#L2072
NOTE: the original invocation here m.discriminator = createDiscriminator(name, schema);
did not do anything because we already ran that exact line above in line 1772.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this seems reasonable, but I'd like to have someone else on the @OpenAPITools/generator-core-team review as well since it affects all generators.
} | ||
|
||
if (innerSchema.getXml() != null) { | ||
m.xmlPrefix = innerSchema.getXml().getPrefix(); | ||
m.xmlNamespace = innerSchema.getXml().getNamespace(); | ||
m.xmlName = innerSchema.getXml().getName(); | ||
} | ||
if (modelDiscriminators > 1) { | ||
LOGGER.error("Allof composed schema is inherriting >1 descriminator. Only use one discriminator: {}", composed); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should error out if the user tries to define multiple discriminators in one schema.
@ethan92429 in this branch I:
|
@jimschubert can you give this another look? Your feedback has been addressed and all tests are passing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've skimmed the Python code since it's not my forte. My concerns and previous comments have been addressed. The explanation of the change in DefaultCodegen seems reasonable but I'd also like someone else on the core team to review since I have not worked much on the code processing allOf
and discriminators.
edit: if there's no response in a day or two from others, I'll go ahead and merge since the change in the file is so small.
@spacether I've been playing around with python-experimental and ran into a hitch with using it for my spec. The problem is that with the changes to have the class property types used directly, circular type referencing or recursive definitions doesn't seem to work anymore: components:
schemas:
Dog:
properties:
enemy:
type: object
$ref: '#/components/schemas/Dog' This should be a valid spec, but the generated python attempts to reference the Dog class in the definition of the Dog class - which clearly doesn't work. This causes a circular import. This looks to be a regression from just the plain python generator - which uses strings to define openApi types. What are your thoughts on this? |
@ethan92429 can you file a bug on your use case where a model has a property which is of type self? |
This PR adds composed schema (allOf/oneOf/anyOf) support to the python-experimental generator. All composed schema instances will now store instances of their composed schema models under the hood, then use them when we get attributes, set attributes, or convert the model instance to a dict for serialization.
Common model methods have been moved into parent classes with a net reduction of -3,800 lines
Test Verification
In depth tests on the Dog allOf class have been added at:
A test showing that allOf parameters are accessible on an instance of Dog is at:
Circular Reference Problem and Solution
Note: class names in dataTypes must look like pet.Pet so we can load modules with lines like
from package.models import pet
at the top of the module, then we can use pet.Pet as the data type later in the model definition.
This is specifically needed for models with composed schemas because those models require circular references.
For example:
Animal uses a Cat discriminator, so the animal module imports cat
The cat module uses allOf Animal in its composed schema definition so it imports animal
The only way to make the circular imports work is using the below two techniques:
Related Issues
This may fix the below issues if the end users use the python-experimental generator:
Potential Future Work
If this PR is accepted, later on I can submit a PR adding a python-experimental client example for the openapi spec demonstrating that oneOf and anyOf also work.
./bin/
(or Windows batch scripts under.\bin\windows
) to update Petstore samples related to your fix. This is important, as CI jobs will verify all generator outputs of your HEAD commit, and these must match the expectations made by your contribution. You only need to run./bin/{LANG}-petstore.sh
,./bin/openapi3/{LANG}-petstore.sh
if updating the code or mustache templates for a language ({LANG}
) (e.g. php, ruby, python, etc).master
,4.3.x
,5.0.x
. Default:master
.@taxpon (2017/07) @frol (2017/07) @mbohlool (2017/07) @cbornet (2017/09) @kenjones-cisco (2017/11) @tomplus (2018/10) @Jyhess (2019/01) @slash-arun (2019/11)
Authors of related work:
@rienafairefr per #2170