Skip to content

<Model>.schema() method handles Enum and IntEnum default field resolution differently #3190

@joaommartins

Description

@joaommartins

Checks

  • I added a descriptive title to this issue
  • I have searched (google, github) for similar issues and couldn't find anything
  • I have read and followed the docs and still think this is a bug

Bug

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

             pydantic version: 1.8.2
            pydantic compiled: True
                 install path: C:\Users\jmartins\.virtualenvs\pydantic_bug_report-NJE4-7fw\Lib\site-packages\pydantic
               python version: 3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)]
                     platform: Windows-10-10.0.19042-SP0
     optional deps. installed: ['typing-extensions']

Generating a schema with the .schema() method works as expected when resolving default values of Enum type, while it does not resolve default values of IntEnum type the same way. A minimum example follows:

# std lib imports
from enum import Enum, IntEnum

# external imports
from pydantic import BaseModel


class ExampleEnum(Enum):
    A = "a"


class ExampleIntEnum(IntEnum):
    A = 1


class ExampleModel(BaseModel):
    example_enum: ExampleEnum = ExampleEnum.A
    example_int_enum: ExampleIntEnum = ExampleIntEnum.A


generated_schema_properties = ExampleModel.schema().get("properties", {})

example_enum_generated_default = generated_schema_properties.get("example_enum", {}).get("default", None)
example_int_enum_generated_default = generated_schema_properties.get("example_int_enum", {}).get("default", None)

print(example_enum_generated_default is ExampleEnum.A.value)
# -> True

print(example_int_enum_generated_default is ExampleIntEnum.A.value)
# -> False

I've tracked the issue down to the encode_default function in schema.py:

def encode_default(dft: Any) -> Any:
    if isinstance(dft, (int, float, str)):
        return dft
    elif sequence_like(dft):
        t = dft.__class__
        return t(encode_default(v) for v in dft)
    elif isinstance(dft, dict):
        return {encode_default(k): encode_default(v) for k, v in dft.items()}
    elif dft is None:
        return None
    else:
        return pydantic_encoder(dft)

When resolving defaults for Enum the else clause is correctly used, but since isinstance(ExampleIntEnum.A, int) is truthy it returns ExampleIntEnum.A when using an IntEnum. I would suggest changing the first if to a stricter direct 'primitive' type check like if type(dft) in (int, float, str):.

I can do this myself and open a PR if there is interest and no opposition to a stricter type check.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug V1Bug related to Pydantic V1.X

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions