Skip to content

A new marker API #12056

@Viicos

Description

@Viicos

In 2.12, we should be able to have #10961 to land. It provides a way to conditionally exclude a field from the serialized output:

class Model(BaseModel):
    a: int = Field(exclude_if=lambda v: v >= 0)

Model(a=1).model_dump()
#> {}

For now, the callable only takes one argument: the value to be serialized. This feature might get extended to have more info (e.g. the serialization context) provided. For instance, to exclude a field from the JSON output:

class Model(BaseModel):
    a: int = Field(exclude_if=lambda v, info: info.mode == 'json')

This is great, but is a bit unfortunate: the serialization mode is known by pydantic-core, and in theory we shouldn't need a Python call (which will hurt performance) to our lambda function to determine the exclusion of the field. We could introduce new exclude_* flags, such as exclude_if_json and exclude_if_python (we already have exclude_none fwiw), but this doesn't scale well (and we'll end up in a flag hell situation).

Introducing a new marker API

The API would roughly look like this:

class Marker(ABC): ...

@dataclass
class ModeIs(Marker):
    mode: Literal['python', 'json']


class Model(BaseModel):
    a: int = Field(exclude_if=ModeIs('json'))


Model(a=1).model_dump()
# {'a': 1}
Model(a=1).model_dump_json()
# {}

To avoid having to use a Python callable (which could hurt performance), similar flags could be introduced (that would be natively understood by pydantic-core):

@dataclass
class ValueIs(Marker):
    """Marker that compares a value by identity."""
    value: Any


my_sentinel = object()


class Model(BaseModel):
    a: object = Field(exclude_if=ValueIs(my_sentinel))

Model(a=my_sentinel).model_dump()
#> {}

Other use cases

exclude_if is one example of where this could be used, but could be used in other places:

class Model(BaseModel):
    my_field: int = Field(alias='myField')

    model_config = {
        'validate_by_name': ModeIs('python'),
        'validate_by_alias': ModeIs('json'),
    }

Model.model_validate({'myField': 1})  # Validation error
Model.model_validate_json('{"my_field": 1}')  # Validation error

Advanced operations

We could add operator support to these markers:

@dataclass
class Predicate(Marker):
    predicate: Callable[[Any], bool]

class Model(BaseModel):
    a: int = Field(exclude_if=ModeIs('json') & Predicate(lambda v: v >= 0))

Model(a=-1).model_dump_json()
# '{"a": -1}'
Model(a=1).model_dump_json()
# '{}'

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions