Pydantic: Python Data Validation Using Type Hints
What Pydantic is
Pydantic describes itself in one line: "Data validation using Python type hints." The repository's README and the documentation, which now brands the library as Pydantic Validation, put the idea simply: you define how data should look in plain, canonical Python, using ordinary type annotations, and Pydantic validates incoming data against that definition. The documentation index goes further and calls it "the most widely used data validation library for Python".
The problem it targets is familiar to anyone who has written a web API, a CLI that reads config files, or a pipeline that ingests JSON from somewhere else. Data arrives as untyped dicts, strings and bytes, and somewhere between the network and your business logic it has to be checked, converted into the right Python types and rejected with a useful error when it is wrong. Pydantic handles that step. The models documentation states the guarantee precisely: untrusted data can be passed to a model and, after parsing and validation, Pydantic guarantees that the fields of the resulting instance conform to the field types defined on the model.
That same page makes a point worth absorbing early. Pydantic calls this process "validation", but the docs describe the term as "a deliberate misnomer": Pydantic guarantees the types and constraints of the output, not the input. Validation can include copying and coercing data, so a string '123' passed to an int field becomes the integer 123 rather than an error, unless you ask for strict behaviour.
The audience is broad. The docs list FastAPI, huggingface, Django Ninja, SQLModel and LangChain among the libraries that depend on Pydantic, and claim that around 8,000 packages on PyPI use it. The "Why use Pydantic" page states that at the time it was written there were 466,400 repositories on GitHub and 8,119 packages on PyPI depending on it, and the index page claims more than 550M downloads a month. Those are the project's own figures.
How it works: pydantic and pydantic-core
Two packages, one schema format
Since V2, Pydantic has been split into two layers. The internals documentation explains that model definition happens in the pydantic package, written in Python, while model validation and serialization happen in pydantic-core, where validation for most types is implemented in Rust. The architecture page says this was done "partly in order to improve validation and serialization performance (with the cost of limited customization and extendibility of the internal logic)".
When you define a BaseModel subclass, a metaclass inspects the class body and collects the annotations that become fields (exposed as model_fields), the model_config settings, any extra validators and serializers, private attributes, class variables and generic parametrisation. That information is then translated into a core schema: a structured, serializable Python dictionary, described with TypedDict definitions, that tells pydantic-core exactly how to validate and serialize the type. Every core schema has a required type key plus properties that depend on that type. For a model, the result is stored on the __pydantic_core_schema__ attribute.
All core schema generation happens in one place, a GenerateSchema class. The docs are explicit that you cannot define arbitrary custom core schemas, because pydantic-core only understands a fixed set of schema types. Customisation goes through hooks instead: __get_pydantic_core_schema__ and __get_pydantic_json_schema__, which follow a wrapper pattern where your code receives a handler, calls it to get the default schema and then adjusts the result.
JSON Schema from the same source
The core schema is also the input to JSON Schema generation. A GenerateJsonSchema class walks the core schema and emits the matching JSON Schema; serialization schemas carry a return_schema key so the serialization-mode JSON Schema can be produced too. Because validation, serialization and schema output all derive from one description of the type, they stay consistent with each other.
Where the Rust code lives now
pydantic-core used to live in its own repository. The v2.13.0b1 changelog entry records that the pydantic-core repository "was merged inside the main pydantic one", and the version policy adds that since v2.13 it is a workspace member inside the pydantic repository, with its releases tagged core-vx.y.z. On the main branch, pyproject.toml pins an exact version, pydantic-core==2.50.0, alongside typing-extensions, annotated-types and typing-inspection.
Key features
- Schemas from type hints. Fields are declared with standard annotations, so the docs point out there is less to learn and the models work with static tools such as mypy and Pyright and with IDEs such as PyCharm and VS Code.
- Constraints through
Annotated. Constraints that plain types cannot express, such as "greater than zero", are attached withtyping.Annotatedand theannotated-typespackage;PositiveIntis documented as shorthand forAnnotated[int, annotated_types.Gt(0)]. - Lax and strict modes. By default Pydantic coerces common mismatches, like numeric strings into integers. Strict mode disables most coercion and can be switched on per validation call, per field or in model configuration.
- Four ways to define schemas. The docs list
BaseModel, Pydantic dataclasses,TypeAdapterfor arbitrary types such asTypedDict,NamedTuple,intortimedelta, and thevalidate_calldecorator for validating function arguments. - Field and model validators. Field validators come in after, before, plain and wrap variants and can be written with the annotated pattern or the
@field_validatordecorator; model validators come in before, after and wrap variants. - Serialization in two modes.
model_dump()produces Python objects, JSON mode produces JSON-compatible output, andmodel_dump_json()returns a JSON string. Output can exclude specific fields, unset fields, defaults orNonevalues, and custom field and model serializers are supported. - JSON Schema generation.
model_json_schema()andTypeAdapter.json_schema()emit schemas the docs describe as compliant with JSON Schema Draft 2020-12 and OpenAPI 3.1.0, in either validation or serialization mode. - JSON parsing in Rust.
model_validate_json()parses and validates in one step; the docs note this lets strict mode still accept sensible conversions such as ISO strings intodatetimewhen the input is JSON. - mypy plugin.
pydantic.mypyadds Pydantic-aware checks; the integration page shows it flagging untyped fields that plain mypy misses.
Getting started
The install page gives the basic commands for pip and uv, and notes that Python 3.11+ is required on the current documentation:
pip install pydantic
uv add pydantic Pydantic is also published on conda-forge:
conda install pydantic -c conda-forge Two optional extras exist: email, which pulls in the email-validator package, and timezone, which installs tzdata as a fallback IANA time zone database (the pyproject.toml restricts it to Windows):
pip install 'pydantic[email,timezone]' The README's starter example shows the core behaviour, including coercion of the string '123' to an integer and of mixed list items to int:
from datetime import datetime
from typing import Optional
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: Optional[datetime] = None
friends: list[int] = []
external_data = {'id': '123', 'signup_ts': '2017-06-01 12:22', 'friends': [1, '2', b'3']}
user = User(**external_data)
print(user)
print(user.id) To see the difference between the default lax mode and strict mode, the strict mode page uses this example:
from pydantic import BaseModel, ValidationError
class MyModel(BaseModel):
x: int
print(MyModel.model_validate({'x': '123'})) # lax mode
try:
MyModel.model_validate({'x': '123'}, strict=True) # strict mode
except ValidationError as exc:
print(exc) The first call prints x=123; the second fails with "Input should be a valid integer" because strict mode refuses to convert the string.
If you use mypy, enable the plugin in your config file, as shown on the mypy integration page:
[mypy]
plugins = pydantic.mypy Validators, serialization and JSON Schema in practice
Custom validation
Built-in constraints cover a lot, but real applications need rules of their own. The validators page describes four kinds of field validator. After validators run once Pydantic's own validation has finished, so they receive a value of the right type and are described as generally more type safe. Before validators run on the raw input before coercion and can reshape it, for example wrapping a single value in a list. Plain validators replace Pydantic's internal validation, and wrap validators receive a handler they can call or skip. A short after-validator using the annotated pattern, from the docs:
from typing import Annotated
from pydantic import AfterValidator, BaseModel, ValidationError
def is_even(value: int) -> int:
if value % 2 == 1:
raise ValueError(f'{value} is not an even number')
return value Attaching it is a single annotation, number: Annotated[int, AfterValidator(is_even)], and a failure surfaces as a value_error in the structured ValidationError. The docs stress returning the validated value from every validator. Model-level validators work on the whole object; an after model validator is an instance method that acts like a post-initialisation hook and must return the instance.
Serialization behaviour to be aware of
Serialization has a default that is deliberately conservative. When a field is annotated with a model type and you pass an instance of a subclass, model_dump() only includes the fields declared on the annotated type. The serialization docs show a UserLogin subclass with a password field being dumped as just {'user': {'name': 'pydantic'}}, and the migration guide notes this V2 change "helps prevent some accidental security bugs". Options such as SerializeAsAny and a serialize_as_any runtime setting exist when you want the subclass fields.
Use cases
- Request and response models for web APIs. The docs compare a model to "the requirements of a single endpoint in an API", and JSON Schema output compatible with OpenAPI 3.1 is what lets frameworks such as FastAPI document endpoints from the same classes.
- Parsing JSON from external services.
model_validate_json()parses and validates in one pass, and the performance guide recommends it overmodel_validate(json.loads(...))in general. - Application settings. Loading configuration from environment variables and secrets files is handled by the separate
pydantic-settingspackage, built on the same validation engine. - Data pipelines with loose inputs. The strict mode page names UUIDs, URL parameters, HTTP headers, environment variables and dates as cases where lax coercion is the useful default.
How it compares
The documentation does not publish a feature matrix against other validation libraries, so any comparison here sticks to what the docs themselves discuss.
Standard library dataclasses and TypedDict. The models page says models share many similarities with dataclasses but differ in ways that streamline validation, serialization and JSON Schema generation. If you prefer standard containers, Pydantic dataclasses wrap a regular dataclass with validation, and TypeAdapter validates a plain TypedDict. The performance guide even claims that in a simple benchmark, TypedDict is "about ~2.5x faster than nested models", which makes it a reasonable choice for hot paths.
Hand-written validation. The "Why" page includes an example in which, it claims, Pydantic is more than 300% faster than dedicated pure-Python code at parsing a JSON payload and validating its URLs, with the printed result showing 3.45x. Treat that as one documented example, not a general benchmark.
Pydantic V1. The pydantic-core README claims the Rust core "is currently around 17x faster than pydantic V1", pointing to its benchmark suite for details. V2 also changed many behaviours, covered in the next section.
Things to know before adopting
V1 to V2 is a real migration
The README calls V2 "a ground-up rewrite" with "some breaking changes". The migration guide is long, and some of the changes are easy to miss in review:
- Renamed methods.
dict()becamemodel_dump(),json()becamemodel_dump_json(),parse_obj()becamemodel_validate()and so on; the old names remain but emitDeprecationWarnings. Optionalno longer implies a default. A field annotatedOptional[T]is required and merely allowsNone, which the guide flags as a breaking change from V1. Fields typedAnyalso no longer default toNone.- Validators changed.
@validatorand@root_validatorare deprecated in favour of@field_validatorand@model_validator, signatures changed, and aTypeErrorraised inside a validator is no longer converted into aValidationError. - Coercion changed. Numbers are no longer coerced to strings by default, iterables of pairs are no longer accepted as dicts, and floats only convert to
intwhen the fractional part is zero. - Unions preserve input types.
int | strgiven'1'now keeps the string;Field(union_mode='left_to_right')restores V1 behaviour. - Regex and URLs moved to Rust crates. Pattern constraints use the Rust
regexcrate, which promises linear-time matching but drops look-arounds and backreferences; aregex_enginesetting switches back to Python's engine. URL types no longer inherit fromstrand append a trailing slash when no path is given.
A code transformation tool exists for the mechanical parts. The guide describes it as "still in beta":
pip install bump-pydantic
bump-pydantic my_package The pydantic.v1 shim has limits
V2 bundles the latest V1 API under pydantic.v1 so code can be upgraded incrementally, but the changelog shows its Python support has moved around. The v2.12.0 entry states "Pydantic V1 is not compatible with Python 3.14 and greater", and the v1.10.24 entry explains that a warning was added because V1 would "silently get broken at runtime" on 3.14. The later v2.13.0 entry says the bundled namespace now matches V1 1.10.26, "which includes support for Python 3.14". Separately, a note in the migration guide on the main branch says the pydantic.v1 namespace "was removed in Pydantic V3". Treat the shim as a bridge, not a place to stay.
Features that moved to separate packages
BaseSettings now lives in pydantic-settings, and its parse_env_var classmethod was removed in favour of custom settings sources. Color types and payment card numbers moved to pydantic-extra-types. Both must be installed separately.
Version policy and experimental features
The version policy promises no intentional breaking changes in V2 minor releases and keeps deprecated functionality until V3. It also lists changes that are not considered breaking and may land in minor releases: bug fixes affecting code that relied on undocumented behaviour, JSON Schema $ref format changes, changes to the msg, ctx and loc fields of validation errors (match on type instead), new error types, __repr__ changes and changes to core schema contents. Experimental features, under pydantic.experimental or prefixed experimental_, can change or be removed "with little notice". The policy also expects V3 to merge pydantic-core in as an internal sub-module rather than a standalone library.
Lax coercion can surprise you
The models page warns that casting input to fit field types can "result in a loss of information", and gives 3.000 becoming 3 and bytes becoming a string as examples. Models also ignore extra fields by default instead of raising, which the extra setting changes. Constructor arguments are often copied, so mutating the original list after construction does not affect the model. Going the other way, the strict mode page cautions that turning strict mode on for models an existing application depends on will make previously coerced inputs start failing. Frozen models give only "faux immutability"; the docs note Python does not enforce it.
Python versions, platforms and the compiled core
Because validation lives in Rust, installation depends on a compiled pydantic-core build for your platform. The main branch pyproject.toml requires Python 3.11+, lists CPython, PyPy and GraalPy implementations and an Emscripten environment classifier, and declares Python 3.11 through 3.15. The v2.14.0a1 changelog notes a 3.14 wheel with the pyemscripten_2026_0 platform tag that only works with Pyodide 314.0 or later, which was still in development, and recommends waiting for a future 2.14 release. Free-threaded Python support was added as experimental in v2.11.0b2. The version policy says a Python version is dropped once it reaches end of life and accounts for less than 5% of downloads of the latest minor release.
Project activity
As of November 2026 the repository has about 29,500 stars and more than 3,100 forks. It was created on 3 May 2017, is written primarily in Python with a Rust core, and is released under the MIT licence; the LICENSE file credits Pydantic Services Inc. and individual contributors. The most recent push to the default main branch was on 10 November 2026.
The latest release recorded in HISTORY.md is v2.14.0, dated 2026-10-08. Its notes say the release drops support for Python 3.9 and adds Python 3.15, and include additions such as a multiple_of constraint for fraction and timedelta schemas, a __namespace__ parameter for create_model(), and a list of validation fixes. The pyproject.toml classifier marks the package as "Production/Stable", and V1 remains maintained on a separate 1.10.X-fixes branch with its own documentation.
- Repository: github.com/pydantic/pydantic
- Documentation: pydantic.dev/docs/validation
- V1 documentation: Pydantic V1.10 documentation
- Settings package: github.com/pydantic/pydantic-settings
Enjoying this project?
Discover more amazing open-source projects on TechLogHub. We curate the best developer tools and projects.
Repository:https://github.com/pydantic/pydantic
GitHub - pydantic/pydantic: Pydantic: Python Data Validation Using Type Hints
Pydantic is an MIT-licensed Python library that validates and serializes data from type hints, with a Rust validation core, lax and strict modes, custom validat...
github - pydantic/pydantic
Related Projects
Maintain Pydantic: Python Data Validation Using Type Hints? Add the badge to your README
Show that the project is featured on TechLogHub and send readers to this write-up.
[](https://techloghub.com/open-source/pydantic?utm_source=techloghub_badge&utm_medium=referral&utm_campaign=readme)
