new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Pydantic · API reference · all subjects

custom types

21 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Annotated pattern for custom types with constraints

The Annotated pattern can be used to create reusable types with constraints. For example, `PositiveInt = Annotated[int, Field(gt=0)]` creates a type that validates integers greater than 0. These types can be used with TypeAdapter for validation, as in `ta = TypeAdapter(PositiveInt)` and `ta.validate_python(1)`.

AfterValidator for custom validation and serialization

The AfterValidator marker can be used within Annotated to add custom validation logic. Example: `TruncatedFloat = Annotated[float, AfterValidator(lambda x: round(x, 1))]`. This validates the input first as the base type, then applies the provided function to the validated value.

PlainSerializer for custom serialization

PlainSerializer can be used with Annotated to define custom serialization behavior. Example: `Annotated[float, PlainSerializer(lambda x: f'{x:.1e}', return_type=str)]` will serialize floats as scientific notation strings.

WithJsonSchema marker for custom JSON schemas

WithJsonSchema can be used within Annotated to customize the JSON schema for a type. Example: `Annotated[float, WithJsonSchema({'type': 'string'}, mode='serialization')]` will generate a string type in the serialization JSON schema.

Type variables with Annotated constraints

Type variables can be used within Annotated types to create generic constrained types. Example: `ShortList = Annotated[list[T], Len(max_length=4)]` creates a type that validates lists of any type T with a maximum length of 4 items.

Named type aliases with TypeAliasType

Named type aliases can be created using TypeAliasType (Python 3.10+) or the `type` statement (Python 3.12+). Named aliases differ from implicit aliases in that they are properly represented as definitions in JSON Schema when used multiple times in a model, rather than having their definition duplicated.

Field-specific metadata in named type aliases not supported

Field-specific metadata such as `alias`, `default`, and `deprecated` cannot be used within named type aliases. Named aliases can only contain metadata that applies to the annotated type itself, such as validation constraints and JSON metadata. Using field-specific metadata in a named alias will cause an error.

Named recursive type aliases example

Recursive type aliases must use the named alias pattern. Example: `Json = TypeAliasType('Json', 'dict[str, Json] | list[Json] | str | int | float | bool | None')` creates a recursively-defined JSON type. The annotation must be wrapped in quotes for eager evaluation to be deferred.

__get_pydantic_core_schema__ method on custom types

Custom types can implement `__get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema` to customize validation. This method receives a source_type parameter and a handler callable, and must return a CoreSchema object defining how validation should work.

__get_pydantic_core_schema__ as annotation metadata

The `__get_pydantic_core_schema__` pattern can be implemented on metadata classes used within Annotated, allowing parametrized custom validation without requiring the result to be an instance of a custom class. The method signature is the same: `__get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema`.

Handling third-party types with custom annotations

Third-party types without Pydantic integration can be handled by creating a marker class with `__get_pydantic_core_schema__` and wrapping the type in an Annotated. This allows defining custom validation and serialization for types not under your control.

GetPydanticSchema for reducing boilerplate

GetPydanticSchema provides a simplified way to define custom schemas inline without creating a separate marker class. Example: `Annotated[str, GetPydanticSchema(lambda tp, handler: core_schema.no_info_after_validator_function(lambda x: x * 2, handler(tp)))]`.

Custom generic classes with __get_pydantic_core_schema__

Generic classes can use `__get_pydantic_core_schema__` with `get_args()` and `get_origin()` to extract type parameters and generate appropriate schemas. Use `handler.generate_schema()` to generate schemas for generic parameters instead of `handler()` to avoid context influence.

Field name access in __get_pydantic_core_schema__

As of Pydantic V2.4, the field name can be accessed via `handler.field_name` within `__get_pydantic_core_schema__`, and is available to validators through `info.field_name` in ValidationInfo.

Field name access in AfterValidator

Field name can be accessed from validators used with Annotated markers like AfterValidator. The field name is available via `info.field_name` in the ValidationInfo parameter passed to the validator function.

Pydantic provides built-in markers for common customizations

Pydantic provides high-level hooks to customize types via Annotated, such as AfterValidator, PlainSerializer, and Field. These should be preferred over lower-level approaches when possible.

core_schema module for advanced customization

Advanced customization uses pydantic_core's core_schema module directly. Common functions include core_schema.no_info_after_validator_function(), core_schema.is_instance_schema(), core_schema.chain_schema(), core_schema.union_schema(), and core_schema.json_or_python_schema().

Serialization info parameter in serializers

Both field and model serializers can optionally accept an extra info parameter providing useful information such as user-defined context, current serialization mode ('python' or 'json'), serialization parameters (exclude_unset, serialize_as_any), and for field serializers, the current field name.

Field serializer with context example

from pydantic import BaseModel, FieldSerializationInfo, field_serializer class Model(BaseModel): text: str @field_serializer('text', mode='plain') @classmethod def remove_stopwords(cls, v: str, info: FieldSerializationInfo) -> str: if isinstance(info.context, dict): stopwords = info.context.get('stopwords', set()) v = ' '.join(w for w in v.split() if w.lower() not in stopwords) return v model = Model(text='This is an example document') print(model.model_dump()) #> {'text': 'This is an example document'} print(model.model_dump(context={'stopwords': ['this', 'is', 'an']})) #> {'text': 'example document'}

Subclasses of supported types serialization

Subclasses of supported types (e.g., custom date subclass) are serialized according to their super class. The custom properties defined on the subclass are not included in serialization.

Custom date subclass serialization example

from datetime import date from pydantic import BaseModel class MyDate(date): @property def my_date_format(self) -> str: return self.strftime('%d/%m/%Y') class FooModel(BaseModel): date: date m = FooModel(date=MyDate(2023, 1, 1)) print(m.model_dump_json()) #> {"date":"2023-01-01"}

Give your agent this brain