What's in the box

A small core,
a deep toolkit

One property — the object retains its own structure — and a compact set of sub-packages that all speak about the same tree. Hot paths run in a native Rust core; the most common entry points are re-exported at the top level as pg.*.

The semantic dial

Per-class and per-field args, and what they buy

pg.Object spans a spectrum — from a plain mutable dataclass to a frozen symbolic tree node. You pick where you sit with class-statement kwargs, and override per field where one field needs to differ.

Class-statement kwargs

KwargDefaultSemantics it employs
topoFalseThe top-level axis. True makes instances symbolic-tree nodes: topo_path / topo_parent, contextual resolution, upward change notification — and flips the write posture to rebind-first. False is a flat, reference-semantics object.
validateTrueValues flow through each field's ValueSpec at construct, assignment, and rebind — type check, coercion, default-filling. Off, values land verbatim.
attr_writefollows not topoGates direct obj.x = v (validated when on). sym_rebind still works either way — only frozen seals it.
frozenFalseSeals the object at construction — even sym_rebind raises. Derive changed values with sym_clone(override=...), the dataclasses.replace() analog.
eqTrueValue-based __eq__ / __hash__ via sym_eq / sym_hash. Off: identity — for unhashable fields or when node identity is the key.
orderFalseSynthesizes < / <= / > / >= over the compare=True fields, à la @dataclass(order=True).
attr_readTrueDotted obj.x access. Off, reads go through obj.sym_get(...) only.
initTruePEP 681 knob: init=False keeps a parent's explicit __init__ visible to pyright instead of resynthesizing. Runtime no-op.

Common combinations

SemanticDeltas from defaultYou get
Plain dataclassvalidate=FalseRaw verbatim values, free mutation.
Validated record(none — the default)Validated, value-equal, mutable. Most subclasses.
Immutable valueattr_write=False or frozen=TrueConstruct-once values; safe dict/set keys.
Symbolic nodetopo=TrueTree positions, path-targeted rebind, patching, search.
Frozen nodetopo=True, frozen=TrueShared constants; sealed against any change.

Per-field flags — pg.field(...)

FlagSemantics it employs
default / default_factoryStatic default (recognized shapes deep-copy per instance) or a per-instance factory.
value_specConstraints beyond the annotation — bounds, regex, enums, converters.
transform / validatorPost-validation transform, or a reject-only check on construct, assignment, and rebind.
repr / compare / hashDrop the field from format() / sym_eq / sym_hash.
init=False (+ clone)Derived field the class manages (typically in on_sym_ready); clone preserves it through sym_clone.
alias / doc / metadataWire-layer key alias, one-line docstring, tool-facing metadata.
the annotation itselfContainer storage: pg.Dict[...] is symbolic on both axes, builtin dict stays raw, unauthored follows topo.

The dial is semantic, not a performance lever — every rung runs on the same native core. Full treatment in the pg.Object style guide.

Module reference

Focused packages, one tree

Each does one thing well and composes with the rest through the shared symbolic tree. The tree itself is opt-in per class with topo=True.

Start with one class.

Everything here builds on the same pg.Object. Adopt only the pieces your problem needs.