GitHub - andonimichael/pydantic-plus-plus: A suite of utilities to improve upon the core Pydantic library

4 min read Original article ↗

CI Release PyPI version

Pydantic++ is a suite of utilities to improve upon the core Pydantic library. We stand on the shoulders of giants here. Huge kudos to the Pydantic team for all their hard work building an amazing product.

Utilities

  • Partial Models — type-safe "partial" Pydantic models where some or all fields are optional
  • Model Updater — type-safe immutable builder for updating model instances
  • Model Registry — discover all BaseModel subclasses in a module tree
  • Dummy Models — generate dummy model instances with random data for testing
  • Mypy Plugin — full mypy integration for partial models and model updates

Installation

pip install pydantic-plus-plus

Requires Python 3.10+ and Pydantic 2.0+.

If you use mypy for type checking, please also add our plugin to your mypy configuration.

[tool.mypy]
plugins = ["pydantic.mypy", "pydantic_plus_plus.mypy"]

Partial Models

partial creates a PartialBaseModel, a variant of the given Pydantic BaseModel where every field is Optional with a default of None. This is the type-safe way to represent partial updates (PATCH payloads, upsert data, sparse sync records) without manually duplicating each model with every field made optional.

Model Updater

update provides a type-safe, immutable builder for updating Pydantic model instances. Pydantic's built-in model_copy(update={"field": value}) takes a dict[str, Any] — you lose all type safety, typos are invisible, and nested updates are painful. update fixes all of this.

Model Registry

ModelRegistry scans one or more Python modules (and all submodules) and collects every BaseModel subclass it finds. This is useful when you have a base class — say BasePrompt, AgentConfig, or BaseEvent — and you need to discover all concrete implementations at runtime for dispatching, serialization, CLI tooling, or documentation generation.

Dummy Models

dummy generates an instance of any Pydantic BaseModel populated with random data. This is useful for testing — round-trip serialization, factory fixtures, property-based tests — anywhere you need a valid model instance without hand-writing every field.

Mypy Plugin

Pydantic++ ships a mypy plugin that gives partial models and model updates full type safety. Without it, mypy sees partial(User) as returning a generic type and can't validate field access or constructor arguments. Similarly, ModelUpdater methods like .set() and .append() would accept any keyword argument without type checking.

With the plugin enabled:

  • Partial models get field-level type checking, constructor validation, and IDE autocompletion.
  • ModelUpdater methods get typed keyword arguments — mypy validates field names, value types, and restricts operations to applicable fields (e.g., .append() only accepts sequence fields).

Comparable Packages

Why Pydantic++ over pydantic-partial?

Pydantic++ offers a similar feature set, but takes a different philosophical approach than pydantic-partial that is more aligned with SOLID Object Oriented design principles.

Here's how the two differ:

Pydantic++ pydantic-partial
Partial models All fields optional All fields optional
Selective fields Yes Yes
Dot-notation nesting Yes Yes
Wildcard nesting Yes Yes
Field metadata preservation Yes Yes
Caching Yes Yes
Recursive by default Yes No (opt-in)
Mypy plugin Yes No
.apply() deep merge Yes No
Standalone partial classes Yes — partials don't subclass the original No — partials subclass the original

Philosophical Differences

pydantic-partial's partials subclass the original model, so isinstance(patch, User) is True. This violates the Liskov Substitution Principle — a partial User weakens the preconditions of the original (required fields become optional), meaning code that expects a complete User can silently receive one with None fields. In addition to breaking LSP, this also causes problems for type checkers because the subclass relationship tells type checkers the partial is a User.

Pydantic++ takes a different philosophical approach, where partials are standalone classes. This preserves LSP. Additionally, because isinstance(patch, User) is False, the type checker can enforce the boundary between partial and complete data. Users must go through .apply() to produce a real User, making the conversion explicit and safe.

Mypy plugin

This is a well known limitation of pydantic-partial. Pydantic++ ships a mypy plugin that synthesizes full TypeInfo objects, giving users field-level type checking and constructor validation with zero # type: ignore comments.

.apply() deep merge

pydantic-partial creates partial models but has no built-in way to merge a partial back into an existing instance. Pydantic++ provides .apply() which recursively merges only explicitly-set fields, preserving untouched nested data.

License

MIT