Datamodel-code-generator: Recognize constants for more readable type hints

Created on 16 Jan 2021  路  3Comments  路  Source: koxudaxi/datamodel-code-generator

Is your feature request related to a problem? Please describe.

Enums generate classes to represent the the type, for example the following JSONSchema:

        "kind": {
          "type": "string",
          "enum": [
            "RoleBindingList"
          ]
        },

will generate:

class Kind155(Enum):
    role_binding_list = "RoleBindingList"

This is a common case in APIs as there often is a type or kind field which is a constant value. Having hundreds of classes called Kind* results in several

  • Readability: kind: Optional[Kind155] = Field(...) is not a helpful type annotation
  • Numbering is not stable, changes to the api might result in a different numbering, for example if something at the beginning of the schema is added all numbers change.

Describe the solution you'd like

It would be great if dcg would recognize these as constants instead of an enum.
This way they could be named after the value instead of the key.

class Kind155(Enum):
    role_binding_list = "RoleBindingList"

Could become:

class RoleBindingList(Enum):
    role_binding_list = "RoleBindingList"

Describe alternatives you've considered

Even better might be:

RoleBindingList = typing.Literal["RoleBindingList"]

But that would only be python 3.8 compatible.

Additional improvement

As an added bonus when implementing this, merging constants (having the same constant twice, merge into one type deceleration). Currently I get one enum class for every use of a constant - evne if it is the same constant, e.g.:

class Kind139(Enum):
    role_binding_list = "RoleBindingList"

# ...

class Kind155(Enum):
    role_binding_list = "RoleBindingList"

Implementation
I have not yet looked into it how much work it would be / how to implement it. Was looking for your @koxudaxi comments first. Is this something you would support?

released

Most helpful comment

@FlorianLudwig
I have added a new option --enum-field-as-literal on 0.6.23.
I provide two values are all or one for the option.

all

_all enum field type is Literal_

--target-python-version 3.8 --enum-field-as-literal all
https://github.com/koxudaxi/datamodel-code-generator/blob/f9b64e0a214d9ecbe583289c6e8d6752e1d9cbb4/tests/data/expected/main/main_openapi_enum_models_all/output.py#L13-L18

one

_the field type is Literal when an enum has only one possible value_

--target-python-version 3.8 --enum-field-as-literal one
https://github.com/koxudaxi/datamodel-code-generator/blob/f9b64e0a214d9ecbe583289c6e8d6752e1d9cbb4/tests/data/expected/main/main_openapi_enum_models_one/output.py#L13-L23

All 3 comments

@FlorianLudwig
I'm sorry. I overlooked a notification for this issue.

I add an option --reuse-model that is reusing the same content model. https://github.com/koxudaxi/datamodel-code-generator/issues/170
The option may cover your-case.

Input

{
    "definitions": {
        "cat": {
            "type": "object",
            "properties": {
                "kind": {
                    "type": "string",
                    "enum": [
                        "animal"
                    ]
                }
            }
        },
        "dog": {
            "type": "object",
            "properties": {
                "kind": {
                    "type": "string",
                    "enum": [
                        "animal"
                    ]
                }
            }
        },
        "snake": {
            "type": "object",
            "properties": {
                "kind": {
                    "type": "string",
                    "enum": [
                        "animal"
                    ]
                }
            }
        }
    }
}

Output

...

class Kind(Enum):
    animal = 'animal'


class Cat(BaseModel):
    kind: Optional[Kind] = None


class Kind1(Enum):
    animal = 'animal'


class Dog(BaseModel):
    kind: Optional[Kind] = None


class Kind2(Enum):
    animal = 'animal'


class Snake(BaseModel):
    kind: Optional[Kind] = None
class RoleBindingList(Enum):
    role_binding_list = "RoleBindingList"

Did you expect the enum is only one when you want to change class name?
If I know the rule when class name is changed then, I can implement it.

RoleBindingList = typing.Literal["RoleBindingList"]
But that would only be python 3.8 compatible.

I feel it's a good idea 馃槃
I guess this option should be provide with --target-python-version 3.8 or later.

Hey,

thanks for the detailed explanation. --reuse-model sounds awesome.

Regarding changing the class name: My idea was to change it when an enum has only one possible value. Because in this case it is not really a enum but a constant.

The idea with the --target-python-version 3.8 sounds perfect to me too.

@FlorianLudwig
I have added a new option --enum-field-as-literal on 0.6.23.
I provide two values are all or one for the option.

all

_all enum field type is Literal_

--target-python-version 3.8 --enum-field-as-literal all
https://github.com/koxudaxi/datamodel-code-generator/blob/f9b64e0a214d9ecbe583289c6e8d6752e1d9cbb4/tests/data/expected/main/main_openapi_enum_models_all/output.py#L13-L18

one

_the field type is Literal when an enum has only one possible value_

--target-python-version 3.8 --enum-field-as-literal one
https://github.com/koxudaxi/datamodel-code-generator/blob/f9b64e0a214d9ecbe583289c6e8d6752e1d9cbb4/tests/data/expected/main/main_openapi_enum_models_one/output.py#L13-L23

Was this page helpful?
0 / 5 - 0 ratings