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

Enum as separate model #1173

Closed
ghost opened this issue Jan 17, 2020 · 7 comments
Closed

Enum as separate model #1173

ghost opened this issue Jan 17, 2020 · 7 comments
Labels
feature request help wanted Pull Request welcome

Comments

@ghost
Copy link

ghost commented Jan 17, 2020

Question

Hello. I use the FastAPI framework. Can I extract the enum field as a separate component(with '$ref' in models schema)? I need it because I use the OpenAPI client generator and it generates several enums for each model.

Output of python -c "import pydantic.utils; print(pydantic.utils.version_info())":

             pydantic version: 1.3
            pydantic compiled: False
                 install path: /Users/viktor/.pyenv/versions/3.7.4/envs/orc/lib/python3.7/site-packages/pydantic
               python version: 3.7.4 (default, Oct 23 2019, 14:30:05)  [Clang 11.0.0 (clang-1100.0.33.8)]
                     platform: Darwin-19.2.0-x86_64-i386-64bit
     optional deps. installed: []

Models example:

import pydantic
import enum
import json


class StrEnum(str, enum.Enum):
    A = 'A'
    B = 'B'


class Foo(pydantic.BaseModel):
    int_field: int
    enum_field: StrEnum


class Bar(Foo):
    another_field: str


print(json.dumps({
    'components': {
        'Foo': Foo.schema(),
        'Bar': Bar.schema(),
    }
}))
'''
{
  "components": {
    "Foo": {
      "title": "Foo",
      "type": "object",
      "properties": {
        "int_field": {
          "title": "Int Field",
          "type": "integer"
        },
        "enum_field": {
          "title": "Enum Field",
          "enum": [
            "A",
            "B"
          ],
          "type": "string"
        }
      },
      "required": [
        "int_field",
        "enum_field"
      ]
    },
    "Bar": {
      "title": "Bar",
      "type": "object",
      "properties": {
        "int_field": {
          "title": "Int Field",
          "type": "integer"
        },
        "enum_field": {
          "title": "Enum Field",
          "enum": [
            "A",
            "B"
          ],
          "type": "string"
        },
        "another_field": {
          "title": "Another Field",
          "type": "string"
        }
      },
      "required": [
        "int_field",
        "enum_field",
        "another_field"
      ]
    }
  }
}
'''
@ghost ghost added the question label Jan 17, 2020
@samuelcolvin
Copy link
Member

Use schema_extra to modify your schema.

@calvinwyoung
Copy link
Contributor

@ViktorPegy Did you find a solution that didn't require you to update schema_extras for every model that uses an enum field?

This seems like an issue for anyone using a codegen tool to generate client code. By default, the codegen tool will duplicate all enum definitions, which definitely isn't desirable.

@samuelcolvin Are there any plans to add a setting that generates enums as separate models by default?

@samuelcolvin samuelcolvin added feature request help wanted Pull Request welcome and removed question labels Apr 22, 2020
@samuelcolvin
Copy link
Member

No plans at present (beyond this issue).

didn't require you to update schema_extras for every model that uses an enum field

Currently you do need to implement a schema_extra function that modifies the schema, but you don't need any duplicate code: create your own base model MyBaseModel with a config class that includes an implementation of the schema_extra method which does what you want.

That way the schema for all models inheriting from MyBaseModel will be as you want them.


Long term if there's a switch or utility function, it'll boil down to the same method for modifying the schema, so your stop gap could also become the long term fix.

In terms of a fix, I'd prefer to either:

  • make this fix automatic: so if an enum is used more than once it's not duplicated, or simply always a separate model
  • or, if people don't like that, have a utility function for modifying the schema and documentation on how to call that method from schema_extra

@samuelcolvin samuelcolvin reopened this Apr 22, 2020
@calvinwyoung
Copy link
Contributor

@samuelcolvin Thanks for the thoughtful response — all of that sounds reasonable.

At a high level, my understanding is that we can achieve the desired behavior by doing the following:

  1. update the appopriate property in the model schema to be a $ref pointing to the enum definition
  2. insert the enum into the definition dict that gets generated for the schema

It looks like schema_extra currently lets us override the model schema, but it doesn't let us modify the definition dict to add new sub-models:
https://github.com/samuelcolvin/pydantic/blob/3cd8b1ee2d5aac76528dbe627f40fe1c27bf59f6/pydantic/schema.py#L459-L466

Is there a trick to letting us insert the enum definition as part of schema_extra?

With regard to the long-term fixes you proposed, I feel like it should be desirable to always output enums as separate models. I know you mentioned that some people might not like that, but what are the circumstances where it'd be better to have duplicate enum definitions over a single enum definition that gets referenced everywhere?

If we were to go down this path, the new behavior should remain backwards compatible since the generated schema still follows the Open API spec for reusable enums, and the fix should be pretty straightforward — looks like we just need a few updates to field_singleton_schema:
https://github.com/samuelcolvin/pydantic/blob/3cd8b1ee2d5aac76528dbe627f40fe1c27bf59f6/pydantic/schema.py#L644-L646

I'd be happy to take a whack at the implementation as I expect this will be useful for anyone using a codegen tool.

@mostaphaRoudsari
Copy link
Contributor

It looks like this issue has been addressed already via #1432 and can be closed.

@PrettyWood
Copy link
Collaborator

@mostaphaRoudsari you're right ! Thanks

@pawelad
Copy link

pawelad commented Oct 25, 2022

Sorry to comment on an old ticket, but is there any way to force the old (pre v1.6) behaviour on recent pydantic releases?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request help wanted Pull Request welcome
Projects
None yet
Development

No branches or pull requests

5 participants