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-experimental] generation of oneOf interfaces not working for oneOf usage in properties - leads to import error #6161

Closed
5 tasks done
niderhoff opened this issue May 4, 2020 · 12 comments
Labels
Client: Python Feature: Composition / Inheritance Inline Schema Handling Schema contains a complex schema in items/additionalProperties/allOf/oneOf/anyOf Issue: Bug

Comments

@niderhoff
Copy link

niderhoff commented May 4, 2020

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • What's the version of OpenAPI Generator used? 4.3.1-SNAPSHOT (jar)
  • Have you search for related issues/PRs? yes found Fix generation of oneOf interfaces for oneOf usage in properties #5400 but nothing related to python client
  • What's the actual output vs expected output? classes should be generated
Description

When using oneOf to match properties inside a schema, the oneOf-classes are not generated which leads to an import error.

openapi-generator version
$ java -jar /c/Develop/projects/openapi-generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar version
4.3.1-SNAPSHOT
OpenAPI declaration file content or url
openapi: "3.0.3"
info:
  version: 0.0.0
  title: Simple API
paths:
  /:
    get:
      responses:
        200:
          description: OK

components:
  schemas:

    TransA:
      type: object
      properties:
        function:
          type: string
          default: abs
      required:
        - function

    TransB:
      type: object
      properties:
        function:
          type: string
          default: bin
      required:
        - function


    TransParent:
        type: "object"
        properties:
          transformation:
            oneOf:
              - $ref: '#/components/schemas/TransA'
              - $ref: '#/components/schemas/TransB'
Command line used for generation
java -jar /c/Develop/projects/openapi-generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -i test_openapi.yaml -g python-experimental -o test_openapi/ && cp -r test_openapi/openapi_client .
Steps to reproduce
import openapi_client

output:

Traceback (most recent call last):
  File "c:\Develop\projects\openapi\openapi_client\models\trans_parent.py", line 33, in <module>
    from openapi_client.models import one_of_trans_a_trans_b
ImportError: cannot import name 'one_of_trans_a_trans_b'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:/Develop/projects/openapi/apicheck.py", line 1, in <module>
    import openapi_client
  File "c:\Develop\projects\openapi\openapi_client\__init__.py", line 38, in <module>
    from openapi_client.models.trans_parent import TransParent
  File "c:\Develop\projects\openapi\openapi_client\models\trans_parent.py", line 36, in <module>
    'openapi_client.models.one_of_trans_a_trans_b']
KeyError: 'openapi_client.models.one_of_trans_a_trans_b'
Related issues/PRs

original PR adding this functionality: #5341
background info: #5791
similar PR: #5400 (merged)
similar issues: #5903 (open), #5730 (open)

Suggest a fix

Implement generation of models.one_of_xxxx-classes

@niderhoff niderhoff changed the title [BUG][python-experimental] generation of oneOf interfaces not working for oneOf usage in properties [BUG][python-experimental] generation of oneOf interfaces not working for oneOf usage in properties - leads to import error May 4, 2020
@niderhoff
Copy link
Author

also the same thing happening with anyOf

@mwilkins91
Copy link

This happens in python-flask as well...

@spacether
Copy link
Contributor

@niderhoff have you tried making transformation into its own model? like:

    Transformation:
        oneOf:
          - $ref: '#/components/schemas/TransA'
          - $ref: '#/components/schemas/TransB'
    TransParent:
        type: "object"
        properties:
          transformation:
              - $ref: '#/components/schemas/Transformation'

That should produce code which works as you intend.
This bug probably exists because deserializing composed schemas is complicated and the Java code is assuming that it is happening at the schemas/CodegenModel level and not at a object property/CodegenProperty level
@wing328 @sebastien-rosset @jimschubert

@spacether spacether added Feature: Composition / Inheritance Inline Schema Handling Schema contains a complex schema in items/additionalProperties/allOf/oneOf/anyOf labels Mar 26, 2021
@haisum
Copy link

haisum commented Jun 16, 2021

This happens with go and java too. @spacether the workaround you mentioned isn't working. Is there any other workaround you can think of?

This spec produces OneOfobjectarray and OneOfarraystring types in code which fails to compile.

{
  "openapi": "3.0.0",
  "info": {
    "title": "",
    "version": "v2"
  },
  "servers": [
    {
      "url": ""
    }
  ],
  "externalDocs": {
    "description": "",
    "x-amf-title": "About",
    "url": ""
  },
  "paths": {
    "/database/databases": {
      "get": {
        "description": "List databases.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/database_databases_query"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/database_databases"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "database_databases": {
        "description": ".",
        "type": "array",
        "items": {}
      },
      "string_array":{
        "description": "string array",
        "type": "array",
        "additionalProperties": false,
        "items": {
          "type": "string"
        }
      },
      "string":{
        "description": "string",
        "type": "string",
        "additionalProperties": false
      },
      "database_databases_query": {
        "description": ".",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "uuid": {
            "description": "Database uuid filter",
            "oneOf": [
              {
                "$ref": "#/components/schemas/string"
              },
              {
                "$ref": "#/components/schemas/string_array"
              }
            ]
          },
          "status": {
            "description": "Database status filter",
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "enum": [
                    "CREATE_IN_PROGRESS",
                    "CREATED",
                    "UPDATE_IN_PROGRESS",
                    "DELETE_IN_PROGRESS",
                    "DELETED"
                  ],
                  "type": "string"
                }
              },
              {
                "enum": [
                  "CREATE_IN_PROGRESS",
                  "CREATED",
                  "UPDATE_IN_PROGRESS",
                  "DELETE_IN_PROGRESS",
                  "DELETED"
                ]
              }
            ]
          }
        }
      }
    }
  }
}

