A Python package named modict
Project description
modict
modict (short for 'model dict' or 'modern dict') is a modern, dict-first data structure designed to interop nicely with Pydantic models.
It’s a dict subclass with an optional model-like layer (typed fields, factories, validators, computed values, JSON Schema export) and easy class-level conversion to/from Pydantic Models.
Core philosophy: keep dict ergonomics and compatibility, gradually opt into stronger model semantics when you want them, and eventually convert to an actual Pydantic model where it matters.
Where it sits compared to Pydantic:
modictis best when you want a realdict(all familiar dict methods, native MutableMapping interface, code expecting dict instances) and only need lightweight, opt-in modeling on top.- Pydantic is best when you want a pure “model” abstraction (
BaseModel), strong tooling/ecosystem, advanced Model features, and you don’t needdictsubclass semantics.
In practice, for many basic use cases (a small typed container, a couple of defaults/validators/computed properties, light validation, JSON/schema output), the two are roughly interchangeable. The real differences tend to show up in more advanced scenarios: ecosystem/tooling, strict contract modeling, serialization knobs, and whether you want to stay dict-first (modict) or model-first (Pydantic).
modict makes it so that you don't have to choose. Start coding with modict as a drop-in replacement of dict, add hints/validators/computed as you go, and convert to Pydantic at API/SDK boundaries if you need to.
Pros / cons (high level)
modict pros:
- Drop-in
dictbehavior + attribute access (no wrapper object). - Incremental adoption: start dict-first, add hints/validators/computed as needed.
- Built-in nested path utilities (
Path/JSONPath,get_nested/set_nested,walk/unwalk) for working with arbitrary nested data. - clean
modict-> Pydantic interop for class-level conversion.
modict cons:
- Smaller ecosystem than Pydantic (fewer integrations, conventions, docs, plugins).
- Model layer is intentionally minimal; some advanced Pydantic behaviors don’t have 1:1 equivalents.
- Pydantic ->
modictinterop is best-effort (especially for computed/deps and Pydantic-only features). - Type checking/coercion is implemented in pure Python (no Rust-backed speedups).
Pydantic pros:
- Very mature validation/serialization ecosystem and widespread integration.
- Clear model semantics (
BaseModel) with strong schema generation and tooling.
Pydantic cons (in a dict-centric codebase):
- Not a
dictsubclass; no nativeupdate,setdefault, etc. bridging to/from mappings is explicit. - Model usage can be heavier than needed when you mainly want “a dict with model-like capabilities”.
Use cases (when to pick what)
modict shines when:
- You want plasticity while prototyping: start with free-form data, progressively add hints/validators/computed as your shape stabilizes.
- You need a dict-first internal representation for config and data manipulation (configs, JSON-like payloads, ETL/transform pipelines) and you want to keep that surface area.
- You need ergonomic nested manipulation (JSONPath/
Path,get_nested/set_nested,walk/unwalk, deepdiffing/comparison) without introducing a separate model layer everywhere. - You want “some structure” (types/validators/computed/schema) but still allow extra keys and mutable updates during processing.
- You want to interop with Pydantic at boundaries (API/SDK layer) but keep a dict-first representation internally.
Pydantic shines when:
- You need a clear data contract and rich validation/serialization options for API communication (request/response models, SDKs, schemas).
- You want strict-ish model semantics and a large ecosystem of integrations (FastAPI, settings, plugins, community conventions).
- Validation/schema generation is the primary goal and you don’t need
dictsubclass behavior. - You’re building an API or SDK where models are a public contract and stability/tooling matter more than dict ergonomics.
Contents
- Installation
- Quick Start
- Use Cases
- Path-Based Tools
- Core Concepts
- Field Definition
- Factories
- Validators
- Computed Fields
- Validation Pipeline
- Configuration (Deep Dive)
- Aliases
- Type Checking & Coercion
- Serialization
- JSON Schema
- Deep Conversion & Deep Ops
- Pydantic Interop (Optional)
- Package Tour (Internal Modules)
- Public API Reference
- Development
- Contributing
- License
Installation
pip install modict
Requirements:
- Python 3.10+
- JSONPath support relies on
jsonpath-ng(the only dependency of the package!) - Pydantic interoperability is optional (only needed if you call
from_model()/to_model())
Quick Start
Use it as a dict (default)
from modict import modict
m = modict({"user": {"name": "Alice"}, "count": 1})
assert m["count"] == 1
assert m.user.name == "Alice" # attribute access for keys
m.extra = 123 # extra keys are allowed by default
assert isinstance(m, dict) # True: modict is a real dict
Define a typed modict class with defaults
from modict import modict
class User(modict):
name: str # annotation-only: not required, but validated/coerced if provided
age: int = 25 # default value
country: str = "FR" # another default (not passed at init)
u = User({"name": "Alice", "age": "30"})
assert u.age == 30 # best-effort coercion unless strict=True
assert u.country == "FR"
Tip: Annotated fields without defaults are not required; they mainly provide type validation/coercion when the key is present. Annotations and regular class defaults are enough for most fields. Use modict.field(...) later when you need more control (constraints, aliases, metadata).
Path-Based Tools
modict comes with a small, consistent path system for reading/writing nested structures (including inside lists), plus a Path object for disambiguation and introspection.
Supported path formats
You can target nested values using:
- JSONPath strings (RFC 9535):
$.users[0].name - Tuples of keys/indices:
("users", 0, "name") Pathobjects:Path.from_jsonpath("$.users[0].name")
JSONPath strings must start with $. Legacy dot-notation (like "users.0.name") is rejected to avoid ambiguity (see MIGRATION.md).
The Path object
Path is a parsed, strongly-typed representation of a nested path.
from modict import Path
p = Path.from_jsonpath("$.users[0].name")
assert p.to_tuple() == ("users", 0, "name")
assert p.to_jsonpath() == "$.users[0].name"
Internally, a Path is a tuple of PathKey components. Each component carries:
- the key/index (
"users",0,"name") - the origin container class (
dict,list, …) when it can be inferred (container_class), which letswalk()→unwalk()preserve container types.
Path.normalize(...) (used by nested helpers) accepts JSONPath strings, tuples, or Path objects and returns a Path.
Nested operations
from modict import modict
m = modict({"user": {"name": "Alice"}})
m.get_nested("$.user.name") # "Alice"
m.set_nested("$.user.age", 30)
m.has_nested("$.user.age") # True
m.pop_nested("$.user.age") # 30
Paths in deep traversal
walk()yields(Path, value)leaf pairs (paths arePathobjects).walked()returns a{Path: value}mapping.unwalk(...)reconstructs a nested structure from{Path: value}, preserving container classes when possible.
Core Concepts
modictis a realdict: it supports standard dict operations and behaves like a mutable mapping.- A
modictclass can declare fields with type annotations +modict.field(...). - The model-like behavior is controlled by the
_configclass attribute (amodictConfigdataclass):
class User(modict):
_config = modict.config(extra="allow") # see "Configuration (Deep Dive)" for full reference
Field Definition
The recommended public entry-point is modict.field(...):
modict.field(
default=MISSING,
hint=None, # None = use class annotation when provided
required=False, # only required when explicitly True
metadata=None, # docs / schema metadata (no runtime semantics)
constraints=None, # JSON-Schema-like constraints (enforced + exported)
aliases=None, # input aliases / serialization alias
validators=None, # internal: used by the metaclass when collecting @modict.validator(...)
)
Example (explicit defaults + constraints):
from modict import modict, MISSING
class User(modict):
name: str = modict.field(default=MISSING, required=True)
age = modict.field(default=25, constraints={"ge": 0}, hint=int)
u = User({"name": "Alice", "age": "30"})
assert u.age == 30
Note: if you pass hint=... explicitly in modict.field(...), it takes precedence over the class annotation.
metadata (documentation-only)
metadata is for documentation/schema hints (not validation). Common keys:
title: strdescription: strexamples: any JSON-serializable value (often a list)deprecated: bool
constraints (validation + JSON Schema)
Constraints are enforced at runtime and exported in json_schema():
- numbers:
gt,ge,lt,le,multiple_of - strings / sized containers:
min_length,max_length,pattern
class Product(modict):
sku: str = modict.field(constraints={"pattern": r"^[A-Z]{3}-\\d{4}$"})
price: float = modict.field(constraints={"gt": 0})
Product({"sku": "ABC-0001", "price": 9.99})
Factories
Use modict.factory(callable) to define dynamic defaults (a new value for every instance), similar to Pydantic’s default_factory.
from modict import modict
class User(modict):
name: str
tags: list[str] = modict.factory(list) # new list per instance
u1 = User(name="Alice")
u2 = User(name="Bob")
u1.tags.append("python")
assert u2.tags == []
Validators
Field validators
Use @modict.validator(field_name, mode="before"|"after") to validate and/or transform a single field.
from modict import modict
class User(modict):
email: str
@modict.validator("email")
def normalize_email(self, value):
return value.strip().lower()
u = User(email=" ALICE@EXAMPLE.COM ")
assert u.email == "alice@example.com"
Model validators (cross-field invariants)
Use @modict.model_validator(mode="before"|"after") for multi-field checks.
In mode="after", the instance is already populated and validated field-by-field.
from modict import modict
class Range(modict):
start: int
end: int
@modict.model_validator(mode="after")
def check_order(self):
if self.start > self.end:
raise ValueError("start must be <= end")
Computed Fields
Computed fields are virtual values evaluated on access.
from modict import modict
class Calc(modict):
a: int
b: int
@modict.computed(cache=True, deps=["a", "b"])
def sum(self) -> int:
return self.a + self.b
c = Calc(a=1, b=2)
assert c.sum == 3
c.a = 10
assert c.sum == 12 # cache invalidated because "a" changed
Inline (non-decorator) form:
from modict import modict
class Calc(modict):
a: int
b: int
sum = modict.computed(lambda m: m.a + m.b, cache=True, deps=["a", "b"])
Inline “dict value” form (no subclass):
from modict import modict
m = modict({"a": 1, "b": 2})
m["sum"] = modict.computed(lambda m: m.a + m.b)
assert m.sum == 3
Notes:
- computed values are stored as
Computedobjects inside the dict and evaluated via__getitem__ - returned values still go through the validation pipeline (type checks, constraints, JSON, …) when enabled
- invalidation semantics:
deps=None(default): invalidate on any key changedeps=[...]: invalidate only when one of those keys changes (can include other computed names)deps=[]: never invalidate automatically
Manual invalidation:
If you use deps=[] (or if updates happen outside of modict assignment hooks), you can invalidate caches explicitly:
from modict import modict
m = modict({"a": 1, "b": 2})
m["sum"] = modict.computed(lambda m: m.a + m.b, cache=True, deps=[])
_ = m.sum # cached
m.a = 10 # deps=[] -> no auto invalidation
assert m.sum == 3
m.invalidate_computed("sum")
assert m.sum == 12
# or invalidate everything:
m.invalidate_computed()
Validation Pipeline
The pipeline is controlled by _config.check_values:
check_values="auto"(default): enabled when the class looks model-like (hints/validators/config/constraints).check_values=True: always enabled.check_values=False: bypassed (pure dict behavior).
Related config flags:
strict: whenTrue, no coercion is attempted.validate_assignment: validate on__setitem__/__setattr__.use_enum_values: whenTrue, enum values are stored/validated as.value.enforce_json: whenTrue, values must be JSON-serializable (encoders can help).
When enabled, validation is applied:
- eagerly at initialization (
__init__→validate()) - optionally on assignment (
validate_assignment=True) - on reads of computed fields (
__getitem__computes then validates the returned value)
Order of operations for a field value:
use_enum_values: if enabled and the value is anEnum, replace withvalue.value- string transforms (optional):
str_strip_whitespace,str_to_lower,str_to_upper - field validators in
mode="before" - coercion (only when
strict=False) - type check (if the field has a type hint)
- field validators in
mode="after" - type check again (post-validators can still transform)
- field constraints (
constraints=...on theField) - JSON-serializability check (
enforce_json=True, with optional encoders)
Configuration (Deep Dive)
All model-like behavior is controlled by the class attribute _config, a modictConfig dataclass created via modict.config(...).
class User(modict):
_config = modict.config(
check_values="auto",
extra="allow",
strict=False,
validate_assignment=False,
auto_convert=True,
)
Config reference
check_values:True/False/"auto"."auto"enables the pipeline when the class looks model-like (has hints/validators/model validators) or when config implies processing (e.g.extra != "allow",enforce_json=True,strict=True, …).
check_keys:True/False/"auto".- Key-level constraints are structural checks (presence/allowed-keys/invariants), independent from value validation.
"auto"enables key constraints when the model (or instance) declares them (e.g.extra != "allow",require_all=True, computed fields, or any field withrequired=True).- When
False,modictbehaves more like a plain dict regarding keys: it won’t enforcerequired=True,require_all=True,extra="forbid"/"ignore", or computed overwrite/delete protection. frozen=Trueis always enforced (it is not controlled bycheck_keys).
Example: keep structure strict but skip value coercion/type checking:
class Msg(modict):
_config = modict.config(check_values=False, check_keys=True, extra="forbid")
role: str = modict.field(required=True)
content: str = modict.field(required=True)
extra:"allow"(default) /"forbid"/"ignore"."forbid"raises on unknown keys at init and on assignment."ignore"drops unknown keys at init, and ignores them on assignment.
strict: whenTrue, disables coercion (type checking still applies when hints exist).validate_assignment: whenTrue, assignment goes through the full pipeline.frozen: whenTrue,__setitem__/__delitem__raise (read-only instances).auto_convert: whenTrue, values stored inside nested mutable containers are lazily upgraded on access:dict→modict(plainmodict, not your subclass)- nested containers inside lists/tuples/sets/dicts are upgraded as you touch them
enforce_json: whenTrue, values must be JSON-serializable.allow_inf_nancontrols whetherNaN/Infinityare allowed when encoding (default:True).json_encoderslets you providetype -> callableencoders for serialization andenforce_json=True.
use_enum_values: whenTrue, enums are normalized to.valueduring validation and serialization.str_strip_whitespace,str_to_lower,str_to_upper: optional Pydantic-like string transformations.- if both
str_to_lowerandstr_to_upperareTrue, lower takes precedence.
- if both
populate_by_name: whenFalseand a field has an alias, input must use the alias (field name is rejected).alias_generator: callable(field_name: str) -> strapplied at class creation time to fields without explicit aliases.validate_default: whenTrue, defaults are type-checked at class creation (skipsFactory/Computed).from_attributes: whenTrue,MyModict(obj)can read declared fields fromobj.fieldattributes (whenobjis not a mapping).
override_computed: whenFalse(default), prevents overriding/deleting computed fields (and passing initial values for computed fields). Set toTrueto allow it explicitly.require_all: whenTrue, requires all declared class fields (including computed) to be present at initialization; declared fields cannot be deleted (annotation-only fields become required).evaluate_computed: whenTrue(default), computed fields are evaluated on access; whenFalse, computed fields are treated as raw stored objects (no evaluation).
Required vs defaults (dict-first semantics):
- A class default (e.g.
age: int = 25) is an initializer: it is injected once at construction if missing, but the key is still removable later whenrequire_all=False. - A field is an invariant only when you opt in to it: set
required=Trueon the field (orrequire_all=Trueon the model) to enforce presence.
Performance / dict-like mode
If you want modict to behave as close as possible to a plain dict (minimal overhead), you can opt out of most advanced features:
class Fast(modict):
_config = modict.config(
check_values=False, # skip validation/coercion pipeline
check_keys=False, # skip structural key constraints (required/extra/...)
auto_convert=False, # skip lazy conversion of nested containers on access
evaluate_computed=False # treat Computed as raw stored objects
)
Config inheritance / merging
modict merges configs across inheritance in a Pydantic-like way:
- config values explicitly set in a subclass override inherited values
- when using multiple inheritance, the left-most base wins (for explicitly-set config keys)
Aliases
Aliases live in Field.aliases (not in metadata).
Supported keys:
alias: single alias (common case)validation_alias: string or list of strings (accepted input keys)serialization_alias: string (used bymodel_dump(by_alias=True))
Input behavior is controlled by _config.populate_by_name:
populate_by_name=False: if a field has aliases, only aliases are accepted (field name is rejected).populate_by_name=True: accept both alias and field name (but never both at once).
class User(modict):
_config = modict.config(populate_by_name=False)
name: str = modict.field(aliases={"alias": "full_name"})
User({"full_name": "Alice"}) # OK
alias_generator
Generate aliases automatically for fields that do not define any explicit alias:
class User(modict):
_config = modict.config(alias_generator=str.upper, populate_by_name=False)
name: str
User({"NAME": "Alice"})
Type Checking & Coercion
modict relies on its internal runtime type system (in modict/_typechecker/) for:
- type checking against annotations (
check_type(hint, value)) - best-effort coercion (
coerce(value, hint)) whenstrict=False - the
@typecheckeddecorator for runtime checking of function arguments/return values
This subsystem supports common typing constructs (e.g. Union, Optional, list[str], dict[str, int], tuple[T, ...], ABCs from collections.abc, …).
If coercion fails, the original value is kept; the subsequent type check decides whether it’s accepted (depending on hints and strict).
Serialization
modict provides lightweight Pydantic-like serialization helpers.
model_dump / model_dump_json
data = u.model_dump(by_alias=True, exclude_none=True)
json_str = u.model_dump_json(by_alias=True)
Supported options:
by_alias: useserialization_aliasthenaliasexclude_none: drop keys withNoneinclude/exclude: sets of field names (not aliases)encoders: mappingtype -> callable(see below)
JSON encoders
Use encoders to serialize custom types and to satisfy enforce_json=True:
from datetime import datetime
class Event(modict):
_config = modict.config(json_encoders={datetime: lambda d: d.isoformat()})
ts: datetime
dumps / dump
dumps() / dump() are thin wrappers around json.dumps / json.dump, and support:
by_aliasexclude_noneencoders
JSON Schema
json_schema() exports a Draft 2020-12 JSON Schema from a modict class:
schema = User.json_schema()
Notes:
$schemais alwayshttps://json-schema.org/draft/2020-12/schemaconstraintsare mapped to standard JSON Schema keywords (minimum,multipleOf,pattern, …)additionalPropertiesis emitted only whenextra="forbid"(diff vs JSON Schema default)
Deep Conversion & Deep Ops
Deep conversion
modict ships with conversion utilities designed to preserve container identity as much as possible:
modict.convert(obj): recursively upgradesdictnodes tomodict(and walks into mutable containers).- root dict becomes your class; nested dicts become plain
modictunless they were already instances. recurse=Falsestops recursion when reaching a modict node (used internally for lazyauto_convert).
- root dict becomes your class; nested dicts become plain
m.to_modict(): deep conversion of an instance in-place (callsconvert(self)).m.to_dict(): deep un-conversion back to plain containers.
Deep operations on nested structures
These operations are implemented on top of the modict._collections_utils package:
walk()/walked(): flatten a nested structure to(Path, value)pairs.unwalk(walked): reconstruct a nested structure from a{Path: value}mapping, preserving container classes when possible.merge(mapping): deep, in-place merge (mappings merge by key; sequences merge by index).diff(mapping): deep diff that returns{Path: (left, right)}withMISSINGfor absent values.deep_equals(mapping): deep equality by comparing walked representations.
Pydantic Interop (Optional)
Pydantic interoperability is an optional feature that operates at the class level (it converts model classes, not just instances).
When Pydantic is installed:
modict.from_model(PydanticModel)converts a PydanticBaseModelclass into a newmodictsubclass.MyModict.to_model()converts amodictsubclass into a new PydanticBaseModelclass.
This is designed for bidirectional, best-effort round-trips (Pydantic v1 and v2 supported).
In practice:
modict → Pydanticis the “clean” direction: sincemodictis the source of truth, the generated Pydantic model is deterministic for the featuresmodictsupports.Pydantic → modictis inherently best-effort: Pydantic has a larger feature surface, and some semantics (especially advanced validation/serialization behaviors) don’t have 1:1 equivalents in a dict-first model.
What gets converted (best-effort)
- Fields: annotations, defaults, and default factories (
Field(default_factory=...)↔modict.factory(...)). - Config: common Pydantic-aligned options (
extra,strict,frozen,validate_assignment, string transforms, aliases-related settings, …). - Aliases:
alias,validation_alias,serialization_aliaswhere representable. - Validators:
- field validators (before/after when possible)
- model/root validators (before/after when possible)
- Computed fields:
- Pydantic v2 computed fields ↔
modictcomputed fields (best-effort forcache/deps)
- Pydantic v2 computed fields ↔
- Nested models:
- referenced
BaseModeltypes inside annotations are recursively converted to nestedmodictsubclasses (cached to preserve sharing)
- referenced
Pydantic-only metadata that can’t be expressed directly in modict is preserved under Field._pydantic so it can be re-emitted when converting back.
Quick example (class-level conversion)
from pydantic import BaseModel, Field
from modict import modict
class UserModel(BaseModel):
name: str
tags: list[str] = Field(default_factory=list)
User = modict.from_model(UserModel) # Pydantic class -> modict class
UserModel2 = User.to_model(name="UserV2") # modict class -> Pydantic class
Package Tour (Internal Modules)
This section is an overview of the main internal modules and what functionality they implement. If you only need the user-facing API, you can skip to Public API Reference.
modict/_modict.py (the modict class)
Core behaviors implemented here:
- dict subclass with attribute access (
m.key↔m["key"]) while keeping Python attributes working - validation pipeline (
validate(), assignment validation, extra handling, constraints) - computed fields evaluation and dependency-based cache invalidation
- lazy nested conversion (
auto_convert) implemented in__getitem__ - nested ops (
get_nested,set_nested,pop_nested, …) backed by JSONPath/Path - deep ops (
walk,walked,unwalk,merge,diff,deep_equals,deepcopy) - JSON helpers (
loads,load,dumps,dump, plusmodel_dump*)
modict/_modict_meta.py (metaclass + field system)
Defines the "model-like layer" that turns a dict subclass into something schema/validation aware:
modictMeta: collects declared fields from__annotations__and assigned attributes- supports plain defaults,
modict.field(...)(Field),modict.factory(...)(Factory), and@modict.computed(...)(Computed) - collects
@modict.validator(...)into per-field validators - collects
@modict.model_validator(...)into model-level validators
- supports plain defaults,
modictConfig: configuration object with explicit-key tracking and inheritance merge semanticsField: storeshint,default,metadata,constraints,aliases(plus an internal_pydanticbucket)Validator/ModelValidator: best-effort signature adapters (modict-style and Pydantic-style callables)modictKeysView/modictValuesView/modictItemsView: dict views that read through__getitem__(so computed + lazy conversion + validation happen during iteration)
modict/_collections_utils/ (nested structure utilities)
This package is responsible for paths, nested operations, and deep traversal.
modict/_collections_utils/_path.pyPath/PathKey: JSONPath (RFC 9535) parsing and formatting viajsonpath-ng- type-aware path components (
PathKey.container_class) sowalk()→unwalk()can preserve container types Path.normalize(...)to accept JSONPath strings, tuples, orPathobjects
modict/_collections_utils/_basic.py- container-agnostic
get_key/set_key/has_key/keys/unroll
- container-agnostic
modict/_collections_utils/_advanced.pyget_nested/set_nested/pop_nested/del_nested/has_nestedwalk/walked/unwalkdeep_merge/diff_nested/deep_equals
modict/_collections_utils/_view.pyView: base class to build custom collection views over mappings or sequences
modict/_collections_utils/_missing.pyMISSING: sentinel to distinguish "missing" fromNone
modict/_typechecker/ (runtime typing + coercion)
This subpackage backs the runtime typing API exported by modict:
TypeChecker: checks values againsttypinghints and collection ABCsCoercer: best-effort conversions for common hints/containers- convenience API:
check_type,coerce,can_coerce,typechecked
modict/_pydantic_interop.py (optional Pydantic conversion)
Class-level conversions, with Pydantic v1 and v2 support:
from_pydantic_model(...): Pydantic → modict- converts nested
BaseModeltypes to nested modict subclasses (with a global cache) - maps a conservative subset of Pydantic Field/Config metadata into
Field.metadata/Field.constraints/Field.aliasesand preserves extra info underField._pydantic - imports field validators and model validators when possible (mode
before/after) - extracts computed fields (Pydantic v2) and maps them to
Computedfields (best-effort forcache/deps)
- converts nested
to_pydantic_model(...): modict → Pydantic- recreates
Field(...)declarations, validators, model validators, and (v2) computed fields - maps modict config back to a Pydantic config, only emitting non-default values
- recreates
TypeCache: weak-reference cache for modict ↔ Pydantic class conversions (to preserve sharing and break cycles)
Public API Reference
This section lists the public symbols exported by modict and the main methods on modict instances.
Exports
From from modict import ...:
- Data structure:
modict
- Field system:
Field(advanced; most users should prefermodict.field(...))Factory(advanced; most users should prefermodict.factory(...))Computed(advanced; most users should prefer@modict.computed(...))Validator,ModelValidator(advanced; decorators are the typical entry-point)
- Config:
modictConfig(usually created viamodict.config(...))
- JSONPath types:
Path,PathKey
- Sentinel:
MISSING
- Pydantic interop cache:
TypeCache
- Type checking / coercion:
check_type(hint, value)coerce(value, hint)can_coerce(value, hint)typechecked(decorator)TypeCheckerCoercer- Exceptions:
TypeCheckError,TypeCheckException,TypeCheckFailureError,TypeMismatchError,CoercionError
modict class methods
modict.config(**kwargs) -> modictConfigmodict.field(...) -> Fieldmodict.factory(callable) -> Factory@modict.validator(field_name, mode="before"|"after")@modict.model_validator(mode="before"|"after")@modict.computed(cache=False, deps=None)modict.json_schema(excluded: set[str] | None = None) -> dict- JSON helpers:
modict.loads(s, **json_kwargs) -> modictmodict.load(fp_or_path, **json_kwargs) -> modict
- Pydantic interop (optional dependency):
modict.from_model(PydanticModel, *, name=None, **config_kwargs) -> type[modict]MyModict.to_model(*, name=None, **config_kwargs) -> type[pydantic.BaseModel]
- Conversion:
modict.convert(obj, seen=None) -> Anymodict.unconvert(obj, seen=None) -> Anymodict.unwalk(walked: dict[Path, Any]) -> Any
modict instance methods
Instance methods keep standard dict behavior, plus:
- Validation:
validate()
- Conversion:
to_modict() -> modict(deep conversion)to_dict() -> dict(deep unconvert)
- Serialization:
model_dump(by_alias=False, exclude_none=False, include=None, exclude=None, encoders=None) -> dictmodel_dump_json(...) -> strdumps(..., by_alias=False, exclude_none=False, encoders=None) -> strdump(fp_or_path, ..., by_alias=False, exclude_none=False, encoders=None) -> None
- Nested operations (JSONPath / tuple / Path):
get_nested(path, default=MISSING)set_nested(path, value)del_nested(path)pop_nested(path, default=MISSING)has_nested(path) -> bool
- Key operations:
rename(mapping_or_kwargs) -> modictexclude(*keys) -> modictextract(*keys) -> modict
- Deep operations:
merge(mapping) -> modictdiff(mapping) -> dictdeep_equals(mapping) -> booldeepcopy() -> modict
- Walking:
walk(callback=None, filter=None, excluded=None) -> Iterable[tuple[Path, Any]]walked(callback=None, filter=None) -> dict[Path, Any]
- Computed cache:
invalidate_computed(*names) -> None(no args = all)invalidate_all_computed() -> None
Development
python3 -m pytest -q
Contributing
Contributions are welcome.
- Please open an issue to discuss larger changes.
- For pull requests: add/adjust tests under
tests/and keeppython3 -m pytest -qgreen. - Local setup:
pip install -e ".[dev]".
See CONTRIBUTING.md for details.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file modict-0.3.2.tar.gz.
File metadata
- Download URL: modict-0.3.2.tar.gz
- Upload date:
- Size: 560.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d59c20b493f75b06c91857945648cc9d733315fe8802f0a846d8c354a2ba9e46
|
|
| MD5 |
f323606a78fa7eb4b45aff29ebd63e6b
|
|
| BLAKE2b-256 |
1d5645fc419101a80392d6c3887d4f4dce66f6ddc851260cd6581d79d6015e2a
|
File details
Details for the file modict-0.3.2-py3-none-any.whl.
File metadata
- Download URL: modict-0.3.2-py3-none-any.whl
- Upload date:
- Size: 615.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3eedc2deb2c1f47659ebfa306147771e61b1c59a3ed8d4aed84064f5541373ad
|
|
| MD5 |
a788497e778c7da16d218b552490a395
|
|
| BLAKE2b-256 |
aedcf753cb434fc8ab73d666129811b0ac8d55a9eccb29fb2d88c9fb60389bcd
|