PydanDict
Pydantic models with dictionary semantics.
PydanDict is a Python library whose primary base class, DictModel, is
both a genuine Pydantic BaseModel and a Python mutable mapping. It is designed
to let existing mapping-oriented code consume models directly, and to let
package authors keep internal records valid as they change.
Status: 0.2.0 prepared for release; Phase 0.2 passed independent review.
Version 0.1.0 was published to PyPI
on 2026-09-13 from the immutable
v0.1.0 tag.
The checkout in src/pydandict contains the unreleased
Phase 0.2 changes with metadata version 0.2.0; that version is not yet published.
See the passed review, the
Phase 0.2 evidence for current qualification
results and the Phase 0.1 findings for
historical evidence. The original prototype guide remains
reproducible.
The plan prioritizes a dependable dependency: atomic failure behavior, protected nested values, complete public typing, tested ecosystem compatibility, measured costs, and verified distribution artifacts. Start with the roadmap, implementation work packages, and quality bar. Qualification uses automated consumer projects and maintainer checks; no external trials or participants are required.
Install the released package with python -m pip install pydandict. For a local
source checkout, install the package and development tools with
python -m pip install -e ".[dev]".
Migrating from 0.1.0
Version 0.2.0 intentionally narrows the alpha annotation contract:
| Previous field annotation | Supported 0.2.0 annotation |
|---|---|
list[T] |
collections.abc.MutableSequence[T] |
dict[K, V] |
collections.abc.MutableMapping[K, V] |
set[T] |
collections.abc.MutableSet[T] |
This applies recursively to nested and union annotations and typed-extra values.
The outer __pydantic_extra__: dict[str, V] metadata declaration remains valid.
Ordinary list/dict/set inputs and serialized shapes remain supported. Generic
models require explicit specialization, such as Box[int] or
Box[MutableSequence[int]]. See the complete migration and support envelope.
One model, two ways to work
from collections.abc import Mapping, MutableMapping
from pydantic import BaseModel, Field, ValidationError
from pydandict import DictModel
class User(DictModel):
name: str
age: int = Field(ge=0)
user = User(name="Eddie", age=40)
assert user.name == user["name"] == "Eddie"
user["age"] = 41
assert user.age == 41
try:
user["age"] = -1
except ValidationError:
assert user.age == 41 # Failed mutations leave the model unchanged.
assert isinstance(user, BaseModel)
assert isinstance(user, Mapping)
assert isinstance(user, MutableMapping)
assert list(user) == ["name", "age"]
assert dict(user) == {"name": "Eddie", "age": 41}
Attribute and mapping access address the same model state. Pydantic supplies validation, field definitions, serializers, and JSON Schema. PydanDict supplies mapping behavior and a transaction boundary around supported mutations.
For existing Python systems
An API written against Mapping[str, object] should need no PydanDict-specific
branch, adapter, or model_dump() call:
from collections.abc import Mapping
def describe(record: Mapping[str, object]) -> str:
return ", ".join(f"{key}={value}" for key, value in record.items())
description = describe(user)
The target includes [], get, containment, key iteration, live mapping views,
dict(model), and keyword unpacking. It does not include isinstance(model, dict)
or compatibility with APIs that insist on a concrete built-in dictionary.
dict(model) is a shallow mapping copy; model_dump() is the serialization API.
For package internals
from pydantic import Field
from pydandict import DictModel
class RetryConfig(DictModel):
timeout: float = Field(default=30.0, gt=0)
retries: int = Field(default=3, ge=0)
config = RetryConfig()
config.update(timeout=60.0, retries=5) # One validation transaction.
assert config.timeout == config["timeout"] == 60.0
Successful writes must satisfy the complete model contract. Failed writes must
preserve values and model metadata. Required fields cannot disappear; the
deletion policy protects all declared fields, with an explicit reset
operation for defaults. Extras follow a documented Pydantic configuration policy.
Continuous validation is a release requirement, including nested mutations.
It cannot be delivered merely by enabling validate_assignment. The design
requires ownership and mutation guards for supported mutable values, validation
of affected parent constraints, and rejection of values that cannot be protected.
The Phase 0.2 implementation requires mutable ABC field annotations and rejects
unprotectable values before commit. See
mutation semantics and
nested ownership.
A Pydantic model for FastAPI
The tested integration uses ordinary model annotations:
from fastapi import FastAPI
app = FastAPI()
@app.post("/users", response_model=User)
def create_user(user: User) -> User:
user.update(age=user.age + 1)
return user
Request parsing, response serialization, and OpenAPI use Pydantic in the pinned integration tests. See the compatibility contract.
Scope and typing
V1 centers on schema-defined DictModel records. It excludes a public TypedMap,
replacement TypedDict, generalized collection framework, persistence,
reactivity, and a new validation engine. Internal guards needed to protect model
fields are part of validation, not separate collection products.
Pyright support is a first-class requirement: attributes retain their declared
types, while generic mapping reads return object and require narrowing. Automatic
per-key inference such as user["age"] -> int is not promised by the base class.
See the typing strategy.
Read the plan
| Document | Purpose |
|---|---|
| Package source | Installable DictModel implementation |
| Phase 0.2 implementation contract | Bounded architecture, public contract, acceptance criteria and verification plan |
| Prototype guide | Reproducible evidence commands and runnable example |
| Prototype findings | Demonstrated solutions, evidence and remaining limitations |
| Documentation index | Reading paths and requirement traceability |
| Product and scope | Audiences, use cases, success criteria |
| Architecture | BaseModel integration and transactional state |
| API specification | Mapping surface, names, return values, errors |
| Mutation semantics | Invariants, atomicity, deletion, defaults, extras |
| Nested values | Ownership, escaped references, parent validation |
| Typing | Pyright, protocols, limitations, typing checks |
| Compatibility | Pydantic, serialization, schema, FastAPI |
| Interoperability | What existing consumers can and cannot assume |
| Competition | Alternatives and focused positioning |
| Testing | Acceptance cases and release gates |
| Roadmap | Sequenced implementation and release policy |
| Implementation work packages | Priorities, dependencies, first increments and stop criteria |
| Quality bar | Measurable gates, automated consumer journeys and maintenance standards |
| Release automation | Tag-gated checks, artifact build, and PyPI Trusted Publishing |
| Security and performance | Trust boundary, costs, benchmarks |
| Decision log | Established requirements and proposed choices |
| Upstream evidence | Sources and reproducible baseline observations |
Contributing
Start with CONTRIBUTING.md. Design contributions should identify the invariant they preserve and the acceptance test that will prove it. The next step for 0.2.0 is the tag-gated release process.
The package is distributed under the MIT license. Use the private GitHub channel described in security reporting and the changelog.
Metadata
Release files for pydandict 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pydandict-0.2.0.tar.gz | 45.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pydandict-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 71.2 kB
Release files / pydandict-0.2.0.tar.gz
| Download URL | pydandict-0.2.0.tar.gz |
|---|---|
| Size | 45.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cbb3402c5e95444a73a0768d4fca856f54f4adc8877ed3b0727d17b806da81c3
|
|
BLAKE2b-256 checksum How to use checksums |
ebe29ae31dd9e8e3dc69f415ed10f6fd286a0ad682bd7e0276d52515418f9f0c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / pydandict-0.2.0-py3-none-any.whl
| Download URL | pydandict-0.2.0-py3-none-any.whl |
|---|---|
| Size | 25.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c5cad6a392d0430f01f43264828e230a85e702007cde5ff1fb7c7e6381435646
|
|
BLAKE2b-256 checksum How to use checksums |
e4473bbde48eecd391a6d07e536ff606c3ba431a4a7b6aaac64123d00f43b4ae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency log