Search code examples
pythonpython-typingpydantic

Ignore a Field in a type annotation


If I have a pydantic class like:

from typing import Annotated, get_origin
from pydantic import BaseModel

class IgnorableBaseModel(BaseModel):
    _ignored: ClassVar[dict[str, Any]] = {}

    def __getattr__(self, attr):
        """do something with _ignored, else fallback to default"""

    def __init_subclass__(cls, **kwargs) -> None:
        del_keys = []
        for key, annotation in cls.__annotations__.items():
            if key.startswith("_"):  # exclude protected/private attributes
                continue
            if get_origin(annotation) is Annotated:
                if get_args(annotation)[1] == "ignore me"
                    cls._ignored[key] = cls.__annotations__[key]
                    del_keys.append(key)
        for key in del_keys:
            del cls.__annotations__[key]

class MyClass(IgnorableBaseModel):
    name: Annotated[str, "ignore me"]   # ignore this
    x: int

I am using this name for a variable that is defined during the __init__ and accessed via __getattr__ so I can't set a value for it. Is there a way to tell mypy to ignore this, or do I need to always override the init args like:

class MyClass(IgnorableBaseModel):
    name: Annotated[str, "ignore me"]
    x: int

    def __init__(self, x: int):
        self.x = x

It'd be great if there was an annotation I could add that would inform mypy that this variable was not needed during the init.

"ignore me" is used here to indicate that I don't want to see the error:

Missing named argument "name" for "MyClass"Mypycall-arg

To provide some background, I'm trying to make a python DSL and so it helps to be able to have some attributes type hinted, but not actually require values.


Solution

  • If you want name to be defined by __init__ without having to pass its value as an argument, use the init argument to Field.

    from typing import Annotated
    from pydantic import BaseModel, Field
    
    class MyClass(BaseModel):
        name: Annotated[str, Field(init=False)]
        x: int
    

    This can also be written as

    class MyClass(BaseModel):
        name: str = Field(init=False)
        x: int
    

    Now the autogenerated __init__ method will only expect an argument for x, and it will be up to use to ensure the name attribute on an instance of MyClass gets defined. (This could be by using a default value, or a default factory, or an explicitly defined __init__ method, or some other technique that Pydantic provides.)