pydantic: Model with field name that shadows existing BaseModel attribute leads to unexpected behavior

Hello, First of all this is an awesome project, I want to use it in all my tools 😃.

I ran into a small issue: when defining a model that has a field with the same name as one of BaseModel’s existing attributes/methods, the field gets parsed but cannot be retrieved:

class BadModel(BaseModel):
    schema: str

obj = BaseModel(**{'schema': 'abc'})
print(obj.schema)

This prints <bound method BaseModel.schema of <class 'pydantic.main.BaseModel'>> instead of abc.

This makes perfect sense when looking at the code but is a bit unexpected. To be honest, I’m not sure what the “right” behavior should be here?

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 15 (12 by maintainers)

Commits related to this issue

Most upvoted comments

Well, that kind of shadowing isn’t compatible with type checking in Python; you cannot have a field with a disparate type on a sub-model. You can define your class methods on the metaclass so that they are not present on instances of Users but your type checker (assuming you use one) won’t know what to do with them either:

from pydantic.main import ModelMetaclass, BaseModel


class UsersMeta(ModelMetaclass):
    def find(cls, pk: int) -> 'Users': ...


class Users(BaseModel, metaclass=UsersMeta):
    find: int

Users.find  # Should this be `int` or `(pk: int) -> Users`?

Anyway, this will work without requiring any changes to Pydantic, assuming that you don’t step on any of its own methods.

@layday thanks I have seen that interesting thread. But that discussion is more around public instance variables as opposed to class variable vs instance variable shadowing. Obviously you wouldn’t want to remove the utils.validate_field_name at this point since its been there since 0.12. But if we can pass in config: Type['BaseConfig'] and make it a config property so we can control the shadow behavior from a config that would be a flexible solution.

I am a bit surprised this PR made it into pydantic. Think about building an ORM. Imagine all of your pydantic models (like Users in the example below) extend your own base Model class which had classmethods of find(), where(), and get(). This allows you to use your model in an ORM fasion like so.

  • Users.find(1)
  • Users.where(‘field’, ‘value’).get()
  • And many others (orderBy, limit etc…)

All fine, until the user wants a field called find, or where, or get etc… I understand they can set the field to get_ and use a alias for the actual db column named get but this seems an ugly hack when you could have just allowed shadowing.

With the above PR utils.validate_field_name will throw a shadow error if the user wants fields called find or where or get etc… Your main argument is that it’s “confusing” the difference between Users.find and user.find. Well, they are two completely different things, and should be allowed. Shadowing is an important part of any language and is a powerful tool when dealing with class variables vs instance variables. My only option at this point is to maintain a fork or pydantic which removes this validate_field_name method completely so I can build a nice ORM without working about stomping all over the users “field” namespace.

Oh thanks, let me look into aliases. If that works then an exception seems fine.