@gardengeek99
Copy link

I am running into the same issue.

@spacether
Copy link
Contributor

@haisum what about my work around specifically didn't work?
This issue has been hit by you and many other users and the work around has worked for them.
In your newest example, again you need to extract your status oneOf schema into a component and reference it.
An enum with anyType (your second oneOf) may not be a case that works right now. Try setting the type of that schema to string.

@rgomezp
Copy link

rgomezp commented Mar 1, 2022

I am running into the same issue in Golang:

model_inline_response_200.go:24:10: undefined: OneOfobjectarray

@rgomezp
Copy link

rgomezp commented Mar 7, 2022

I have the following in my api.json file:

"properties": {
    "id": {
      "type": "string"
    },
    "recipients": {
      "type": "integer",
      "description": "my description"
    },
    "external_id": {
      "type": "string"
    },
    "errors": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "invalid_external_user_ids": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "my description"
            },
            "invalid_player_ids": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "my description"
            }
          }
        },
        {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "my description"
        }
      ]
    }
  }

Which results in the following in the resulting Go build:

// NotificationPropertiesTransformation struct for NotificationPropertiesTransformation
type NotificationPropertiesTransformation struct {
	Id *string `json:"id,omitempty"`
	// Estimated number of subscribers targetted by notification.
	Recipients *int32 `json:"recipients,omitempty"`
	ExternalId *string `json:"external_id,omitempty"`
	Errors *OneOfobjectarray `json:"errors,omitempty"`
	AdditionalProperties map[string]interface{}
}

The object type OneOfobjectarray doesn't exist:

Screen Shot 2022-03-07 at 5 07 52 PM

The above solutions didn't work for me. I notice in your example, you use transformation. Perhaps this is affecting the differing behavior?

Any guidance? @spacether

@wing328
Copy link
Member

wing328 commented Mar 8, 2022

For the following:

    "errors": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "invalid_external_user_ids": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "my description"
            },
            "invalid_player_ids": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "my description"
            }
          }
        },
        {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "my description"
        }
      ]
    }

Instead of defining the oneOf schema (and oneOf member schemas) inline, can you try defining these separately and referencing them instead as a workaround?

@rgomezp
Copy link

rgomezp commented Mar 8, 2022

Thanks @wing328

Ok, so I made some slight progress:

      "InvalidIdentifierError": {
        "type": "object",
        "properties": {
          "invalid_external_user_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Returned if using include_external_user_ids and some were valid and others were not.\nIf multiple devices or the same device with multiple player ids gets the same external_user_id,\nthen this indicates how many were unsubscribed.\nMore details on why the same device might have multiple records here: https://documentation.onesignal.com/docs/player-id\n"
          },
          "invalid_player_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Returned if using include_player_ids and some were valid and others were not.\nPlease process these on your server and remove them from your database if you are tracking them.\n"
          }
        }
      },
      "NoSubscribersError": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "description": "Returned if no subscribed players.\n"
      },
      "Notification200Errors": {
        "oneOf": [{
          "$ref": "#/components/schemas/InvalidIdentifierError"
        },
        {
          "$ref": "#/components/schemas/NoSubscribersError"
        }]
      },

results in the following struct:

type Notification200Errors struct {
	InvalidIdentifierError *InvalidIdentifierError
	[]string *[]string    // <-- error
}

and the following functions:

// []stringAsNotification200Errors is a convenience function that returns []string wrapped in Notification200Errors
func []stringAsNotification200Errors(v *[]string) Notification200Errors {
	return Notification200Errors{ []string: v}
}

notice the function name []stringAsNotification200Errors leads to an error also.

Digging into the source code a bit, it looks like the problem occurs in model_oneof.mustache:

// {{classname}} - {{#description}}{{{description}}}{{/description}}{{^description}}struct for {{{classname}}}{{/description}}
type {{classname}} struct {
	{{#oneOf}}
	{{{.}}} *{{{.}}}
	{{/oneOf}}
}

{{#oneOf}}
// {{{.}}}As{{classname}} is a convenience function that returns {{{.}}} wrapped in {{classname}}
func {{{.}}}As{{classname}}(v *{{{.}}}) {{classname}} {
	return {{classname}}{ {{{.}}}: v}
}

{{/oneOf}}

In particular, this portion: {{{.}}} *{{{.}}} which results in the name and the type being the same. With my first model (InvalidIdentifierError) there is no issue since the model is it's own object with own definition. But in the second case (NoSubscribersError) the model is an array so I think it's not creating its own type.

The correct result would be something like:

type Notification200Errors struct {
	InvalidIdentifierError *InvalidIdentifierError
	NoSubscribersError *[]string    // <-- no error
}

@spacether
Copy link
Contributor

@rgomezp can you please move your discussion to a go issue because it is not caused by the python or python-experimental generator?

@spacether
Copy link
Contributor

spacether commented Mar 30, 2022

This issue has been fixed in the new python-experimental which was released in 5.4.0.
One can see composition working in all inline locations including properties here:
https://github.com/OpenAPITools/openapi-generator/pull/11420/files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Client: Python Feature: Composition / Inheritance Inline Schema Handling Schema contains a complex schema in items/additionalProperties/allOf/oneOf/anyOf Issue: Bug
Projects
None yet
Development

No branches or pull requests

7 participants