The architecture of Pydantic, x-rayed
Pydantic's x-ray is really three codebases sharing a package: the v2 public API, a _internal/ engine room that translates your type hints into pydantic-core schemas, and an entire frozen copy of v1 kept for migration. At 104 components it's the largest Python repo in this gallery — and the most deliberate about which parts you're allowed to touch.
archsteer xray v0.4.1 against pydantic/pydantic@c9688f4 (pydantic/) on 2026-07-05. Read-only static analysis — no code executed, no architecture rules declared. Structure, not judgment.Module dependency graph
Top 14 modules by connectivity; an arrow means "imports from".
graph LR
m0["__init__"]
m1["_internal"]
m2["_migration"]
m3["config"]
m4["dataclasses"]
m5["deprecated"]
m6["errors"]
m7["fields"]
m8["json_schema"]
m9["main"]
m10["type_adapter"]
m11["types"]
m12["typing"]
m13["warnings"]
m0 --> m2
m0 --> m3
m0 --> m4
m0 --> m5
m0 --> m6
m0 --> m7
m0 --> m8
m0 --> m9
m0 --> m10
m0 --> m11
m0 --> m12
m0 --> m13
m1 --> m0
m1 --> m3
m1 --> m4
m1 --> m6
m1 --> m7
m1 --> m8
m1 --> m9
m1 --> m10
m1 --> m11
m1 --> m12
m1 --> m13
m2 --> m1
m2 --> m6
m2 --> m12
m2 --> m13
m3 --> m0
m3 --> m1
m3 --> m2
m3 --> m6
m3 --> m7
m3 --> m12
m3 --> m13
m4 --> m1
m4 --> m2
m4 --> m3
m4 --> m6
m4 --> m7
m4 --> m11
m4 --> m12
m4 --> m13
m5 --> m0
m5 --> m1
m5 --> m4
m5 --> m6
m5 --> m8
m5 --> m9
m5 --> m10
m5 --> m11
m5 --> m12
m5 --> m13
m6 --> m1
m6 --> m2
m6 --> m12
m7 --> m0
m7 --> m1
m7 --> m3
m7 --> m4
m7 --> m6
m7 --> m8
m7 --> m11
m7 --> m12
m7 --> m13
m8 --> m0
m8 --> m1
m8 --> m3
m8 --> m4
m8 --> m6
m8 --> m9
m8 --> m10
m8 --> m12
m8 --> m13
m9 --> m0
m9 --> m1
m9 --> m2
m9 --> m3
m9 --> m5
m9 --> m6
m9 --> m7
m9 --> m8
m9 --> m11
m9 --> m12
m9 --> m13
m10 --> m0
m10 --> m1
m10 --> m3
m10 --> m4
m10 --> m6
m10 --> m8
m10 --> m9
m10 --> m11
m10 --> m12
m11 --> m0
m11 --> m1
m11 --> m2
m11 --> m4
m11 --> m6
m11 --> m8
m11 --> m12
m11 --> m13
m12 --> m2Components by module
Most connected components
Top 20 of 104 by exported API surface and dependency count.
| Component | Module | Key exports |
|---|---|---|
types.py | types | Strict, conint, Model, Model |
v1/errors.py | v1 | cls_kwargs, PydanticErrorMixin, PydanticTypeError, PydanticValueError |
_internal/_generate_schema.py | _internal | check_validator_fields_against_field_name, check_decorator_fields_exist, filter_field_decorator_info_by_field, apply_each_item_validators |
v1/validators.py | v1 | str_validator, strict_str_validator, bytes_validator, strict_bytes_validator |
fields.py | fields | _FromFieldInfoInputs, _FieldInfoInputs, _FieldInfoAsDict, FieldInfo |
networks.py | networks | UrlConstraints, _BaseUrl, _BaseMultiHostUrl, _build_type_adapter |
config.py | config | ConfigDict, with_config, with_config, with_config |
functional_validators.py | functional_validators | AfterValidator, BeforeValidator, PlainValidator, WrapValidator |
v1/schema.py | v1 | _apply_modify_schema, schema, model_schema, get_field_info_schema |
_internal/_model_construction.py | _internal | _ModelNamespaceDict, NoInitField, ModelMetaclass, init_private_attributes |
v1/utils.py | v1 | import_string, truncate, sequence_like, validate_field_name |
json_schema.py | json_schema | PydanticJsonSchemaWarning, _DefinitionsRemapping, GenerateJsonSchema, model_json_schema |
v1/types.py | v1 | _registered, _registered, _registered, ConstrainedNumberMeta |
_internal/_decorators.py | _internal | ValidatorDecoratorInfo, FieldValidatorDecoratorInfo, RootValidatorDecoratorInfo, FieldSerializerDecoratorInfo |
main.py | main | _check_frozen, _model_field_setattr_handler, _private_setattr_handler, BaseModel |
_internal/_fields.py | _internal | PydanticMetadata, PydanticExtraInfo, pydantic_general_metadata, _general_metadata_cls |
_internal/_validators.py | _internal | sequence_validator, import_string, _import_string_logic, pattern_either_validator |
_internal/_typing_extra.py | _internal | is_annotated, annotated_type, unpack_type, is_hashable |
mypy.py | mypy | plugin, PydanticPlugin, PydanticPluginConfig, from_attributes_callback |
_internal/_generics.py | _internal | LimitedDict, PydanticGenericMetadata, create_generic_submodel, _get_caller_frame_info |
Declared runtime dependencies
What the structure teaches
The real engine isn't in this repo
Validation itself happens in pydantic-core, a separate Rust crate. What this package does is schema generation: _internal/_generate_schema.py — one of the largest components in the x-ray — walks your annotations, generics, and validators and compiles them into a core schema the Rust engine executes.
A whole major version lives in the package
The v1/ tree is a complete, importable copy of Pydantic 1.x — dozens of components kept so `from pydantic import v1` works during migrations. Shipping your previous architecture inside your current one is an expensive but honest migration strategy, and the x-ray shows exactly how much it costs.
_internal/ is a hard boundary, by convention
The private engine room is the biggest module in the graph, and public modules like types.py and main.py all point into it. The underscore prefix is the contract: everything in _internal/ can change in a minor release, so the supported surface stays small while the machinery stays free to evolve.
X-ray your own repo
This page is unedited archsteer xray output plus commentary. The same map of your codebase takes one command, runs locally, and never sends code anywhere: pip install archsteer && archsteer xray
FAQ
How is Pydantic v2 structured internally?
In three zones: a public API layer (BaseModel in main.py, types.py, fields.py), a private _internal/ package that compiles Python type annotations into core schemas, and pydantic-core — a separate Rust package that executes validation. Plus a bundled v1/ tree for backwards compatibility. The x-ray counts 104 components across 40 modules with just 4 runtime dependencies.
Why is Pydantic v2 so much faster than v1?
Because validation moved out of Python: the pydantic package compiles your model definitions into a schema once, and the Rust-based pydantic-core executes that schema for every validation call. The Python side you see in this x-ray is the compiler; the hot path lives in Rust.
How was this architecture map generated?
By running `archsteer xray` (an open-source, MIT-licensed CLI) against the pydantic/ package directory of the public GitHub repo — read-only static analysis, no code executed. Reproduce it with `pip install archsteer && archsteer xray`.
More x-rays
The architecture of FastAPI, x-rayed
An auto-generated architecture map of FastAPI: 48 components across 28 modules, the module…
The architecture of Flask, x-rayed
An auto-generated architecture map of Flask: 24 components, the sansio core split, bluepri…
The architecture of HTTPX, x-rayed
An auto-generated architecture map of HTTPX: 23 underscore-private components, the pluggab…
The architecture of Starlette, x-rayed
An auto-generated architecture map of Starlette: 34 components, the middleware stack, and …