@validator is deprecated in Pydantic v2, replace with @field_validator. V1 pattern: from pydantic import validator; @validator('username'); def validate_username(cls, v): return v.lower(). V2 pattern: from pydantic import field_validator; @field_validator('username'); def validate_username(cls, v): return v.lower(). Key changes: (1) mode parameter replaces pre argument: @validator('field', pre=True) becomes @field_validator('field', mode='before'). (2) No config argument in v2 - use info.config instead: @field_validator('field'); def validate(cls, v, info): config = info.config. (3) No each_item argument - apply validators to type annotation instead: List[Annotated[str, validator]]. (4) Access other field values via info.data dict: def validate(cls, v, info): other = info.data.get('other_field'). Field metadata accessed via cls.model_fields[info.field_name]. V1 field argument (ModelField object) no longer exists. Deprecation: @validator works in v2.0+ with warnings, removed in v3.0. Essential for v2 migration - most common validation pattern.
Pydantic V2 Migration FAQ & Answers
50 expert Pydantic V2 Migration answers researched from official documentation. Every answer cites authoritative sources you can verify.
unknown
50 questions@root_validator is deprecated in Pydantic v2, replace with @model_validator. V1 pattern: from pydantic import root_validator; @root_validator(pre=True); def validate_root(cls, values): return values. V2 pattern: from pydantic import model_validator; @model_validator(mode='before'); def validate_root(cls, values): return values. Key changes: (1) mode='before' replaces pre=True, mode='after' replaces pre=False. (2) skip_on_failure removed - v2 validators always run. If keeping deprecated @root_validator, MUST set skip_on_failure=True explicitly (default False no longer supported). (3) mode='wrap' is new - gives full control: @model_validator(mode='wrap'); def validate(cls, values, handler): result = handler(values); return result. Returns ValidationInfo object for accessing context. Migration tool: from pydantic import field_validator, model_validator fixes imports automatically. Critical: validation order differs - after validators receive model instance, before validators receive dict. Deprecation timeline: v2.0 deprecated, v3.0 removed. Essential migration for complex validation logic.
Config inner class is deprecated in Pydantic v2, replace with model_config class attribute using ConfigDict. V1 pattern: from pydantic import BaseModel; class User(BaseModel): name: str; class Config: str_strip_whitespace = True; validate_assignment = True. V2 pattern: from pydantic import BaseModel, ConfigDict; class User(BaseModel): model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True); name: str. Key changes: (1) Use dict-based ConfigDict instead of class. (2) New config options: validate_default (replaces validate_all), from_attributes (replaces orm_mode), ser_json_inf_nan='strings'. (3) Removed options: fields, error_msg_templates (use custom errors instead). (4) json_encoders replaced by @field_serializer and @model_serializer decorators. (5) schema_extra replaced by json_schema_extra. Common configs: arbitrary_types_allowed, frozen, extra='forbid'|'allow'|'ignore'. Inheritance: child model_config merges with parent. Deprecation: Config class still works in v2 with warnings but removed in v3. Migration tool: pydantic.v1 compatibility layer available but not recommended. Essential change affecting every model definition.
model.dict() is deprecated in Pydantic v2, replace with model.model_dump(). V1: user_dict = user.dict(). V2: user_dict = user.model_dump(). Key differences: (1) Recursive conversion: model.model_dump() recursively converts nested models to dicts by default, dict(model) does not. (2) New mode parameter: model_dump(mode='json') serializes to JSON-compatible types (datetime → str, UUID → str), mode='python' (default) preserves Python types. (3) Exclude/include: model_dump(exclude={'password'}, include={'id', 'name'}). (4) by_alias parameter: model_dump(by_alias=True) uses field aliases. (5) exclude_none: model_dump(exclude_none=True) omits None values. (6) exclude_unset: model_dump(exclude_unset=True) omits fields not explicitly set. Alternative: dict(model) still works for simple dict conversion but lacks mode and recursive options. Warning: PydanticDeprecationWarning raised if using .dict(). Migration: search/replace .dict( with .model_dump( in codebase. Essential for serialization - used in every API response, database save, etc.
parse_obj() is deprecated in Pydantic v2, replace with model_validate(). V1: user = User.parse_obj({'id': 1, 'name': 'Alice'}). V2: user = User.model_validate({'id': 1, 'name': 'Alice'}). Key changes: (1) Same functionality - validates dict/object and returns model instance. (2) ORM mode: V1 from_orm() is deprecated, use model_validate() with from_attributes=True in model_config: model_config = ConfigDict(from_attributes=True); user = User.model_validate(orm_user). (3) Context parameter: model_validate(data, context={'user_id': 123}) passes context to validators accessed via info.context. (4) Strict mode: model_validate(data, strict=True) disables coercion (e.g., '123' won't convert to int 123). (5) Error handling: raises ValidationError same as v1. Return type: always model instance or raises. Alternative: model_validate_json() for JSON strings (replaces parse_raw()). Migration: parse_obj → model_validate, from_orm → model_validate with from_attributes=True config. Warning: parse_obj still works in v2 but deprecated, removed in v3. Essential for loading data from dicts, databases, APIs.
parse_raw() is deprecated in Pydantic v2, replace with model_validate_json(). V1: user = User.parse_raw('{"id": 1, "name": "Alice"}'). V2: user = User.model_validate_json('{"id": 1, "name": "Alice"}'). Key changes: (1) Parses JSON string directly to model instance. (2) Faster than json.loads() + model_validate() due to optimized Rust core. (3) Strict parameter: model_validate_json(data, strict=True) disables type coercion. (4) Context parameter: model_validate_json(data, context={...}) for validator context. (5) parse_file() also deprecated - read file manually then use model_validate_json(): with open('data.json') as f: user = User.model_validate_json(f.read()). Bytes support: model_validate_json(b'{...}') accepts bytes directly. Error handling: raises ValidationError on invalid JSON or validation failure. Performance: ~2-5x faster than json.loads + model_validate for large payloads due to pydantic-core Rust implementation. Migration: parse_raw → model_validate_json, parse_file → open + model_validate_json. Essential for API clients, webhook handlers, file processing.
@computed_field decorator makes properties serializable in Pydantic v2. V1 workaround (properties excluded from dict): class User(BaseModel): first: str; last: str; @property; def full_name(self): return f'{self.first} {self.last}'; user.dict() excludes full_name. V2 with @computed_field: from pydantic import computed_field; class User(BaseModel): first: str; last: str; @computed_field; @property; def full_name(self) -> str: return f'{self.first} {self.last}'; user.model_dump() includes full_name. Key features: (1) Serialized by default in model_dump(), model_dump_json(). (2) Included in JSON schema. (3) Type annotation required: @computed_field must specify return type. (4) Read-only: cannot be set during initialization. (5) Cached variant: use @computed_field with @cached_property for expensive computations. (6) Alias support: @computed_field(alias='fullName'). repr parameter: @computed_field(repr=False) excludes from repr. Use case: derived fields (full_name from first+last), formatted values, computed URLs. Replaces V1 Config.fields workaround. Essential for API responses including derived data.
Pydantic v2 separates input validation aliasing from output serialization aliasing. validation_alias: controls field name during parsing/validation. Example: from pydantic import Field; email: str = Field(validation_alias='emailAddress') accepts {'emailAddress': '[email protected]'} as input. serialization_alias: controls field name during serialization. Example: email: str = Field(serialization_alias='emailAddress') outputs {'emailAddress': '[email protected]'} from model_dump(by_alias=True). Precedence with alias: when using alias + validation_alias + serialization_alias together, validation_alias has priority for validation, serialization_alias has priority for serialization, alias is fallback. Example: email: str = Field(alias='email', validation_alias='emailAddress', serialization_alias='email_address') accepts emailAddress, outputs email_address. AliasChoices: validation_alias=AliasChoices('email', 'emailAddress') accepts either. AliasPath: validation_alias=AliasPath('user', 'contact', 'email') for nested paths. Use case: API expects camelCase, database uses snake_case: Field(validation_alias='userId', serialization_alias='user_id'). Essential for API compatibility without field name conflicts.
Discriminated unions use a discriminator field to determine which model to parse. Pattern: from pydantic import BaseModel, Field, Tag; from typing import Union, Literal; class Cat(BaseModel): pet_type: Literal['cat']; meow: str; class Dog(BaseModel): pet_type: Literal['dog']; bark: str; Pet = Annotated[Union[Cat, Dog], Field(discriminator='pet_type')]. Parse: pet = Pet.model_validate({'pet_type': 'cat', 'meow': 'loud'}) returns Cat instance. JSON schema: includes OpenAPI discriminator mapping. Benefits: (1) Faster parsing - checks discriminator first, only tries one model. (2) Better errors - "pet_type='cat' but Dog expected" instead of trying all unions. (3) OpenAPI compatible - generates proper oneOf with discriminator. Alias support: discriminator works with alias='petType'. String discriminator: most common. Callable discriminator: def get_type(v): return v['type']; Field(discriminator=get_type). Limitations: discriminator field must be Literal type in each model. Use case: polymorphic API responses, event types, message queues. V1 had discriminator but V2 improves performance and error messages. Essential for type-safe polymorphic models.
@field_serializer customizes individual field serialization, @model_serializer customizes entire model. field_serializer example: from pydantic import field_serializer; class User(BaseModel): created_at: datetime; @field_serializer('created_at'); def serialize_dt(self, value): return value.isoformat(). Replaces v1 json_encoders. mode parameter: @field_serializer('amount', mode='plain') for JSON mode only, mode='wrap' wraps default serializer: @field_serializer('name', mode='wrap'); def wrap_name(self, value, handler): return handler(value).upper(). model_serializer example: @model_serializer; def serialize_model(self): return {'id': self.id, 'custom': 'format'}. mode='wrap': @model_serializer(mode='wrap'); def wrap_model(self, handler): data = handler(self); data['extra'] = 'field'; return data. return_type: @model_serializer(return_type=dict) specifies return type. when_used: @field_serializer('field', when_used='json') only for JSON serialization. Multiple fields: @field_serializer('field1', 'field2') applies to both. Replaces v1 Config.json_encoders entirely. Use case: datetime formatting, UUID to string, custom JSON schema. Essential for API compatibility and custom formats.
TypeAdapter validates standalone types without creating BaseModel classes. Pattern: from pydantic import TypeAdapter; UserList = TypeAdapter(List[User]); users = UserList.validate_python([{'id': 1}, {'id': 2}]). Use cases: (1) Validate primitives: IntAdapter = TypeAdapter(int); result = IntAdapter.validate_python('123') returns 123. (2) Validate collections: TypeAdapter(Dict[str, int]).validate_python({'a': '1'}) coerces values. (3) Validate Union types: TypeAdapter(Union[int, str]). (4) Custom types with validators: from typing import Annotated; PositiveInt = Annotated[int, Field(gt=0)]; TypeAdapter(PositiveInt).validate_python(5). Methods: validate_python(data) parses Python objects, validate_json(json_str) parses JSON, dump_python(obj) serializes, dump_json(obj) to JSON bytes. JSON schema: adapter.json_schema() generates schema. Strict mode: adapter.validate_python(data, strict=True). Replaces v1 parse_obj_as and parse_raw_as. Core difference from BaseModel: no class definition needed, validates any type. Essential for validating function arguments, API parameters, configuration without model overhead. Performance: slightly faster than BaseModel for simple types.
Private attributes use underscore prefix but require PrivateAttr for initialization. V2 pattern: from pydantic import BaseModel, PrivateAttr; class User(BaseModel): name: str; _internal_id: str = PrivateAttr(default='auto'). Key rules: (1) Single underscore prefix (_field) marks private. (2) Must use PrivateAttr() for default values, not regular assignment. (3) Not validated - no type checking. (4) Excluded from model_dump(), model_dump_json(), JSON schema. (5) Can be set after initialization: user._internal_id = 'new_value'. (6) Default factory: _created: datetime = PrivateAttr(default_factory=datetime.now). V1 difference: V1 used fields with underscore prefix implicitly, V2 requires explicit PrivateAttr(). Double underscore prefix (__field) not recommended - use single underscore. Access: user._internal_id, not user.internal_id. Use case: caching, internal state, computed values not for serialization. Common pattern: _cache: dict = PrivateAttr(default_factory=dict) for memoization. Not included in model_validate() input. Essential for maintaining internal state without exposing in API.
mode='wrap' in @model_validator gives full control over validation flow. Pattern: from pydantic import model_validator; @model_validator(mode='wrap'); def validate_wrapper(cls, values, handler): # pre-processing; result = handler(values); # runs default validation; # post-processing; return result. handler() triggers default Pydantic validation. Use cases: (1) Logging: @model_validator(mode='wrap'); def log_validation(cls, values, handler): logger.info('Validating'); try: return handler(values); except ValidationError as e: logger.error(e); raise. (2) Transformation before/after: def wrap(cls, values, handler): values = preprocess(values); result = handler(values); result.custom_attr = 'added'; return result. (3) Conditional validation: def wrap(cls, values, handler): if values.get('skip_validation'): return cls(**values); return handler(values). (4) Caching: cache key based on input, skip validation if cached. handler parameter: callable that accepts values dict, returns model instance. Error handling: catch ValidationError from handler, add context, re-raise. Difference from mode='before'/'after': wrap has control to skip handler entirely. Essential for complex validation workflows, debugging, conditional logic.
Pydantic v2 JSON schema generation has multiple breaking changes from v1. Key changes: (1) method: model.model_json_schema() replaces model.schema(). (2) OpenAPI 3.1 by default (was 3.0 in v1) - uses JSON Schema 2020-12. Mode parameter: model_json_schema(mode='validation') for input schema, mode='serialization') for output schema. (3) by_alias: model_json_schema(by_alias=True) uses field aliases in schema. (4) ref_template: controls $ref format, default '${model}'. (5) schema_generator: custom GenerateJsonSchema class for full control. (6) $defs location: definitions moved to top-level $defs (was definitions in v1). (7) Discriminator: properly generates OpenAPI discriminator for unions. (8) Config.schema_extra replaced by model_config = ConfigDict(json_schema_extra={...}) or @model_serializer. Custom schema: def json_schema_extra(schema, model): schema['examples'] = [...]; ConfigDict(json_schema_extra=json_schema_extra). Breaking: additionalProperties handling changed, enum representation changed, date-time format stricter. Use case: OpenAPI spec generation, API documentation, validation tools. Migration: update schema() calls to model_json_schema(), update Config.schema_extra to json_schema_extra.
Strict mode disables type coercion in Pydantic v2. Global strict mode: model_config = ConfigDict(strict=True). Per-field: age: int = Field(strict=True). Runtime: model.model_validate(data, strict=True). Behavior: (1) Strict int: '123' raises error (v1 coerces to 123). (2) Strict str: 123 raises error (v1 coerces to '123'). (3) Strict bool: 1 raises error (v1 coerces to True). (4) Strict datetime: string raises error (v1 parses ISO strings). Type-specific: from pydantic import StrictInt, StrictStr, StrictBool; age: StrictInt validates only int, no coercion. Example: class User(BaseModel): id: StrictInt; name: str. User.model_validate({'id': '123', 'name': 'Alice'}) raises ValidationError on id. Mixed: class User(BaseModel): id: StrictInt; age: int; accepts age='25' (coerces) but rejects id='123'. JSON mode: model_validate_json enables coercion even in strict mode for JSON-native types (JSON doesn't distinguish int/float). Use case: API validation where '123' is wrong input, type-safe parsing, preventing bugs from implicit conversions. Essential for strict APIs and data integrity.
Forward references occur when models reference not-yet-defined classes. V2 pattern: from typing import ForwardRef; class User(BaseModel): friends: List['User']. Auto-resolution: v2 automatically resolves forward refs at model creation. Manual rebuild: User.model_rebuild() forces re-resolution after class is defined. update_forward_refs removed: v1 method deprecated, use model_rebuild() instead. Use cases: (1) Circular references: class Node(BaseModel): children: List['Node']; Node.model_rebuild(). (2) Conditional imports: if TYPE_CHECKING: from other import Other; class Model(BaseModel): ref: 'Other'. (3) Generic models: T = TypeVar('T'); class Container(BaseModel, Generic[T]): item: T; Container[User].model_rebuild(). String annotations: from future import annotations enables automatic string annotations. rebuild_with_context: from pydantic import _model_construction; User.model_rebuild(_types_namespace={'CustomType': CustomType}). Force rebuild: User.model_rebuild(force=True) even if already built. Validation: forward refs must resolve before model_validate. Error: NameError if forward ref unresolved. Essential for complex model hierarchies and plugin systems.
Pydantic v2 is 5-50x faster than v1 due to Rust core (pydantic-core). Key optimizations: (1) Rust validation core: written in Rust using PyO3, compiled to native code. (2) Lazy schema building: schemas built on first use, not at class definition. (3) Optimized JSON parsing: model_validate_json() uses faster Rust JSON parser (jiter). 2-5x faster than json.loads + validate. (4) Efficient field access: pydantic_fields_set tracks which fields were set during init. (5) String interning: repeated string validation uses interned strings. (6) TypeAdapter optimization: validates simple types without BaseModel overhead. (7) Cached validators: compiled validators cached per type. Benchmarks: simple model validation ~10-20x faster, complex nested models ~5-10x faster, JSON parsing ~2-5x faster. Performance tips: (1) use model_validate_json for JSON input, (2) use TypeAdapter for simple types, (3) avoid mode='wrap' validators (slower), (4) use computed_field with @cached_property for expensive computations. Memory: v2 uses slightly more memory for schema caching but faster CPU. Profiling: PYDANTIC_PROFILE=1 enables profiling. Essential: upgrade to v2 for production performance.
bump-pydantic is official migration tool for automated v1 to v2 conversion. Install: pip install bump-pydantic. Usage: bump-pydantic path/to/code. Automated changes: (1) Imports: pydantic.validator → pydantic.field_validator, pydantic.root_validator → pydantic.model_validator. (2) Methods: .dict() → .model_dump(), .parse_obj() → .model_validate(), .schema() → .model_json_schema(). (3) Config class: class Config → model_config = ConfigDict(). (4) Decorators: @validator → @field_validator, @root_validator(pre=True) → @model_validator(mode='before'). Flags: --diff shows changes without applying, --disable checks comma-separated rules. Example: bump-pydantic --diff src/. Limitations: (1) Cannot migrate complex validators automatically. (2) May not handle all edge cases (each_item, custom json_encoders). (3) Requires manual review of changes. Workflow: (1) Run with --diff. (2) Review changes. (3) Apply: bump-pydantic src/. (4) Run tests. (5) Fix remaining issues manually. GitHub: https://github.com/pydantic/bump-pydantic. Use case: large codebases with hundreds of models. Essential for minimizing manual migration effort.
Pydantic v2 ValidationError has different structure and behavior. Error format changes: (1) loc: error location tuple, e.g. ('field', 0, 'subfield'). (2) msg: human-readable message. (3) type: error code like 'int_parsing', 'missing', 'string_too_short'. (4) ctx: additional context dict (e.g. {'max_length': 10}). V2 improvements: more specific error types (v1 had generic 'type_error.integer'), better messages, localized errors (error.errors(include_url=True) adds doc links). Error codes: v2 has 100+ specific codes vs v1's ~30. Example: v1 'type_error.integer', v2 'int_parsing', 'int_from_float'. Custom errors: from pydantic_core import PydanticCustomError; raise PydanticCustomError('custom_error', 'message', {'ctx': 'value'}). Error dict: exc.errors() returns list[dict]. JSON: exc.json() for JSON string. Error count: exc.error_count(). URL in errors: include_url=False by default (v1 always included). Breaking: error type strings changed - update error handling code. Migration: search for error.type checks, update to new error codes. Use case: API error responses, user-friendly messages, error monitoring. Essential for proper error handling in v2.
RootModel validates primitives, lists, dicts without wrapping in object. Pattern: from pydantic import RootModel; UserList = RootModel[List[User]]. Replaces v1 root pattern. V1: class UserList(BaseModel): root: List[User]. V2: class UserList(RootModel): root: List[User]. Access: users = UserList.model_validate([{'id': 1}]); users.root returns list. Iteration: class UserList(RootModel[List[User]]): def iter(self): return iter(self.root); for user in users: print(user). Index access: def getitem(self, item): return self.root[item]; users[0]. Primitives: class StringModel(RootModel[str]): pass; model = StringModel.model_validate('hello'). Dict root: class ConfigModel(RootModel[Dict[str, Any]]): pass. Validation: add model_validator to RootModel class. Serialization: model.model_dump() returns root value directly (list/dict/primitive), not wrapped object. JSON schema: generates schema for root type, not object wrapper. Use case: API accepting arrays, validating config dicts, typed primitives. Migration: replace root with RootModel[T]. Essential for clean validation of non-object types.
Annotated from typing combines type with metadata in Pydantic v2. Pattern: from typing import Annotated; from pydantic import Field; Username = Annotated[str, Field(min_length=3, max_length=20)]. Use in model: class User(BaseModel): username: Username. Equivalent to: username: str = Field(min_length=3, max_length=20). Benefits: (1) Reusable types: define once, use everywhere. (2) Type aliases with constraints: PositiveInt = Annotated[int, Field(gt=0)]. (3) Multiple constraints: Annotated[str, Field(min_length=1), Field(pattern=r'[a-z]')]. (4) Custom validators: Annotated[str, AfterValidator(lambda x: x.lower())]. (5) Clearer than Union: Annotated[int | str, Field(description='ID')] vs Field on union. Stacking: Email = Annotated[str, Field(pattern=r'.+@.+')]; StrictEmail = Annotated[Email, Field(max_length=100)]. Custom metadata: Annotated[str, MyCustomMetadata('value')]. Validators: from pydantic import AfterValidator, BeforeValidator; Annotated[int, BeforeValidator(int), AfterValidator(lambda x: abs(x))]. Use case: domain types (Email, URL, PositiveInt), reusable validation, API schemas. V2 recommendation: prefer Annotated over Field assignment. Essential for clean, reusable type definitions.
pydantic.v1 module provides v1 API compatibility in Pydantic v2 installations. Import: from pydantic.v1 import BaseModel, validator. Allows running v1 code unchanged with v2 installed. Use cases: (1) Gradual migration: migrate codebase incrementally. (2) Dependencies: third-party libraries still using v1 API. (3) Temporary compatibility: keep old code working during migration. Example: from pydantic.v1 import BaseModel, validator; class User(BaseModel): name: str; @validator('name'); def validate_name(cls, v): return v.lower(). Limitations: (1) Performance: uses v1 validation logic, not fast Rust core. (2) No v2 features: cannot use model_dump, field_validator in v1 models. (3) Mixing: cannot mix v1 and v2 models easily (inheritance issues). (4) Deprecated: will be removed in future Pydantic version. Best practice: use only during migration, not long-term. Migration path: (1) Install v2, import from pydantic.v1. (2) Migrate models incrementally to pydantic. (3) Remove pydantic.v1 imports when done. Not recommended: new projects should use v2 API directly. Essential for large codebases needing gradual migration.
extra parameter in model_config controls how Pydantic handles fields not defined in model. Three modes: forbid, allow, ignore. Forbid (strictest): model_config = ConfigDict(extra='forbid'). Raises ValidationError if extra fields present. User.model_validate({'name': 'Alice', 'age': 30}) raises error if age not defined. Use case: strict APIs, prevent typos. Allow: model_config = ConfigDict(extra='allow'). Accepts extra fields, stores in model. Access: user.age or user.pydantic_extra dict. Serialization: extra fields included in model_dump(). Validation: extra fields not validated. Use case: dynamic fields, passthrough data. Ignore (default): model_config = ConfigDict(extra='ignore'). Silently ignores extra fields. Not stored, not accessible. Use case: API versioning, backward compatibility. V1 equivalent: class Config: extra = Extra.forbid|allow|ignore. Migration: Extra enum → string 'forbid'|'allow'|'ignore'. Field-level: no field-level control, model-level only. Inheritance: child inherits parent's extra setting unless overridden. JSON schema: extra='forbid' adds additionalProperties: false. Essential for controlling API contract strictness.
validate_default=True forces validation of default values in Pydantic v2. Config: model_config = ConfigDict(validate_default=True). V1 equivalent: validate_all=True (renamed in v2). Behavior without: class User(BaseModel): status: str = 'active'. Default 'active' not validated, even if invalid. Behavior with: model_config = ConfigDict(validate_default=True); class User(BaseModel): status: Literal['active', 'inactive'] = 'pending'. Raises ValidationError at class definition if default invalid. Per-field: age: int = Field(default=0, validate_default=True). Use cases: (1) Ensure defaults meet constraints: Field(default=5, gt=0) validated. (2) Catch bugs: default='abc' with int type raises error early. (3) Default factories: default_factory=lambda: {...}, validates result. Example: created_at: datetime = Field(default_factory=datetime.now, validate_default=True). Validation timing: at class definition time for literals, at instance creation for factories. Performance: slight overhead validating defaults. V1 migration: validate_all=True → validate_default=True. Recommendation: enable in dev/test, optional in prod. Essential for catching invalid defaults early.
Common Pydantic v2 migration errors and fixes: (1) AttributeError: 'BaseModel' object has no attribute 'dict'. Fix: replace .dict() with .model_dump(). (2) AttributeError: 'BaseModel' object has no attribute 'parse_obj'. Fix: User.parse_obj(data) → User.model_validate(data). (3) PydanticUserError: '@validator' is deprecated. Fix: @validator → @field_validator, update arguments (values → info.data). (4) PydanticUserError: '@root_validator' with pre=False must specify skip_on_failure=True. Fix: migrate to @model_validator(mode='after'). (5) TypeError: ConfigDict() got unexpected keyword argument 'fields'. Fix: remove fields from config, use model_fields class attribute. (6) PydanticUserError: 'Config' class is deprecated. Fix: class Config → model_config = ConfigDict(). (7) ImportError: cannot import 'ConstrainedStr'. Fix: use Annotated[str, Field(min_length=...)] instead. (8) ValidationError: wrong error type codes. Fix: update error handling to use new type codes (int_parsing not type_error.integer). (9) AttributeError: 'from_orm' not found. Fix: model_validate with from_attributes=True config. (10) PydanticSchemaGenerationError with forward refs. Fix: call model_rebuild() after class definition. Use bump-pydantic for automated fixes.
@validator is deprecated in Pydantic v2, replace with @field_validator. V1 pattern: from pydantic import validator; @validator('username'); def validate_username(cls, v): return v.lower(). V2 pattern: from pydantic import field_validator; @field_validator('username'); def validate_username(cls, v): return v.lower(). Key changes: (1) mode parameter replaces pre argument: @validator('field', pre=True) becomes @field_validator('field', mode='before'). (2) No config argument in v2 - use info.config instead: @field_validator('field'); def validate(cls, v, info): config = info.config. (3) No each_item argument - apply validators to type annotation instead: List[Annotated[str, validator]]. (4) Access other field values via info.data dict: def validate(cls, v, info): other = info.data.get('other_field'). Field metadata accessed via cls.model_fields[info.field_name]. V1 field argument (ModelField object) no longer exists. Deprecation: @validator works in v2.0+ with warnings, removed in v3.0. Essential for v2 migration - most common validation pattern.
@root_validator is deprecated in Pydantic v2, replace with @model_validator. V1 pattern: from pydantic import root_validator; @root_validator(pre=True); def validate_root(cls, values): return values. V2 pattern: from pydantic import model_validator; @model_validator(mode='before'); def validate_root(cls, values): return values. Key changes: (1) mode='before' replaces pre=True, mode='after' replaces pre=False. (2) skip_on_failure removed - v2 validators always run. If keeping deprecated @root_validator, MUST set skip_on_failure=True explicitly (default False no longer supported). (3) mode='wrap' is new - gives full control: @model_validator(mode='wrap'); def validate(cls, values, handler): result = handler(values); return result. Returns ValidationInfo object for accessing context. Migration tool: from pydantic import field_validator, model_validator fixes imports automatically. Critical: validation order differs - after validators receive model instance, before validators receive dict. Deprecation timeline: v2.0 deprecated, v3.0 removed. Essential migration for complex validation logic.
Config inner class is deprecated in Pydantic v2, replace with model_config class attribute using ConfigDict. V1 pattern: from pydantic import BaseModel; class User(BaseModel): name: str; class Config: str_strip_whitespace = True; validate_assignment = True. V2 pattern: from pydantic import BaseModel, ConfigDict; class User(BaseModel): model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True); name: str. Key changes: (1) Use dict-based ConfigDict instead of class. (2) New config options: validate_default (replaces validate_all), from_attributes (replaces orm_mode), ser_json_inf_nan='strings'. (3) Removed options: fields, error_msg_templates (use custom errors instead). (4) json_encoders replaced by @field_serializer and @model_serializer decorators. (5) schema_extra replaced by json_schema_extra. Common configs: arbitrary_types_allowed, frozen, extra='forbid'|'allow'|'ignore'. Inheritance: child model_config merges with parent. Deprecation: Config class still works in v2 with warnings but removed in v3. Migration tool: pydantic.v1 compatibility layer available but not recommended. Essential change affecting every model definition.
model.dict() is deprecated in Pydantic v2, replace with model.model_dump(). V1: user_dict = user.dict(). V2: user_dict = user.model_dump(). Key differences: (1) Recursive conversion: model.model_dump() recursively converts nested models to dicts by default, dict(model) does not. (2) New mode parameter: model_dump(mode='json') serializes to JSON-compatible types (datetime → str, UUID → str), mode='python' (default) preserves Python types. (3) Exclude/include: model_dump(exclude={'password'}, include={'id', 'name'}). (4) by_alias parameter: model_dump(by_alias=True) uses field aliases. (5) exclude_none: model_dump(exclude_none=True) omits None values. (6) exclude_unset: model_dump(exclude_unset=True) omits fields not explicitly set. Alternative: dict(model) still works for simple dict conversion but lacks mode and recursive options. Warning: PydanticDeprecationWarning raised if using .dict(). Migration: search/replace .dict( with .model_dump( in codebase. Essential for serialization - used in every API response, database save, etc.
parse_obj() is deprecated in Pydantic v2, replace with model_validate(). V1: user = User.parse_obj({'id': 1, 'name': 'Alice'}). V2: user = User.model_validate({'id': 1, 'name': 'Alice'}). Key changes: (1) Same functionality - validates dict/object and returns model instance. (2) ORM mode: V1 from_orm() is deprecated, use model_validate() with from_attributes=True in model_config: model_config = ConfigDict(from_attributes=True); user = User.model_validate(orm_user). (3) Context parameter: model_validate(data, context={'user_id': 123}) passes context to validators accessed via info.context. (4) Strict mode: model_validate(data, strict=True) disables coercion (e.g., '123' won't convert to int 123). (5) Error handling: raises ValidationError same as v1. Return type: always model instance or raises. Alternative: model_validate_json() for JSON strings (replaces parse_raw()). Migration: parse_obj → model_validate, from_orm → model_validate with from_attributes=True config. Warning: parse_obj still works in v2 but deprecated, removed in v3. Essential for loading data from dicts, databases, APIs.
parse_raw() is deprecated in Pydantic v2, replace with model_validate_json(). V1: user = User.parse_raw('{"id": 1, "name": "Alice"}'). V2: user = User.model_validate_json('{"id": 1, "name": "Alice"}'). Key changes: (1) Parses JSON string directly to model instance. (2) Faster than json.loads() + model_validate() due to optimized Rust core. (3) Strict parameter: model_validate_json(data, strict=True) disables type coercion. (4) Context parameter: model_validate_json(data, context={...}) for validator context. (5) parse_file() also deprecated - read file manually then use model_validate_json(): with open('data.json') as f: user = User.model_validate_json(f.read()). Bytes support: model_validate_json(b'{...}') accepts bytes directly. Error handling: raises ValidationError on invalid JSON or validation failure. Performance: ~2-5x faster than json.loads + model_validate for large payloads due to pydantic-core Rust implementation. Migration: parse_raw → model_validate_json, parse_file → open + model_validate_json. Essential for API clients, webhook handlers, file processing.
@computed_field decorator makes properties serializable in Pydantic v2. V1 workaround (properties excluded from dict): class User(BaseModel): first: str; last: str; @property; def full_name(self): return f'{self.first} {self.last}'; user.dict() excludes full_name. V2 with @computed_field: from pydantic import computed_field; class User(BaseModel): first: str; last: str; @computed_field; @property; def full_name(self) -> str: return f'{self.first} {self.last}'; user.model_dump() includes full_name. Key features: (1) Serialized by default in model_dump(), model_dump_json(). (2) Included in JSON schema. (3) Type annotation required: @computed_field must specify return type. (4) Read-only: cannot be set during initialization. (5) Cached variant: use @computed_field with @cached_property for expensive computations. (6) Alias support: @computed_field(alias='fullName'). repr parameter: @computed_field(repr=False) excludes from repr. Use case: derived fields (full_name from first+last), formatted values, computed URLs. Replaces V1 Config.fields workaround. Essential for API responses including derived data.
Pydantic v2 separates input validation aliasing from output serialization aliasing. validation_alias: controls field name during parsing/validation. Example: from pydantic import Field; email: str = Field(validation_alias='emailAddress') accepts {'emailAddress': '[email protected]'} as input. serialization_alias: controls field name during serialization. Example: email: str = Field(serialization_alias='emailAddress') outputs {'emailAddress': '[email protected]'} from model_dump(by_alias=True). Precedence with alias: when using alias + validation_alias + serialization_alias together, validation_alias has priority for validation, serialization_alias has priority for serialization, alias is fallback. Example: email: str = Field(alias='email', validation_alias='emailAddress', serialization_alias='email_address') accepts emailAddress, outputs email_address. AliasChoices: validation_alias=AliasChoices('email', 'emailAddress') accepts either. AliasPath: validation_alias=AliasPath('user', 'contact', 'email') for nested paths. Use case: API expects camelCase, database uses snake_case: Field(validation_alias='userId', serialization_alias='user_id'). Essential for API compatibility without field name conflicts.
Discriminated unions use a discriminator field to determine which model to parse. Pattern: from pydantic import BaseModel, Field, Tag; from typing import Union, Literal; class Cat(BaseModel): pet_type: Literal['cat']; meow: str; class Dog(BaseModel): pet_type: Literal['dog']; bark: str; Pet = Annotated[Union[Cat, Dog], Field(discriminator='pet_type')]. Parse: pet = Pet.model_validate({'pet_type': 'cat', 'meow': 'loud'}) returns Cat instance. JSON schema: includes OpenAPI discriminator mapping. Benefits: (1) Faster parsing - checks discriminator first, only tries one model. (2) Better errors - "pet_type='cat' but Dog expected" instead of trying all unions. (3) OpenAPI compatible - generates proper oneOf with discriminator. Alias support: discriminator works with alias='petType'. String discriminator: most common. Callable discriminator: def get_type(v): return v['type']; Field(discriminator=get_type). Limitations: discriminator field must be Literal type in each model. Use case: polymorphic API responses, event types, message queues. V1 had discriminator but V2 improves performance and error messages. Essential for type-safe polymorphic models.
@field_serializer customizes individual field serialization, @model_serializer customizes entire model. field_serializer example: from pydantic import field_serializer; class User(BaseModel): created_at: datetime; @field_serializer('created_at'); def serialize_dt(self, value): return value.isoformat(). Replaces v1 json_encoders. mode parameter: @field_serializer('amount', mode='plain') for JSON mode only, mode='wrap' wraps default serializer: @field_serializer('name', mode='wrap'); def wrap_name(self, value, handler): return handler(value).upper(). model_serializer example: @model_serializer; def serialize_model(self): return {'id': self.id, 'custom': 'format'}. mode='wrap': @model_serializer(mode='wrap'); def wrap_model(self, handler): data = handler(self); data['extra'] = 'field'; return data. return_type: @model_serializer(return_type=dict) specifies return type. when_used: @field_serializer('field', when_used='json') only for JSON serialization. Multiple fields: @field_serializer('field1', 'field2') applies to both. Replaces v1 Config.json_encoders entirely. Use case: datetime formatting, UUID to string, custom JSON schema. Essential for API compatibility and custom formats.
TypeAdapter validates standalone types without creating BaseModel classes. Pattern: from pydantic import TypeAdapter; UserList = TypeAdapter(List[User]); users = UserList.validate_python([{'id': 1}, {'id': 2}]). Use cases: (1) Validate primitives: IntAdapter = TypeAdapter(int); result = IntAdapter.validate_python('123') returns 123. (2) Validate collections: TypeAdapter(Dict[str, int]).validate_python({'a': '1'}) coerces values. (3) Validate Union types: TypeAdapter(Union[int, str]). (4) Custom types with validators: from typing import Annotated; PositiveInt = Annotated[int, Field(gt=0)]; TypeAdapter(PositiveInt).validate_python(5). Methods: validate_python(data) parses Python objects, validate_json(json_str) parses JSON, dump_python(obj) serializes, dump_json(obj) to JSON bytes. JSON schema: adapter.json_schema() generates schema. Strict mode: adapter.validate_python(data, strict=True). Replaces v1 parse_obj_as and parse_raw_as. Core difference from BaseModel: no class definition needed, validates any type. Essential for validating function arguments, API parameters, configuration without model overhead. Performance: slightly faster than BaseModel for simple types.
Private attributes use underscore prefix but require PrivateAttr for initialization. V2 pattern: from pydantic import BaseModel, PrivateAttr; class User(BaseModel): name: str; _internal_id: str = PrivateAttr(default='auto'). Key rules: (1) Single underscore prefix (_field) marks private. (2) Must use PrivateAttr() for default values, not regular assignment. (3) Not validated - no type checking. (4) Excluded from model_dump(), model_dump_json(), JSON schema. (5) Can be set after initialization: user._internal_id = 'new_value'. (6) Default factory: _created: datetime = PrivateAttr(default_factory=datetime.now). V1 difference: V1 used fields with underscore prefix implicitly, V2 requires explicit PrivateAttr(). Double underscore prefix (__field) not recommended - use single underscore. Access: user._internal_id, not user.internal_id. Use case: caching, internal state, computed values not for serialization. Common pattern: _cache: dict = PrivateAttr(default_factory=dict) for memoization. Not included in model_validate() input. Essential for maintaining internal state without exposing in API.
mode='wrap' in @model_validator gives full control over validation flow. Pattern: from pydantic import model_validator; @model_validator(mode='wrap'); def validate_wrapper(cls, values, handler): # pre-processing; result = handler(values); # runs default validation; # post-processing; return result. handler() triggers default Pydantic validation. Use cases: (1) Logging: @model_validator(mode='wrap'); def log_validation(cls, values, handler): logger.info('Validating'); try: return handler(values); except ValidationError as e: logger.error(e); raise. (2) Transformation before/after: def wrap(cls, values, handler): values = preprocess(values); result = handler(values); result.custom_attr = 'added'; return result. (3) Conditional validation: def wrap(cls, values, handler): if values.get('skip_validation'): return cls(**values); return handler(values). (4) Caching: cache key based on input, skip validation if cached. handler parameter: callable that accepts values dict, returns model instance. Error handling: catch ValidationError from handler, add context, re-raise. Difference from mode='before'/'after': wrap has control to skip handler entirely. Essential for complex validation workflows, debugging, conditional logic.
Pydantic v2 JSON schema generation has multiple breaking changes from v1. Key changes: (1) method: model.model_json_schema() replaces model.schema(). (2) OpenAPI 3.1 by default (was 3.0 in v1) - uses JSON Schema 2020-12. Mode parameter: model_json_schema(mode='validation') for input schema, mode='serialization') for output schema. (3) by_alias: model_json_schema(by_alias=True) uses field aliases in schema. (4) ref_template: controls $ref format, default '${model}'. (5) schema_generator: custom GenerateJsonSchema class for full control. (6) $defs location: definitions moved to top-level $defs (was definitions in v1). (7) Discriminator: properly generates OpenAPI discriminator for unions. (8) Config.schema_extra replaced by model_config = ConfigDict(json_schema_extra={...}) or @model_serializer. Custom schema: def json_schema_extra(schema, model): schema['examples'] = [...]; ConfigDict(json_schema_extra=json_schema_extra). Breaking: additionalProperties handling changed, enum representation changed, date-time format stricter. Use case: OpenAPI spec generation, API documentation, validation tools. Migration: update schema() calls to model_json_schema(), update Config.schema_extra to json_schema_extra.
Strict mode disables type coercion in Pydantic v2. Global strict mode: model_config = ConfigDict(strict=True). Per-field: age: int = Field(strict=True). Runtime: model.model_validate(data, strict=True). Behavior: (1) Strict int: '123' raises error (v1 coerces to 123). (2) Strict str: 123 raises error (v1 coerces to '123'). (3) Strict bool: 1 raises error (v1 coerces to True). (4) Strict datetime: string raises error (v1 parses ISO strings). Type-specific: from pydantic import StrictInt, StrictStr, StrictBool; age: StrictInt validates only int, no coercion. Example: class User(BaseModel): id: StrictInt; name: str. User.model_validate({'id': '123', 'name': 'Alice'}) raises ValidationError on id. Mixed: class User(BaseModel): id: StrictInt; age: int; accepts age='25' (coerces) but rejects id='123'. JSON mode: model_validate_json enables coercion even in strict mode for JSON-native types (JSON doesn't distinguish int/float). Use case: API validation where '123' is wrong input, type-safe parsing, preventing bugs from implicit conversions. Essential for strict APIs and data integrity.
Forward references occur when models reference not-yet-defined classes. V2 pattern: from typing import ForwardRef; class User(BaseModel): friends: List['User']. Auto-resolution: v2 automatically resolves forward refs at model creation. Manual rebuild: User.model_rebuild() forces re-resolution after class is defined. update_forward_refs removed: v1 method deprecated, use model_rebuild() instead. Use cases: (1) Circular references: class Node(BaseModel): children: List['Node']; Node.model_rebuild(). (2) Conditional imports: if TYPE_CHECKING: from other import Other; class Model(BaseModel): ref: 'Other'. (3) Generic models: T = TypeVar('T'); class Container(BaseModel, Generic[T]): item: T; Container[User].model_rebuild(). String annotations: from future import annotations enables automatic string annotations. rebuild_with_context: from pydantic import _model_construction; User.model_rebuild(_types_namespace={'CustomType': CustomType}). Force rebuild: User.model_rebuild(force=True) even if already built. Validation: forward refs must resolve before model_validate. Error: NameError if forward ref unresolved. Essential for complex model hierarchies and plugin systems.
Pydantic v2 is 5-50x faster than v1 due to Rust core (pydantic-core). Key optimizations: (1) Rust validation core: written in Rust using PyO3, compiled to native code. (2) Lazy schema building: schemas built on first use, not at class definition. (3) Optimized JSON parsing: model_validate_json() uses faster Rust JSON parser (jiter). 2-5x faster than json.loads + validate. (4) Efficient field access: pydantic_fields_set tracks which fields were set during init. (5) String interning: repeated string validation uses interned strings. (6) TypeAdapter optimization: validates simple types without BaseModel overhead. (7) Cached validators: compiled validators cached per type. Benchmarks: simple model validation ~10-20x faster, complex nested models ~5-10x faster, JSON parsing ~2-5x faster. Performance tips: (1) use model_validate_json for JSON input, (2) use TypeAdapter for simple types, (3) avoid mode='wrap' validators (slower), (4) use computed_field with @cached_property for expensive computations. Memory: v2 uses slightly more memory for schema caching but faster CPU. Profiling: PYDANTIC_PROFILE=1 enables profiling. Essential: upgrade to v2 for production performance.
bump-pydantic is official migration tool for automated v1 to v2 conversion. Install: pip install bump-pydantic. Usage: bump-pydantic path/to/code. Automated changes: (1) Imports: pydantic.validator → pydantic.field_validator, pydantic.root_validator → pydantic.model_validator. (2) Methods: .dict() → .model_dump(), .parse_obj() → .model_validate(), .schema() → .model_json_schema(). (3) Config class: class Config → model_config = ConfigDict(). (4) Decorators: @validator → @field_validator, @root_validator(pre=True) → @model_validator(mode='before'). Flags: --diff shows changes without applying, --disable checks comma-separated rules. Example: bump-pydantic --diff src/. Limitations: (1) Cannot migrate complex validators automatically. (2) May not handle all edge cases (each_item, custom json_encoders). (3) Requires manual review of changes. Workflow: (1) Run with --diff. (2) Review changes. (3) Apply: bump-pydantic src/. (4) Run tests. (5) Fix remaining issues manually. GitHub: https://github.com/pydantic/bump-pydantic. Use case: large codebases with hundreds of models. Essential for minimizing manual migration effort.
Pydantic v2 ValidationError has different structure and behavior. Error format changes: (1) loc: error location tuple, e.g. ('field', 0, 'subfield'). (2) msg: human-readable message. (3) type: error code like 'int_parsing', 'missing', 'string_too_short'. (4) ctx: additional context dict (e.g. {'max_length': 10}). V2 improvements: more specific error types (v1 had generic 'type_error.integer'), better messages, localized errors (error.errors(include_url=True) adds doc links). Error codes: v2 has 100+ specific codes vs v1's ~30. Example: v1 'type_error.integer', v2 'int_parsing', 'int_from_float'. Custom errors: from pydantic_core import PydanticCustomError; raise PydanticCustomError('custom_error', 'message', {'ctx': 'value'}). Error dict: exc.errors() returns list[dict]. JSON: exc.json() for JSON string. Error count: exc.error_count(). URL in errors: include_url=False by default (v1 always included). Breaking: error type strings changed - update error handling code. Migration: search for error.type checks, update to new error codes. Use case: API error responses, user-friendly messages, error monitoring. Essential for proper error handling in v2.
RootModel validates primitives, lists, dicts without wrapping in object. Pattern: from pydantic import RootModel; UserList = RootModel[List[User]]. Replaces v1 root pattern. V1: class UserList(BaseModel): root: List[User]. V2: class UserList(RootModel): root: List[User]. Access: users = UserList.model_validate([{'id': 1}]); users.root returns list. Iteration: class UserList(RootModel[List[User]]): def iter(self): return iter(self.root); for user in users: print(user). Index access: def getitem(self, item): return self.root[item]; users[0]. Primitives: class StringModel(RootModel[str]): pass; model = StringModel.model_validate('hello'). Dict root: class ConfigModel(RootModel[Dict[str, Any]]): pass. Validation: add model_validator to RootModel class. Serialization: model.model_dump() returns root value directly (list/dict/primitive), not wrapped object. JSON schema: generates schema for root type, not object wrapper. Use case: API accepting arrays, validating config dicts, typed primitives. Migration: replace root with RootModel[T]. Essential for clean validation of non-object types.
Annotated from typing combines type with metadata in Pydantic v2. Pattern: from typing import Annotated; from pydantic import Field; Username = Annotated[str, Field(min_length=3, max_length=20)]. Use in model: class User(BaseModel): username: Username. Equivalent to: username: str = Field(min_length=3, max_length=20). Benefits: (1) Reusable types: define once, use everywhere. (2) Type aliases with constraints: PositiveInt = Annotated[int, Field(gt=0)]. (3) Multiple constraints: Annotated[str, Field(min_length=1), Field(pattern=r'[a-z]')]. (4) Custom validators: Annotated[str, AfterValidator(lambda x: x.lower())]. (5) Clearer than Union: Annotated[int | str, Field(description='ID')] vs Field on union. Stacking: Email = Annotated[str, Field(pattern=r'.+@.+')]; StrictEmail = Annotated[Email, Field(max_length=100)]. Custom metadata: Annotated[str, MyCustomMetadata('value')]. Validators: from pydantic import AfterValidator, BeforeValidator; Annotated[int, BeforeValidator(int), AfterValidator(lambda x: abs(x))]. Use case: domain types (Email, URL, PositiveInt), reusable validation, API schemas. V2 recommendation: prefer Annotated over Field assignment. Essential for clean, reusable type definitions.
pydantic.v1 module provides v1 API compatibility in Pydantic v2 installations. Import: from pydantic.v1 import BaseModel, validator. Allows running v1 code unchanged with v2 installed. Use cases: (1) Gradual migration: migrate codebase incrementally. (2) Dependencies: third-party libraries still using v1 API. (3) Temporary compatibility: keep old code working during migration. Example: from pydantic.v1 import BaseModel, validator; class User(BaseModel): name: str; @validator('name'); def validate_name(cls, v): return v.lower(). Limitations: (1) Performance: uses v1 validation logic, not fast Rust core. (2) No v2 features: cannot use model_dump, field_validator in v1 models. (3) Mixing: cannot mix v1 and v2 models easily (inheritance issues). (4) Deprecated: will be removed in future Pydantic version. Best practice: use only during migration, not long-term. Migration path: (1) Install v2, import from pydantic.v1. (2) Migrate models incrementally to pydantic. (3) Remove pydantic.v1 imports when done. Not recommended: new projects should use v2 API directly. Essential for large codebases needing gradual migration.
extra parameter in model_config controls how Pydantic handles fields not defined in model. Three modes: forbid, allow, ignore. Forbid (strictest): model_config = ConfigDict(extra='forbid'). Raises ValidationError if extra fields present. User.model_validate({'name': 'Alice', 'age': 30}) raises error if age not defined. Use case: strict APIs, prevent typos. Allow: model_config = ConfigDict(extra='allow'). Accepts extra fields, stores in model. Access: user.age or user.pydantic_extra dict. Serialization: extra fields included in model_dump(). Validation: extra fields not validated. Use case: dynamic fields, passthrough data. Ignore (default): model_config = ConfigDict(extra='ignore'). Silently ignores extra fields. Not stored, not accessible. Use case: API versioning, backward compatibility. V1 equivalent: class Config: extra = Extra.forbid|allow|ignore. Migration: Extra enum → string 'forbid'|'allow'|'ignore'. Field-level: no field-level control, model-level only. Inheritance: child inherits parent's extra setting unless overridden. JSON schema: extra='forbid' adds additionalProperties: false. Essential for controlling API contract strictness.
validate_default=True forces validation of default values in Pydantic v2. Config: model_config = ConfigDict(validate_default=True). V1 equivalent: validate_all=True (renamed in v2). Behavior without: class User(BaseModel): status: str = 'active'. Default 'active' not validated, even if invalid. Behavior with: model_config = ConfigDict(validate_default=True); class User(BaseModel): status: Literal['active', 'inactive'] = 'pending'. Raises ValidationError at class definition if default invalid. Per-field: age: int = Field(default=0, validate_default=True). Use cases: (1) Ensure defaults meet constraints: Field(default=5, gt=0) validated. (2) Catch bugs: default='abc' with int type raises error early. (3) Default factories: default_factory=lambda: {...}, validates result. Example: created_at: datetime = Field(default_factory=datetime.now, validate_default=True). Validation timing: at class definition time for literals, at instance creation for factories. Performance: slight overhead validating defaults. V1 migration: validate_all=True → validate_default=True. Recommendation: enable in dev/test, optional in prod. Essential for catching invalid defaults early.
Common Pydantic v2 migration errors and fixes: (1) AttributeError: 'BaseModel' object has no attribute 'dict'. Fix: replace .dict() with .model_dump(). (2) AttributeError: 'BaseModel' object has no attribute 'parse_obj'. Fix: User.parse_obj(data) → User.model_validate(data). (3) PydanticUserError: '@validator' is deprecated. Fix: @validator → @field_validator, update arguments (values → info.data). (4) PydanticUserError: '@root_validator' with pre=False must specify skip_on_failure=True. Fix: migrate to @model_validator(mode='after'). (5) TypeError: ConfigDict() got unexpected keyword argument 'fields'. Fix: remove fields from config, use model_fields class attribute. (6) PydanticUserError: 'Config' class is deprecated. Fix: class Config → model_config = ConfigDict(). (7) ImportError: cannot import 'ConstrainedStr'. Fix: use Annotated[str, Field(min_length=...)] instead. (8) ValidationError: wrong error type codes. Fix: update error handling to use new type codes (int_parsing not type_error.integer). (9) AttributeError: 'from_orm' not found. Fix: model_validate with from_attributes=True config. (10) PydanticSchemaGenerationError with forward refs. Fix: call model_rebuild() after class definition. Use bump-pydantic for automated fixes.