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

[openapi spec 3.1.0][python-flask generator] Nullable properties not propagated to child models #19619

Open
a-akshat opened this issue Sep 18, 2024 · 5 comments

Comments

@a-akshat
Copy link

a-akshat commented Sep 18, 2024

Description

Currently, we use openapi spec 3.0.3 with openapi-generator version 5.4.0 and we are planning to migrate to openapi spec 3.1.0 with the latest generator version.

While migrating to 3.1.0 I see that nullable is not supported anymore and we use the isNullable value to set our generated model instance variable to Optional as follows:

{{#isNullable}}Optional[{{/isNullable}}{{#isDateTime}}Union[datetime, DateTimeProxy]{{/isDateTime}}{{^isDateTime}}{{dataType}}{{/isDateTime}}{{#isNullable}}]{{/isNullable}}

I noticed that with the new version of the generator and openapi spec 3.1.0 when we set a property as "null" this constraint is not inherited by the child schema in the model generated. Here is a simple example:

Openapi spec 3.1.0:

openapi: 3.1.0
paths:
  /users/{userId}:
    get:
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'

components:
  schemas:
    BaseUser:
      type: object
      properties:
        id:
          type:
            - string
            - 'null'
        username:
          type: string

    UserResponse:
      type: object
      allOf:
        - $ref: '#/components/schemas/BaseUser'
        - type: object
          properties:
            email:
              type: string
              description: The email of the user

Model generated:
base_user.py

class BaseUser(TypedDict):
    id: Optional[str]
    username: str

user_response.py

class UserResponse(TypedDict):
    id: str
    username: str
    email: str

Notice how the id : Optional[str] is not the same as parent in the child schema.
This worked as expected with 3.0.3 and the new version of the generator as well. So makes me wonder if the 3.1.0 spec isNullable property is propagated to the child schema.

Please let me know if there is a way I can resolve this as we want to utilize the great features in 3.1.0 without breaking our client code.

openapi-generator version

7.8.0

OpenAPI declaration file content or url
Command line used for generation

openapi-generator-cli generate --input-spec spec/bundle.yaml --config openapi-generator.yaml --output ../..

Note: the openapi-generator.yaml config file is straightforward and no options are enabled in it.

@wing328
Copy link
Member

wing328 commented Sep 19, 2024

i tested with python-flask in the latest master and don't see TypedDict in the auto-generated models so looks like you're using a customized version/template somehow

i also tested with python client generator and couldn't repeat the issue, which means id is correctly annotated with the type Optional[str]

@a-akshat
Copy link
Author

a-akshat commented Sep 19, 2024

@wing328 yes you are right, we use a customized templates for our use case.
Also, please note that this template worked with openapi spec 3.0.3 and latest version of the generator
Here is the model customized template that produces the TypedDict

{{/parent}}
{{! Simple object types get an Enum- or TypedDict-base representation: }}
{{^parent}}
{{#isEnum}}
class {{classname}}({{#isString}}str, {{/isString}}enum.Enum):
{{#allowableValues}}
    {{#enumVars}}
    {{name}} = {{{value}}}
    {{/enumVars}}
{{/allowableValues}}
{{/isEnum}}
{{^isEnum}}
class {{classname}}(TypedDict):
{{#vars}}
    {{name}}: {{> field_type}}
{{/vars}}
{{/isEnum}}
{{/parent}}
{{/vendorExtensions.x-python-imports}}
{{/model}}
{{/models}}

field_type.mustache:
{{#isNullable}}Optional[{{/isNullable}}{{#isDateTime}}Union[datetime, DateTimeProxy]{{/isDateTime}}{{^isDateTime}}{{dataType}}{{/isDateTime}}{{#isNullable}}]{{/isNullable}}

Do you recommend using the python generator instead of python flask for the models?
https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/python/model_generic.mustache

@wing328
Copy link
Member

wing328 commented Sep 19, 2024

    "vars" : [ {
      "openApiType" : "string",
      "baseName" : "id",
      "getter" : "getId",
      "setter" : "setId",
      "dataType" : "str",
      "datatypeWithEnum" : "str",
      "name" : "id",
      "defaultValueWithParam" : " = data.id;",
      "baseType" : "str",
      "example" : "''",
      "jsonSchema" : "{\r\n  \"nullable\" : true,\r\n  \"type\" : \"string\"\r\n}",
      "exclusiveMinimum" : false,
      "exclusiveMaximum" : false,
      "required" : false,
      "deprecated" : false,
      "hasMoreNonReadOnly" : false,
      "isPrimitiveType" : true,
      "isModel" : false,
      "isContainer" : false,
      "isString" : true,
      "isNumeric" : false,
      "isInteger" : false,
      "isShort" : false,
      "isLong" : false,
      "isUnboundedInteger" : false,
      "isNumber" : false,
      "isFloat" : false,
      "isDouble" : false,
      "isDecimal" : false,
      "isByteArray" : false,
      "isBinary" : false,
      "isFile" : false,
      "isBoolean" : false,
      "isDate" : false,
      "isDateTime" : false,
      "isUuid" : false,
      "isUri" : false,
      "isEmail" : false,
      "isPassword" : false,
      "isNull" : false,
      "isVoid" : false,
      "isFreeFormObject" : false,
      "isAnyType" : false,
      "isArray" : false,
      "isMap" : false,
      "isOptional" : false,
      "isEnum" : false,
      "isInnerEnum" : false,
      "isEnumRef" : false,
      "isReadOnly" : false,
      "isWriteOnly" : false,
      "isNullable" : true,

using --global-property debugModels in CLI, isNullable is correctly set to true so no idea why it's not working in your use cases with customized templates.

@a-akshat
Copy link
Author

@wing328 thank you for pointing me towards the debugModels option it makes things clearer.
I can confirm that isNullable is not set on the UserResponse

{
  "importPath" : "from api.v5.generated.models.user_response import UserResponse",
  "pyImports" : [ ],
  "model" : {
    "interfaces" : [ "BaseUser" ],
    "anyOf" : [ ],
    "oneOf" : [ ],
    "allOf" : [ "BaseUser" ],
    "name" : "UserResponse",
    **"schemaName" : "UserResponse",
    "classname" : "UserResponse",**
    "classVarName" : "user_response",
    "modelJson" : "{\n  \"allOf\" : [ {\n    \"$ref\" : \"#/components/schemas/BaseUser\"\n  }, {\n    \"properties\" : {\n      \"email\" : {\n        \"type\" : \"string\",\n        \"description\" : \"The email of the user\"\n      }\n    }\n  } ]\n}",
    "dataType" : "BaseUser",
    "classFilename" : "user_response",
    "isAlias" : false,
    "isString" : false,
    "isInteger" : false,
    "isLong" : false,
    "isNumber" : false,
    "isNumeric" : false,
    "isFloat" : false,
    "isDouble" : false,
    "isDate" : false,
    "isDateTime" : false,
    "isDecimal" : false,
    "isShort" : false,
    "isUnboundedInteger" : false,
    "isPrimitiveType" : false,
    "isBoolean" : false,
    "isFreeFormObject" : false,
    "additionalPropertiesIsAnyType" : false,
    "vars" : [ {
      "openApiType" : "string",
      "baseName" : "id",
      "getter" : "getId",
      "setter" : "setId",
      "dataType" : "str",
      "datatypeWithEnum" : "str",
      "name" : "id",
      "defaultValueWithParam" : " = data.id;",
      "baseType" : "str",
      "title" : "id",
      "example" : "''",
      "jsonSchema" : "{\n  \"title\" : \"id\"\n}",
      "exclusiveMinimum" : false,
      "exclusiveMaximum" : false,
      "required" : false,
      "deprecated" : false,
      "hasMoreNonReadOnly" : false,
      "isPrimitiveType" : true,
      "isModel" : false,
      "isContainer" : false,
      "isString" : true,
      "isNumeric" : false,
      "isInteger" : false,
      "isShort" : false,
      "isLong" : false,
      "isUnboundedInteger" : false,
      "isNumber" : false,
      "isFloat" : false,
      "isDouble" : false,
      "isDecimal" : false,
      "isByteArray" : false,
      "isBinary" : false,
      "isFile" : false,
      "isBoolean" : false,
      "isDate" : false,
      "isDateTime" : false,
      "isUuid" : false,
      "isUri" : false,
      "isEmail" : false,
      "isPassword" : false,
      "isNull" : false,
      "isVoid" : false,
      "isFreeFormObject" : false,
      "isAnyType" : false,
      "isArray" : false,
      "isMap" : false,
      "isOptional" : false,
      "isEnum" : false,
      "isInnerEnum" : false,
      "isEnumRef" : false,
      "isReadOnly" : false,
      "isWriteOnly" : false,
      **"isNullable" : false,**
      "isSelfReference" : false,
      "isCircularReference" : false,
      "isDiscriminator" : false,
      "isNew" : false,
      "isOverridden" : false,
      "vars" : [ ],
      "requiredVars" : [ ],
      "vendorExtensions" : {
        "x-py-typing" : "Optional[StrictStr] = None"
      },
      "hasValidation" : false,
      "isInherited" : false,
      "nameInCamelCase" : "id",
      "nameInPascalCase" : "Id",
      "nameInSnakeCase" : "ID",
      "uniqueItems" : false,
      "isXmlAttribute" : false,
      "isXmlWrapped" : false,
      "additionalPropertiesIsAnyType" : false,
      "hasVars" : false,
      "hasRequired" : false,
      "hasDiscriminatorWithNonEmptyMapping" : false,
      "hasMultipleTypes" : false,
      "schemaIsFromAdditionalProperties" : false,
      "isBooleanSchemaTrue" : false,
      "isBooleanSchemaFalse" : false,
      "isEnumOrRef" : false,
      "datatype" : "str",
      "exclusiveMaximum" : false,
      "hasItems" : false
    }

Is it possible that you might be looking at BaseUser ?

@a-akshat
Copy link
Author

Btw, I found a workaround, there is a vendor extension that can be used to mark a property nullable.
x-nullable: true propagates isNullable as true to its children. Let me know if this is not the intended use-case.

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

No branches or pull requests

2 participants