diff --git a/changes/1310-FuegoFro.md b/changes/1310-FuegoFro.md new file mode 100644 index 0000000..83c392e --- /dev/null +++ b/changes/1310-FuegoFro.md @@ -0,0 +1 @@ +Have `BaseModel` inherit from `Representation` to make mypy happy when overriding `__str__`. diff --git a/pydantic/main.py b/pydantic/main.py index e778bf3..d7e2763 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -184,6 +184,12 @@ def validate_custom_root_type(fields: Dict[str, ModelField]) -> None: UNTOUCHED_TYPES = FunctionType, property, type, classmethod, staticmethod +# Note `ModelMetaclass` refers to `BaseModel`, but is also used to *create* `BaseModel`, so we need to add this extra +# (somewhat hacky) boolean to keep track of whether we've created the `BaseModel` class yet, and therefore whether it's +# safe to refer to it. If it *hasn't* been created, we assume that the `__new__` call we're in the middle of is for +# the `BaseModel` class, since that's defined immediately after the metaclass. +_is_base_model_class_defined = False + class ModelMetaclass(ABCMeta): @no_type_check # noqa C901 @@ -195,7 +201,7 @@ class ModelMetaclass(ABCMeta): pre_root_validators, post_root_validators = [], [] for base in reversed(bases): - if issubclass(base, BaseModel) and base != BaseModel: + if _is_base_model_class_defined and issubclass(base, BaseModel) and base != BaseModel: fields.update(deepcopy(base.__fields__)) config = inherit_config(base.__config__, config) validators = inherit_validators(base.__validators__, validators) @@ -297,7 +303,7 @@ class ModelMetaclass(ABCMeta): return cls -class BaseModel(metaclass=ModelMetaclass): +class BaseModel(Representation, metaclass=ModelMetaclass): if TYPE_CHECKING: # populated by the metaclass, defined here to help IDEs only __fields__: Dict[str, ModelField] = {} @@ -314,12 +320,7 @@ class BaseModel(metaclass=ModelMetaclass): Config = BaseConfig __slots__ = ('__dict__', '__fields_set__') - # equivalent of inheriting from Representation - __repr_name__ = Representation.__repr_name__ - __repr_str__ = Representation.__repr_str__ - __pretty__ = Representation.__pretty__ - __str__ = Representation.__str__ - __repr__ = Representation.__repr__ + __doc__ = '' # Null out the Representation docstring def __init__(__pydantic_self__, **data: Any) -> None: """ @@ -768,6 +769,9 @@ class BaseModel(metaclass=ModelMetaclass): return self.__dict__ +_is_base_model_class_defined = True + + def create_model( model_name: str, *, diff --git a/pydantic/utils.py b/pydantic/utils.py index 8249a95..ca85ca0 100644 --- a/pydantic/utils.py +++ b/pydantic/utils.py @@ -220,6 +220,8 @@ class Representation: of objects. """ + __slots__: Tuple[str, ...] = tuple() + def __repr_args__(self) -> 'ReprArgs': """ Returns the attributes to show in __str__, __repr__, and __pretty__ this is generally overridden. diff --git a/tests/mypy/modules/success.py b/tests/mypy/modules/success.py index b5cbd10..a2d5fde 100644 --- a/tests/mypy/modules/success.py +++ b/tests/mypy/modules/success.py @@ -16,6 +16,9 @@ from pydantic.generics import GenericModel class Flags(BaseModel): strict_bool: StrictBool = False + def __str__(self) -> str: + return f'flag={self.strict_bool}' + class Model(BaseModel): age: int