pygx.symbolic¶
Symbolic classes, containers and functions, plus the operations on them.
symbolic
¶
Symbolic classes, containers and functions, plus the operations on them.
This module is the core of PyGX's symbolic object model: a symbolic value keeps the arguments it was built from as an inspectable tree, so a program's objects can be examined, compared, cloned and rewritten after creation. It provides:
- Symbolic types —
pg.Object(dataclass-like classes), the containerspg.Dictandpg.List, andpg.Functor(functions whose arguments bind incrementally). - Symbolizers for existing code —
pg.symbolize,pg.wrap,pg.functorandpg.compoundturn regular classes and functions into symbolic ones without rewriting them. - Operations on symbolic values —
pg.eq,pg.diff,pg.query,pg.traverseandpg.clone. - Serialization and IO —
pg.to_json,pg.from_json,pg.load,pg.saveandpg.from_uri. - Global and scoped flags that control symbolic behaviors — e.g.
pg.notify_on_change,pg.as_sealedandpg.track_origin.
Most names here are also re-exported at the package top level (pg.Object,
pg.eq, ...).
ContextualObject
¶
ContextualObject(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: Object
Base class for contextual objects.
Contextual objects are objects whose attributes can be dynamically
overridden using pg.contextual.override or resolved through
pg.contextual.Placeholder, allowing them to inherit values from
ancestor ContextualObjects in the containing tree.
Note that ContextualObject subclasses are declared with topo=True:
contextual resolution walks the parent chain, so instances always
participate in the bi-directional symbolic tree.
An attribute is resolved from the first available source, in order:
- An instance-level
self.override(...)scope. - A
pg.contextual.override(..., override_attrs=True)scope (this is the only form that overrides attributes that already have values). - The attribute's own value; if it is a
Placeholder, the nearest ancestorContextualObjectthat carries the attribute. - A plain
pg.contextual.override(...)scope. - The
Placeholder'sdefault, if given.
Example:
import threading
from typing import Any
import pygx as pg
# Define a contextual object.
class A(pg.ContextualObject):
x: int
y: Any = pg.contextual.Placeholder()
# Create an instance of A.
a = A(x=1)
print(a.x) # Outputs: 1
# Reading `a.y` here would raise AttributeError: `y` has no value and
# `a` has no containing object to resolve it from.
# Define another contextual object containing an instance of A.
class B(pg.ContextualObject):
y: int
a: A
# Create an instance of B, containing `a`.
b = B(y=2, a=a)
print(a.y) # Outputs: 2, as `y` is resolved from the containing B.
# Overriding an attribute that already has a value requires
# `override_attrs=True`.
with pg.contextual.override(x=2, override_attrs=True):
print(a.x) # Outputs: 2
# Thread-specific behavior of `pg.contextual.override`.
def foo(a):
print(a.x)
with pg.contextual.override(x=3, override_attrs=True):
t = threading.Thread(target=foo, args=(a,))
t.start()
t.join()
# Outputs: 1, because `pg.contextual.override` is limited to the
# current thread to avoid clashes in multi-threaded environments.
# To propagate the override to a new thread, use
# `pg.contextual.with_override`.
with pg.contextual.override(x=3, override_attrs=True):
t = threading.Thread(target=pg.contextual.with_override(foo), args=(a,))
t.start()
t.join()
# Outputs: 3, as the override is explicitly propagated.
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
sym_inferred
¶
Infers the attribute value, consulting scoped contextual overrides.
Resolution order: instance-level self.override(...) scopes, then
pg.contextual.override(..., override_attrs=True) scopes, then the
symbolic tree (the stored value, or for a Placeholder the chain
of containing ContextualObjects), then plain
pg.contextual.override(...) scopes, then the Placeholder's
default.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Attribute name. |
required |
default
|
Any
|
Value to return if inference fails (else the error propagates). |
RAISE_IF_NOT_FOUND
|
**kwargs
|
Any
|
Optional keyword arguments for value inference. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
The value of the symbolic attribute. If not available, returns the |
Any
|
default value. |
Raises:
| Type | Description |
|---|---|
AttributeError
|
The attribute does not exist or the contextual
attribute is not ready, and no |
Source code in pygx/contextual/_contextual_object.py
override
¶
override(**kwargs) -> ContextManager[dict[str, Override]]
Context manager to override attributes of this instance only.
Instance-level overrides are context-local (contextvars — scoped
per task/thread), take precedence over pg.contextual.override,
and apply even to attributes that already have values.
Example:
class A(pg.ContextualObject):
x: int
a = A(x=1)
with a.override(x=2):
assert a.x == 2
assert a.x == 1
Source code in pygx/contextual/_contextual_object.py
DescendantQueryOption
¶
Bases: Enum
Options for selecting which matched descendants to return.
Passed as the option argument of
Symbolic.sym_descendants.
IMMEDIATE
class-attribute
instance-attribute
¶
Returns only the outermost matched descendants: once a node matches, its sub-tree is not searched further.
LEAF
class-attribute
instance-attribute
¶
Returns only the innermost matched descendants: a matched node is dropped when a deeper match exists under it.
FieldUpdate
¶
FieldUpdate(
path: KeyPath,
target: Symbolic,
field: Field | None,
old_value: Any,
new_value: Any,
)
Bases: Formattable
Describes one committed update to a field in an object tree.
This is the payload of change notification: on_sym_change receives a
dict of relative key path to FieldUpdate, one per changed field.
Each instance carries the path of the changed field, the target
node owning it, its field definition (None for keys no schema
field describes), and the old_value / new_value pair.
Constructor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
KeyPath
|
KeyPath of the field that is updated. |
required |
target
|
Symbolic
|
Parent of updated field. |
required |
field
|
Field | None
|
Specification of the updated field. |
required |
old_value
|
Any
|
Old value of the field. |
required |
new_value
|
Any
|
New value of the field. |
required |
Source code in pygx/symbolic/_base.py
restore
¶
Writes old_value back, undoing this update.
The rollback seam: _validate_model_or_rollback calls this instead of
writing through target directly, because where an update landed is
not always a key target owns. target names the node that OBSERVES
the change (it drives notification and path arithmetic); the subclass
below writes back into the raw container that actually holds the key.
A missing old value means this update ADDED the member, and the undo
depends on the container: dispatch to the target's
_sym_restore_missing (#749) rather than assuming the write core's
missing arm fits. Dispatching on the TARGET (not an update subclass)
keeps the fork out of update construction, which the native core
performs itself through the bridge-held FieldUpdate class.
Source code in pygx/symbolic/_base.py
format
¶
Formats this object.
Source code in pygx/symbolic/_base.py
Inferential
¶
Bases: TopologyAware, CustomTyping
Interface for values that could be dynamically inferred upon read.
Inferential values are objects whose values are not stored directly but
derived when read — e.g. a reference to another object
(pg.Ref), or a value computed from the symbolic
tree the object resides in
(pg.symbolic.ValueFromParentChain).
When inferential values are utilized as symbolic attributes, we can obtain
their original definition by invoking pg.Symbolic.sym_getattr, and
their inferred values can be retrieved by calling
pg.Symbolic.sym_inferred. The values retrieved from pg.Dict,
pg.List and pg.Object through __getitem__ or
__getattribute__ are all inferred values.
infer
abstractmethod
¶
Returns the inferred value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Any
|
Optional keyword arguments for inference, which are usually inferential subclass specific. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
Inferred value. |
Raises:
| Type | Description |
|---|---|
AttributeError
|
If the value cannot be inferred. |
Source code in pygx/symbolic/_base.py
needs_topo
¶
Whether infer() reads the symbolic tree.
A topo=False holder has no tree, so an inferential whose
infer() walks topo_parent / reads topo_path cannot resolve
there — such a holder refuses the value with TopoNotEnabled where
it lands (construction, assignment, sym_rebind), and reading one
that got in another way raises the same. One whose infer() is
self-contained (pg.Ref returns its own referent; a lazily-computed
value calls its own thunk) has no such dependency and is held and
resolves fine in either mode.
Defaults to True — the conservative answer, and correct for the
contextual family this interface was originally shaped around.
Override to False when infer() touches no tree state.
Source code in pygx/symbolic/_base.py
SharedHoldError
¶
Bases: Exception
Raised when an already-held topo=True value is held a second time.
A node in a symbolic tree has ONE position: topo_path / topo_parent
/ topo_root are single-valued functions of it, and change notification
routes along that single chain. Holding one node from two places would
make all of them ambiguous, so the second hold has to be resolved — and
only the caller knows which resolution was meant:
holder1 = Container(value=node) # adopts `node`
# `node` is held now. A second hold must say what it wants:
holder2 = Container(value=pg.maybe_ref(node)) # SHARE one node
holder3 = Container(value=pg.maybe_clone(node)) # independent COPY
Neither is the default. Sharing keeps the holders coupled — writes through one are visible through the other; copying keeps them independent but forks the state at that moment. Both are correct for some code and wrong for other code, so pygx asks rather than guesses.
Historically the second hold was silently copied. That was ambiguous in a way this error removes: the same expression adopted a free value but copied a held one, so its meaning depended on whether some unrelated holder happened to get there first — and on construction order, which decided WHICH holder aliased the caller's object.
Symbolic
¶
Symbolic(
*,
allow_partial: bool,
accessor_writable: bool,
sealed: bool,
root_path: KeyPath | None,
init_super: bool = True
)
Bases: TopologyAware, Formattable, WireConvertible, MaybePartial, HtmlConvertible
Base for all symbolic types.
Symbolic types are types that provide interfaces for symbolic programming, based on which symbolic objects can be created. In PyGX, there are three categories of symbolic types:
- Symbolic classes: Defined by
pygx.Objectsubclasses, including symbolic classes created frompygx.symbolize, which inheritpygx.ClassWrapper, a subclass ofpg.Object. - Symbolic functions: Defined by
pygx.Functor. - Symbolic container types: Defined by
pygx.Listandpygx.Dict.
Constructor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allow_partial
|
bool
|
Whether to allow required fields to be MISSING_VALUE or partial. |
required |
accessor_writable
|
bool
|
Whether to allow write access via attributes. This flag
is useful when we want to enforce update of fields using |
required |
sealed
|
bool
|
Whether the object is sealed (closed against modification). This flag is useful when we don't want downstream to modify the object. |
required |
root_path
|
KeyPath | None
|
KeyPath of current object in its context (object tree). |
required |
init_super
|
bool
|
If True, call super.init, otherwise short-circuit.
This flag is useful when users want to explicitly implement
|
True
|
Source code in pygx/symbolic/_base.py
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 | |
sym_partial
property
¶
Returns True if current value is partial (has missing values).
A partial value has one or more required fields still unbound
(pg.MISSING_VALUE), e.g. an object created via partial() or
under pygx.allow_partial.
sym_puresymbolic
property
¶
Returns True if current value is PureSymbolic or contains one.
sym_abstract
property
¶
Returns True if current value is abstract (partial or pure symbolic).
topo_field
property
¶
topo_field: Field | None
Returns the symbolic field for current object.
Available only under topo=True — which field holds a value is a
position question.
topo_root
property
¶
topo_root: Symbolic
Returns the root of the symbolic tree.
Available only under topo=True; a topo=False value is its own
world and has no tree to be the root of.
topo_parent
property
¶
topo_parent: Symbolic
Returns the containing symbolic object (None at the root).
Available only under topo=True. A topo=False value is data: it
is held by reference rather than adopted, so it has no containing
object to name, and asking raises TopoNotEnabled rather than
answering None (§10.4 — a data object never answers position
questions, not even with a degraded value).
topo_path
property
¶
topo_path: KeyPath
Returns the path of current object from the root of its symbolic tree.
Available only under topo=True. A topo=False value has no tree
position, so asking raises TopoNotEnabled rather than answering
the empty pg.KeyPath() (§10.4 — "where am I" is not a question data
answers).
accessor_writable
property
¶
Returns True if mutation can be made by attribute assignment.
sym_seal
¶
Seals or unseals current object from further modification.
Sealing is deep: the entire sub-tree (including owned containers)
becomes read-only, and any write raises WritePermissionError
until the object is unsealed (sym_seal(False)) or the write runs
under a pg.as_sealed(False) scope. Returns self.
Source code in pygx/symbolic/_base.py
sym_intern
¶
Returns the canonical instance for this sealed value.
Interning (graph-mode.md §11.0 ruling 22's deferred increment) trades
identity for memory: two sealed nodes that are sym_eq and hash alike
collapse onto ONE shared instance, so a program holding many equal
frozen values (config trees, cache keys, memo tables) pays for one.
Opt-in per node, keyed on the frozen hash the seal already caches:
class Foo(pg.Object):
x: int
a = Foo(x=1).sym_seal()
b = Foo(x=1).sym_seal()
assert a is not b
assert a.sym_intern() is b.sym_intern()
The FIRST value interned for a given equivalence class becomes the canonical one and is returned for every later equal value; callers must use the RETURN value, not the receiver (which is unchanged — interning never mutates, so it cannot break a caller still holding the original).
Sealed only. Interning an unsealed node would publish a value whose hash can still change, corrupting the pool, so it raises. This is the same soundness argument that scopes the frozen-hash cache to the sealed window (ruling 22) — and the reason interning is keyed on that cache.
The pool holds values WEAKLY. A canonical entry survives only while
some caller holds it; once the last reference drops, the entry leaves
and a later equal value becomes canonical in its turn. So interning
never accumulates unbounded state, matching this branch's weak-edge
posture (#629). Values that cannot be weakly referenced (a node with no
__weakref__ slot) decline the pool and are returned as-is —
interning is an optimization, never a correctness requirement.
Returns:
| Type | Description |
|---|---|
Self
|
The canonical instance: |
Self
|
equivalence class (or the pool declined it), otherwise the equal |
Self
|
instance already interned. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If this object is not sealed. |
A cyclic value cannot reach the pool at all today: the container
adoption guard rejects the insertion that would close the cycle, so
such a value never gets built, let alone sealed. Once #630 gives
sym_seal its own cycle guard, whatever exception that guard raises
belongs in this contract — documented here rather than named, since
naming an exception this module does not define would promise a
symbol callers cannot import.
Source code in pygx/symbolic/_base.py
1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 | |
sym_missing
abstractmethod
¶
Returns missing values as a dict of key (or key path) to marker.
A value is missing when a required field is still unbound
(pg.MISSING_VALUE) — only partial values have any. Nested missing
values are included: as nested dicts when flatten is False, as
flattened key-path entries (e.g. 'a.b') when True.
The public override point (there is no protected _sym_missing hook):
concrete subclasses (Object / Dict / List / Functor)
override this directly and route their computation through
self._sym_cached('_sym_missing_values', compute, flatten). A user
subclass overrides it too and may call super().sym_missing() to get
the (cached) parent result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
flatten
|
bool
|
If True, flatten nested structures into a key-path dict. |
True
|
Source code in pygx/symbolic/_base.py
sym_nondefault
abstractmethod
¶
Returns the values differing from their declared field defaults.
The result maps keys (or flattened key paths, when flatten is
True) to the differing values.
The public override point (there is no protected _sym_nondefault
hook); see sym_missing for the override / caching contract.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
flatten
|
bool
|
If True, flatten nested structures into a key-path dict. |
True
|
Source code in pygx/symbolic/_base.py
topo_ancestor
¶
topo_ancestor() -> Optional[Symbolic]
topo_ancestor(where: Callable[[Any], bool]) -> Optional[Symbolic]
Returns the nearest ancestor matching where.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
where
|
type[_T] | Callable[[Any], bool] | None
|
One of: - None (default): returns the immediate parent. - A class: returns the nearest ancestor that is an instance of the class. Static type-checkers narrow the result to that class. - A callable: returns the nearest ancestor for which the callable returns True. |
None
|
Available only under topo=True — a topo=False value has no
ancestors to search.
Source code in pygx/symbolic/_base.py
sym_descendants
¶
sym_descendants(
where: None = None,
option: DescendantQueryOption = ...,
include_self: bool = ...,
) -> list[Any]
sym_descendants(
where: type[_T],
option: DescendantQueryOption = ...,
include_self: bool = ...,
) -> list[_T]
sym_descendants(
where: Callable[[Any], bool],
option: DescendantQueryOption = ...,
include_self: bool = ...,
) -> list[Any]
sym_descendants(
where: type[_T] | Callable[[Any], bool] | None = None,
option: DescendantQueryOption = ALL,
include_self: bool = False,
) -> list[Any]
Returns all descendants matching where.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
where
|
type[_T] | Callable[[Any], bool] | None
|
One of:
- None (default): all descendants are returned.
- A class: descendants that are instances of the class are returned.
Static type-checkers narrow the result to |
None
|
option
|
DescendantQueryOption
|
Descendant query options, indicating whether all matched, immediate matched or only the matched leaf nodes will be returned. |
ALL
|
include_self
|
bool
|
If True, |
False
|
Returns:
| Type | Description |
|---|---|
list[Any]
|
A list of objects that match |
Source code in pygx/symbolic/_base.py
sym_has
¶
sym_has(path: KeyPath | str | int) -> bool
Returns True if a path exists in the sub-tree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
KeyPath | str | int
|
A KeyPath object or its equivalent: a path string (e.g.
|
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the path exists in current sub-tree, otherwise False. |
Source code in pygx/symbolic/_base.py
sym_get
¶
sym_get(
path: KeyPath | str | int,
default: Any = RAISE_IF_NOT_FOUND,
use_inferred: bool = False,
) -> Any
Returns a sub-node by path.
NOTE: there is no sym_set, use sym_rebind.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
KeyPath | str | int
|
A KeyPath object or its equivalent: a path string (e.g.
|
required |
default
|
Any
|
Default value if path does not exist. If absent, |
RAISE_IF_NOT_FOUND
|
use_inferred
|
bool
|
If True, return inferred value instead of the symbolic form
of |
False
|
Returns:
| Type | Description |
|---|---|
Any
|
Value of symbolic attribute specified by path if found, otherwise the |
Any
|
default value if it's specified. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
Source code in pygx/symbolic/_base.py
sym_hasattr
abstractmethod
¶
sym_getattr
¶
Gets a symbolic attribute in its symbolic (unresolved) form.
Unlike plain attribute access or __getitem__, an Inferential
value (e.g. pg.Ref) is returned as-is instead of being resolved;
use sym_inferred for the resolved value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str | int
|
Key of symbolic attribute. |
required |
default
|
Any
|
Default value if attribute does not exist. If absent,
|
RAISE_IF_NOT_FOUND
|
Returns:
| Type | Description |
|---|---|
Any
|
Value of symbolic attribute if found, otherwise the default value |
Any
|
if it's specified. |
Raises:
| Type | Description |
|---|---|
AttributeError
|
If |
Source code in pygx/symbolic/_base.py
sym_inferrable
¶
Returns True if the attribute under key can be inferred.
Source code in pygx/symbolic/_base.py
sym_inferred
¶
Returns the inferred value of the attribute under key.
The public override point (there is no protected _sym_inferred hook):
a subclass overrides this directly and calls super().sym_inferred(...)
for the base infer-unwrap (read the attribute; if it is Inferential,
return infer(**kwargs)). The default / RAISE_IF_NOT_FOUND
fallback — return default instead of propagating an inference error —
is applied here, so a subclass override that defers to super() for the
not-found path inherits it.
Source code in pygx/symbolic/_base.py
sym_keys
abstractmethod
¶
sym_values
abstractmethod
¶
sym_items
abstractmethod
¶
topo_setparent
¶
topo_setparent(parent: Optional[TopologyAware])
sym_contains
¶
Returns True if the object contains sub-nodes of given value or type.
Source code in pygx/symbolic/_base.py
topo_setpath
¶
topo_setpath(path: str | KeyPath | None) -> None
Sets the path of current node in its symbolic tree.
Source code in pygx/symbolic/_base.py
sym_rebind
¶
sym_rebind(
path_value_pairs: None | (Mapping[Any, Any] | Callable[..., Any]) = None,
*,
raise_on_no_change: bool = False,
notify_parents: bool = True,
skip_notification: bool | None = None,
**kwargs: Any
) -> Self
Mutates the sub-nodes of current object.
sym_rebind is the recommended way for mutating symbolic objects in
PyGX:
- It allows mutations not only on immediate child nodes, but on the entire sub-tree.
- It allows mutations by rules via passing a callable object as the
value for
path_value_pairs. - It batches the updates from multiple sub-nodes, which triggers the
on_sym_changeoron_sym_boundevent once for recomputing the parent object's internal states. - It respects the "sealed" flag of the object or the
pg.as_sealedcontext manager to trigger permission error.
Example:
#
# Rebind on pg.Object subclasses.
#
class A(pg.Object):
x: pg.Dict[str, int] = pg.Dict(y=0)
z: int = 1
a = A()
# Rebind using path-value pairs.
a.sym_rebind({
'x.y': 1,
'z': 0
})
# Rebind using **kwargs.
a.sym_rebind(x={'y': 1}, z=1)
# Rebind using rebinders.
# Rebind based on path.
a.sym_rebind(lambda k, v: 2 if k == 'x.y' else v)
# Rebind based on key.
a.sym_rebind(lambda k, v: 3 if k and k.key == 'y' else v)
# Rebind based on value.
a.sym_rebind(lambda k, v: 0 if v == 3 else v)
# Rebind based on value and parent.
a.sym_rebind(lambda k, v, p: (4 if isinstance(p, A) and isinstance(v, int)
else v))
#
# Rebind on pg.Dict.
#
d = pg.Dict(
value_spec=pg.typing.Dict([
('a', pg.typing.Dict([
('b', pg.typing.Int(default=0)),
])),
('c', pg.typing.Float(default=0.0)),
])
)
# Rebind using **kwargs.
d.sym_rebind(a={'b': 1}, c=1.0)
# Rebind using key path to value dict.
d.sym_rebind({
'a.b': 2,
'c': 2.0
})
# **kwargs combine with a dict (or rebinder) argument: the keyword
# arguments are merged on top of the resolved updates.
d.sym_rebind({'a.b': 3}, c=3.0)
# Rebind with rebinder by key (on subtree). The rebinder also sees
# the root, whose path is empty — guard with `k`.
d.sym_rebind(lambda k, v: 1 if k and k.key == 'b' else v)
# Rebind with rebinder by value (on subtree).
d.sym_rebind(lambda k, v: 0 if isinstance(v, int) else v)
#
# Rebind on pg.List.
#
l = pg.List(
[{'a': 'foo', 'b': 0}],
value_spec=pg.typing.List(
pg.typing.Dict([
('a', pg.typing.Str()),
('b', pg.typing.Int()),
]),
max_size=10,
),
)
# Rebind using integer as list index: update semantics on list[0].
l.sym_rebind({
0: {
'a': 'bar',
'b': 1
}
})
# Rebind: trigger append semantics when index is larger than list length.
l.sym_rebind({
999: {
'a': 'fun',
'b': 2
}
})
# Rebind using key path.
l.sym_rebind({
'[0].a': 'bar2',
'[1].b': 3
})
# Rebind using function (rebinder).
# Change all integers to 0 in sub-tree.
l.sym_rebind(lambda k, v: v if not isinstance(v, int) else 0)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path_value_pairs
|
None | (Mapping[Any, Any] | Callable[..., Any])
|
A dictionary of key/or key path to new field value, or a function that generate updates based on the key path, value and parent of each node under current object. We use terminology 'rebinder' for this type of functions. The signature of a rebinder is: |
None
|
raise_on_no_change
|
bool
|
If True, raises |
False
|
notify_parents
|
bool
|
If True (default), parents will be notified upon change.
Otherwise only the current object and the impacted children will
be notified. A most common use case for setting this flag to False
is when users want to rebind a child within the parent |
True
|
skip_notification
|
bool | None
|
If True, there will be no |
None
|
**kwargs
|
Any
|
For |
{}
|
Returns:
| Type | Description |
|---|---|
Self
|
Self. |
Raises:
| Type | Description |
|---|---|
WritePermissionError
|
If object is sealed. |
KeyError
|
If update location specified by key or key path is not aligned with the schema of the object tree. |
TypeError
|
If updated field value type does not conform to field spec. |
ValueError
|
If updated field value is not acceptable according to field
spec, or nothing is updated and |
Note
sym_rebind works in both topo modes. On a topo=False
(the default) object it descends for writes exactly as reads do:
plain field names, nested path keys ('a.b', 'x[0]') and
rebinder callables are all accepted, validated, and applied through
the same write path as attribute assignment. Only the upward
half differs: with no parent link, the batched on_sym_change
fires on the object owning each changed field and stops — there
are no ancestors to notify.
Source code in pygx/symbolic/_base.py
1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 | |
sym_jsonify
abstractmethod
¶
sym_jsonify(*, exclude_defaults: bool = False, **kwargs) -> WireValue
Converts representation of current object to a plain Python object.
sym_ne
¶
sym_eq
¶
sym_gt
¶
sym_lt
¶
sym_hash
abstractmethod
¶
sym_setorigin
¶
sym_setorigin(
source: Any,
tag: str,
stacktrace: bool | None = None,
stacklimit: int | None = None,
stacktop: int = -1,
)
Sets the symbolic origin of current object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Any
|
Source value for current object. |
required |
tag
|
str
|
A descriptive tag of the origin. Built-in tags are:
|
required |
stacktrace
|
bool | None
|
If True, enable stack trace for the origin. If None, enable
stack trace if a |
None
|
stacklimit
|
int | None
|
An optional integer to limit the stack depth. If None, it's
determined by the value passed to |
None
|
stacktop
|
int
|
A negative or zero-value integer indicating the stack top among
the stack frames that we want to present to user, by default it's
1-level up from the stack within current |
-1
|
Example:
def foo():
return bar()
def bar():
s = MyObject()
t = s.build()
t.sym_setorigin(s, 'builder',
stacktrace=True, stacklimit=5, stacktop=-1)
This example sets the origin of t using s as its source with tag
'builder'. We also record the callstack where the sym_setorigin is
called, so users can call t.sym_origin.stacktrace to get the call stack
later. The stacktop -1 indicates that we do not need the stack frame
within sym_setorigin, so users will see the stack top within the
function bar. We also set the max number of stack frames to display to 5,
not including the stack frame inside sym_setorigin.
Source code in pygx/symbolic/_base.py
sym_clone
¶
sym_clone(
deep: bool = False,
memo: Any | None = None,
override: Mapping[Any, Any] | None = None,
) -> Self
Clones current object symbolically (the formalized public entry).
Layers override-rebind + origin-tracking on top of the per-subclass
_sym_clone core, so copy protocols (__copy__ / __deepcopy__),
pg.clone, and child walks compose those exactly once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
deep
|
bool
|
If True, perform deep copy (equivalent to copy.deepcopy). Otherwise shallow copy (equivalent to copy.copy). |
False
|
memo
|
Any | None
|
Memo object for deep clone. |
None
|
override
|
Mapping[Any, Any] | None
|
An optional dict of key path to new values to override cloned
value. Accepted in both |
None
|
Returns:
| Type | Description |
|---|---|
Self
|
A copy of self. |
Source code in pygx/symbolic/_base.py
2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 | |
to_json
¶
to_json(**kwargs) -> WireValue
Serializes current object into a plain Python value.
Equivalent to pg.to_json(self, **kwargs); the conversion itself
is implemented by sym_jsonify.
Source code in pygx/symbolic/_base.py
to_json_str
¶
load
classmethod
¶
Loads an instance of this type using the global load handler.
Source code in pygx/symbolic/_base.py
save
¶
Saves current object using the global save handler.
inspect
¶
inspect(
path_regex: str | None = None,
where: None | (Callable[[Any], bool] | Callable[[Any, Any], bool]) = None,
custom_selector: None | (
Callable[[KeyPath, Any], bool] | Callable[[KeyPath, Any, Any], bool]
) = None,
file: IO[str] = stdout,
**kwargs: Any
) -> None
Inspects current object by printing out selected values.
Example:
class A(pg.Object):
x: int = 0
y: int
value = pg.Dict(
a1=A(x=0, y=0),
a2=[A(x=1, y=1), A(x=1, y=2)],
a3=pg.Dict(
p=A(x=2, y=1),
q=A(x=2, y=2),
),
)
# Inspect without constraint, which is equivalent to
# `print(value.format(exclude_defaults=True))`.
value.inspect(exclude_defaults=True)
# Inspect by path regex.
# Shall print:
# {'a3.p': A(x=2, y=1)}
value.inspect(r'.*p', compact=True)
# Inspect by value.
# Shall print:
# {
# 'a2[1].y': 2,
# 'a3.p.x': 2,
# 'a3.q.x': 2,
# 'a3.q.y': 2
# }
value.inspect(where=lambda v: v == 2)
# Inspect by path, value and parent.
# Shall print:
# {
# 'a2[1].y': 2
# }
value.inspect(
r'.*y', where=lambda v, p: v > 1 and isinstance(p, A) and p.x == 1)
# Inspect by custom_selector.
# Shall print:
# {
# 'a2[0].x': 1,
# 'a2[0].y': 1,
# 'a2[1].x': 1,
# 'a3.p.x': 2,
# 'a3.q.x': 2,
# 'a3.q.y': 2
# }
value.inspect(
custom_selector=lambda k, v, p: (
len(k) == 3 and isinstance(p, A) and p.x == v))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path_regex
|
str | None
|
Optional regex expression to constrain path. |
None
|
where
|
None | (Callable[[Any], bool] | Callable[[Any, Any], bool])
|
Optional callable to constrain value and parent when path matches
|
None
|
custom_selector
|
None | (Callable[[KeyPath, Any], bool] | Callable[[KeyPath, Any, Any], bool])
|
Optional callable object as custom selector. When
|
None
|
file
|
IO[str]
|
Output file stream. This can be any object with a |
stdout
|
**kwargs
|
Any
|
Wildcard keyword arguments to pass to |
{}
|
Source code in pygx/symbolic/_base.py
2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 | |
on_sym_change
abstractmethod
¶
on_sym_change(field_updates: dict[KeyPath, FieldUpdate])
Event that is triggered when field values in the subtree are updated.
This event will be called
* On a per-field basis when the object is modified via attribute.
* In batch when multiple fields are modified via sym_rebind.
When a field in an object tree is updated, all ancestors' on_sym_change event
will be triggered in order, from the nearest one to furthest one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
field_updates
|
dict[KeyPath, FieldUpdate]
|
Updates made to the subtree. Key path is relative to current object. |
required |
Source code in pygx/symbolic/_base.py
TopologyAware
¶
Interface for values that carry a position in a symbolic tree.
A TopologyAware value knows where it lives: its holder
(topo_parent) and its location under the tree root (topo_path),
plus the corresponding setters used by containers during adoption.
Every pygx.Symbolic value implements this
interface — though on a flat (topo=False) value the topo_*
surface raises TopoNotEnabled — and so
do Inferential placeholders, whose
meaning depends on where they sit.
topo_parent
abstractmethod
property
¶
topo_parent: Optional[TopologyAware]
Returns the parent of this object.
topo_path
abstractmethod
property
¶
topo_path: KeyPath
Returns the path of this object under its topology.
topo_setparent
abstractmethod
¶
topo_setparent(parent: Optional[TopologyAware]) -> None
TopoNotEnabled
¶
Bases: Exception
Raised when a tree-dependent operation is used on a topo=False object.
A pg.Object subclass declared with topo=False is still a symbolic
object — it keeps its init args, validates, compares by value, serializes,
and rebinds. What it lacks is a parent link, so it has no position in a
tree. The tree-position surface raises this error: topo_path,
topo_parent, topo_root, topo_ancestor, topo_field,
topo_setparent / topo_setpath, and contextual resolution (which
resolves by walking upward). Declare the class with topo=True to use
those features (the default is topo=False — a flat, validated
dataclass; #524).
sym_rebind is NOT in that list: it works in either mode and descends to
any depth. Only the upward half differs — under topo=False the change
notifies the rebound object and stops, with nothing to climb to.
Subclasses Exception directly (not AttributeError) so contextual
resolution's error handling cannot silently swallow it.
TraverseAction
¶
Bases: Enum
Enum for the next action after a symbolic node is visited.
See also: pygx.traverse.
ENTER
class-attribute
instance-attribute
¶
Enters the sub-tree of the visited node, if it has one. Meaningful
only from a preorder visitor; from a postorder visitor (which runs after
the sub-tree) it has the same effect as CONTINUE.
CONTINUE
class-attribute
instance-attribute
¶
Continues to the next node without entering the sub-tree.
WritePermissionError
¶
Bases: Exception
Raised when write access to object fields is not allowed.
ClassWrapper
¶
ClassWrapper(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: Object
Base class for symbolic class wrapper.
Please see pygx.wrap for details.
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
Compound
¶
Bases: Object
Base class for compound objects.
Unlike most pg.Object subclasses (which default to topo=False),
compound classes are topo=True: the decomposed object follows the
compound's parent chain, so contextual (inferred) values resolve through
the tree the compound lives in.
Source code in pygx/symbolic/_compounding.py
decomposed
abstractmethod
property
¶
Returns the underlying object created by the factory function.
Dict
¶
Dict(
dict_obj: (
None | Iterable[tuple[str | int, Any]] | dict[str | int, Any]
) = None,
*,
value_spec: Dict | None = None,
wrap: bool = True,
assign: str | None = None,
onchange_callback: (
None | Callable[[dict[KeyPath, FieldUpdate]], None]
) = None,
allow_partial: bool = False,
accessor_writable: bool = True,
sealed: bool = False,
pass_through: bool = False,
root_path: KeyPath | None = None,
as_object_attributes_container: bool = False,
under_topo: bool = True,
**kwargs: Any
)
Bases: dict[K, V], Symbolic, CustomTyping
Symbolic dict.
pg.Dict implements a dict type whose instances are symbolically
programmable. It is a subclass of the built-in Python dict and of
pygx.Symbolic.
pg.Dict provides the following features:
- It is a symbolically programmable dict with string or int keys.
- It enables attribute access on string keys.
- It supports symbolic validation and value completion based on a
pg.typing.Dictvalue spec. - It provides events (
onchange_callback) to handle sub-node changes.
pg.Dict can be used as a regular dict:
or:
Besides regular item access using [], it allows attribute access
to its string keys:
pg.Dict supports symbolic validation when the value_spec argument
is provided:
d = pg.Dict(
x=1,
y=2,
value_spec=pg.typing.Dict([
('x', pg.typing.Int(min_value=1)),
('y', pg.typing.Int(min_value=1)),
(pg.typing.StrKey('foo.*'), pg.typing.Str()),
]),
)
# Okay: all keys started with 'foo' are acceptable and are strings.
d.foo1 = 'abc'
# Raises KeyError: 'bar' is not an acceptable key per the value spec.
d.bar = 'abc'
Users can mutate the values contained in it:
d = pg.Dict(x=pg.Dict(y=1), p=pg.List([0]))
d.sym_rebind({
'x.y': 2,
'p[0]': 1
})
assert d.x.y == 2
assert d.p[0] == 1
It also allows the users to subscribe to subtree updates:
def on_change(updates):
print(updates)
d = pg.Dict(x=1, onchange_callback=on_change)
# `on_change` will be triggered on item insertion.
d['y'] = {'z': 1}
# `on_change` will be triggered on item removal.
del d.x
# `on_change` will also be triggered on subtree change.
d.sym_rebind({'y.z': 2})
Values stored in a pg.Dict become part of its symbolic tree: a
plain dict / list value is wrapped into a pg.Dict /
pg.List (see the wrap constructor argument), and a symbolic
value is held by reference and parented under this dict. A node can
hold only one position, so storing a value that is already held
elsewhere raises SharedHoldError —
resolve it explicitly with pg.maybe_ref
(share one node between the holders) or
pg.maybe_clone (store an independent
copy).
pg.Dict[K, V] can also be used as a field annotation on a
pg.Object subclass to declare a schema-validated symbolic dict
field:
class Config(pg.Object):
metrics: pg.Dict[str, float] = {}
c = Config(metrics={'acc': 0.9})
assert isinstance(c.metrics, pg.Dict)
assert c.metrics.acc == 0.9
Writes through [] / attributes / del can be disabled with
accessor_writable=False, in which case mutations must go through
sym_rebind; sealed=True seals the dict (recursing into its
symbolic children) against any modification.
Constructor.
Three parameters are internal to pygx.symbolic and not part of
the user-facing surface: pass_through,
as_object_attributes_container and under_topo (each marked
below).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dict_obj
|
None | Iterable[tuple[str | int, Any]] | dict[str | int, Any]
|
An optional dict or iterable of (key, value) pairs as
the initial value for this Dict. |
None
|
value_spec
|
Dict | None
|
Value spec that applies to this Dict. |
None
|
wrap
|
bool
|
Container-level default for whether nested raw
|
True
|
assign
|
str | None
|
Container-level ownership policy for the values this dict
is given, applied when the field holding a value declares none:
|
None
|
onchange_callback
|
None | Callable[[dict[KeyPath, FieldUpdate]], None]
|
Callback when sub-tree has been modified. |
None
|
allow_partial
|
bool
|
Whether to allow unbound or partial fields. This takes effect only when value_spec is not None. |
False
|
accessor_writable
|
bool
|
Whether to allow modification of this Dict using accessors (operator[] / attributes). |
True
|
sealed
|
bool
|
Whether to seal this Dict after creation. |
False
|
pass_through
|
bool
|
Internal — when True (and |
False
|
root_path
|
KeyPath | None
|
KeyPath of this Dict in its object tree. |
None
|
as_object_attributes_container
|
bool
|
Internal — when True, child item
parents propagate to |
False
|
under_topo
|
bool
|
Internal — the inner |
True
|
**kwargs
|
Any
|
Key value pairs that will be inserted into the dict as
initial value, which provides a syntax sugar for usage as below:
|
{}
|
Source code in pygx/symbolic/_dict.py
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 | |
value_spec
property
¶
value_spec: Dict | None
Returns value spec of this dict, or None if it is schema-less.
partial
classmethod
¶
partial(
dict_obj: dict[str | int, Any] | None = None,
value_spec: Dict | None = None,
*,
onchange_callback: (
None | Callable[[dict[KeyPath, FieldUpdate]], None]
) = None,
**kwargs
) -> Self
Creates a partial Dict (a shortcut for allow_partial=True).
A partial dict may leave fields required by value_spec unbound
(their values read as pg.MISSING_VALUE; see sym_missing).
Source code in pygx/symbolic/_dict.py
from_json
classmethod
¶
from_json(
json_value: Any,
*,
value_spec: Dict | None = None,
allow_partial: bool = False,
root_path: KeyPath | None = None,
**kwargs: Any
) -> Self
Loads a symbolic Dict from a JSON value.
Nested values carrying a _type marker are deserialized into
their symbolic objects, as with pg.from_json.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
json_value
|
Any
|
Input JSON value, only JSON dict is acceptable. |
required |
value_spec
|
Dict | None
|
An optional value spec to apply. |
None
|
allow_partial
|
bool
|
Whether to allow members of the dict to be partial. |
False
|
root_path
|
KeyPath | None
|
KeyPath of loaded object in its object tree. |
None
|
**kwargs
|
Any
|
Allow passing through keyword arguments that are not applicable. |
{}
|
Returns:
| Type | Description |
|---|---|
Self
|
A symbolic dict (schemaless unless |
Self
|
whose values may be symbolic. For example: |
Self
|
```python |
Self
|
class Foo(pg.Object): f1: int = 0 |
Self
|
d = pg.Dict.from_json({ 'a': { 'type': Foo.__type_name_, 'f1': 1, }, 'b': 2, |
Self
|
}) |
Self
|
assert d.value_spec is None |
Self
|
assert isinstance(d.a, Foo) |
Self
|
assert d.a.f1 == 1 |
Self
|
Okay: the dict itself is schemaless.¶ |
Self
|
d.c = 1 |
Self
|
``` |
Source code in pygx/symbolic/_dict.py
use_value_spec
¶
use_value_spec(value_spec: Dict | None, allow_partial: bool = False) -> Self
Applies a pg.typing.Dict as the value spec for current dict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value_spec
|
Dict | None
|
A |
required |
allow_partial
|
bool
|
Whether allow partial dict based on the schema. This flag will override allow_partial flag in init for spec-less Dict. |
False
|
Returns:
| Type | Description |
|---|---|
Self
|
Self. |
Raises:
| Type | Description |
|---|---|
ValueError
|
validation failed due to value error. |
RuntimeError
|
Dict is already bound with a different spec. |
TypeError
|
type errors during validation. |
KeyError
|
key errors during validation. |
Source code in pygx/symbolic/_dict.py
sym_missing
¶
Returns unbound (missing) schema fields in this dict's subtree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
flatten
|
bool
|
If True (default), the result is keyed by the key paths
of the missing values relative to this dict (e.g. |
True
|
Returns:
| Type | Description |
|---|---|
dict[str | int, Any]
|
A dict of key (or key path) to |
Source code in pygx/symbolic/_dict.py
sym_nondefault
¶
Returns values in this dict's subtree that differ from defaults.
For a schema-less dict, all values are reported (there are no
defaults). With flatten=True (default) the result is keyed by
key paths relative to this dict; otherwise it mirrors the
hierarchy with nested dicts.
Source code in pygx/symbolic/_dict.py
sym_seal
¶
Seals or unseals current object (recursing into children).
Source code in pygx/symbolic/_dict.py
sym_attr_field
¶
sym_attr_field(key: str | int) -> Field | None
Returns the field definition for a symbolic attribute.
Source code in pygx/symbolic/_dict.py
sym_hasattr
¶
sym_keys
¶
Iterates the keys of symbolic attributes.
Source code in pygx/symbolic/_dict.py
sym_values
¶
Iterates the values of symbolic attributes.
Source code in pygx/symbolic/_dict.py
sym_items
¶
Iterates the (key, value) pairs of symbolic attributes.
Source code in pygx/symbolic/_dict.py
topo_setparent
¶
topo_setparent(parent: TopologyAware | None)
Sets the topological parent of this Dict.
When this Dict is the attributes container of a pg.Object, the
parent also propagates to its child values (pass-through), so
children report the owning object as their parent.
Source code in pygx/symbolic/_dict.py
sym_hash
¶
Symbolic hashing.
Source code in pygx/symbolic/_dict.py
on_sym_change
¶
on_sym_change(field_updates: dict[KeyPath, FieldUpdate])
get
¶
Get item in this Dict.
Source code in pygx/symbolic/_dict.py
keys
¶
items
¶
Returns an iterator of (key, value) items in current dict.
Source code in pygx/symbolic/_dict.py
values
¶
Returns an iterator of values in current dict.
Source code in pygx/symbolic/_dict.py
copy
¶
Returns a copy via shallow symbolic clone.
Symbolic (tree-owned) children are cloned rather than shared;
primitives and topo=False objects (opaque, reference-held
leaves) are shared. See sym_clone.
Source code in pygx/symbolic/_dict.py
pop
¶
Pops a key from current dict and returns its value.
Popping a key declared as a const schema field resets its value to
the field's default (the key remains present); otherwise the key
is removed. When the key does not exist — or its stored value is
pg.MISSING_VALUE — default is returned; a KeyError is
raised for a nonexistent key when no default is given.
Source code in pygx/symbolic/_dict.py
popitem
¶
Pops and returns the last inserted (key, value) pair.
Raises:
| Type | Description |
|---|---|
ValueError
|
If this dict is bound to a value spec. |
WritePermissionError
|
If this dict is sealed. |
Source code in pygx/symbolic/_dict.py
clear
¶
Removes all the keys in current dict.
For a spec-bound dict the value spec is re-applied afterwards, so const schema fields are restored to their default values; clearing raises if the spec has required (default-less) fields and this dict does not allow partial values.
Source code in pygx/symbolic/_dict.py
setdefault
¶
Sets default as the value for key if not present.
A key whose stored value is pg.MISSING_VALUE counts as absent.
Returns the stored value afterwards, as dict.setdefault does.
Source code in pygx/symbolic/_dict.py
update
¶
update(
other: None | dict[str | int, Any] | Iterable[tuple[str | int, Any]] = None,
**kwargs
) -> None
Updates this dict with the same semantics as dict.update.
Accepts a mapping or an iterable of (key, value) pairs, plus
keyword arguments. Assigning pg.MISSING_VALUE to a key deletes
it. Updates are validated against the value spec, but change
notification is skipped (onchange_callback will not fire).
Source code in pygx/symbolic/_dict.py
sym_jsonify
¶
sym_jsonify(
*,
exclude_frozen: bool = True,
exclude_defaults: bool = False,
exclude_keys: Sequence[str | int] | None = None,
use_inferred: bool = False,
exclude_none: bool = False,
omit_symbolic_marker: bool = True,
**kwargs
) -> WireValue
Converts this dict to a JSON-compatible plain Python dict.
Symbolic values are recursively converted (symbolic objects emit
their _type marker, as with pg.to_json). The flags select
which entries are emitted: exclude_frozen=True (default) drops
frozen schema fields; exclude_defaults=True drops entries equal
to their schema default; exclude_none=True drops None-valued
entries — uniformly for pg.Object fields and pg.Dict entries
(list items are never dropped); exclude_keys drops the named
keys. The flags propagate to nested values.
Source code in pygx/symbolic/_dict.py
1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 | |
custom_apply
¶
custom_apply(
path: KeyPath,
value_spec: ValueSpec,
allow_partial: bool,
child_transform: None | Callable[[KeyPath, Field, Any], Any] = None,
) -> tuple[bool, Dict]
Implement pg.typing.CustomTyping interface.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
KeyPath
|
KeyPath of current object. |
required |
value_spec
|
ValueSpec
|
Origin value spec of the field. |
required |
allow_partial
|
bool
|
Whether allow partial object to be created. |
required |
child_transform
|
None | Callable[[KeyPath, Field, Any], Any]
|
Function to transform child node values in dict_obj into their final values. Transform function is called on leaf nodes first, then on their containers, recursively. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[bool, Dict]
|
A tuple (proceed_with_standard_apply, transformed value) |
Source code in pygx/symbolic/_dict.py
format
¶
format(
compact: bool = False,
verbose: bool = True,
root_indent: int = 0,
*,
python_format: bool = False,
exclude_frozen: bool = True,
exclude_defaults: bool = False,
exclude_missing: bool = False,
include_keys: set[str | int] | None = None,
exclude_keys: set[str | int] | None = None,
use_inferred: bool = False,
cls_name: str | None = None,
bracket_type: BracketType = CURLY,
key_as_attribute: bool = False,
extra_blankline_for_field_docstr: bool = False,
extra_fields: list[tuple[Field | None, str | int, Any]] | None = None,
**kwargs
) -> str
Renders this Dict as a string.
compact=True renders on one line; verbose=True (with
compact=False) adds schema field descriptions as comments;
python_format=True renders keys as 'key': value (evaluable
Python) instead of key = value. The exclude_* /
include_keys flags select which entries render.
Four of the keyword flags are a rendering API for in-repo callers
rather than everyday Dict knobs: cls_name, key_as_attribute
and bracket_type re-skin the output as Foo(x=1) / [...]
instead of {x=1} (used by pg.diff's renderer and by
Object.format), and extra_fields injects rendered entries
not present in the underlying Dict. Injected entries take the same
per-field codepath as normal ones (description, indentation,
exclude_missing).
Source code in pygx/symbolic/_dict.py
2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 | |
Diff
¶
Diff(
left: Any = _DIFF_MISSING,
right: Any = _DIFF_MISSING,
children: dict[str, Diff] | None = None,
**kwargs: Any
)
Bases: PureSymbolic, Object, Extension
A value diff between two objects: a 'left' object and a 'right' object.
If one of them is missing, it may be represented by pg.Diff.MISSING.
For example:
>>> pg.Diff(3.14, 1.618)
Diff(left=3.14, right=1.618)
>>> pg.Diff('hello world', pg.Diff.MISSING)
Diff(left='hello world', right=MISSING)
Source code in pygx/symbolic/_diff.py
sym_eq
¶
Symbolic equality.
A Diff equals another Diff with equal left / right /
children; a no-diff Diff (see __bool__) additionally
equals the value both sides share, e.g.
pg.eq(pg.Diff(1, 1), 1) is True.
Source code in pygx/symbolic/_diff.py
format
¶
Override format to conditionally print the shared value or the diff.
Source code in pygx/symbolic/_diff.py
Functor
¶
Functor(
*args: Any,
root_path: KeyPath | None = None,
override_args: bool = False,
ignore_extra_args: bool = False,
**kwargs: Any
)
Bases: Object, Functor
Symbolic functions (Functors).
A symbolic function is a symbolic class with a __call__ method, whose
arguments can be bound partially, incrementally bound by attribute
assignment, or provided at call time.
Another useful trait is that a symbolic function is serializable, when its definition is imported by the target program and its arguments are also serializable. Therefore, it is very handy to move a symbolic function around in distributed scenarios.
A functor is a value, not a tree node: like other pg.Object subclasses
it defaults to topo=False, so bound arguments are held by reference
without parent/path bookkeeping. Declare topo=True on a subclass when
the functor itself must join a symbolic tree (e.g. for contextual
resolution or origin tracking through the parent chain).
Symbolic functions can be created from regular functions via
pygx.functor:
# Create a functor class using the @pg.functor decorator.
@pg.functor([
('a', pg.typing.Int(), 'Argument a'),
# No field specification for 'b', which will be treated as any type.
])
def add(a, b=1, *args, **kwargs):
return a + b + sum(args) + sum(kwargs.values())
add(1)() # returns 2: prebind a=1, invoke with b=1 (default).
add(a=1)() # returns 2: same as above.
add()(1) # returns 2: bind a=1 at call time, b=1 (default).
add(b=2)(1) # returns 3: prebind b=2, invoke with a=1.
add(b=2)() # wrong: `a` is not provided.
add(1)(2) # wrong: 'a' is provided multiple times.
add(1)(2, override_args=True) # ok: override `a` value with 2.
add()(1, 2, 3, 4) # returns 10: a=1, b=2, *args=[3, 4]
add(c=4)(1, 2, 3) # returns 10: a=1, b=2, *args=[3], **kwargs={'c': 4}
Or created by subclassing pg.Functor:
class Sum(pg.Functor):
a: int
b: int = 1
def _call(self) -> int:
return self.a + self.b
assert Sum(1)() == 2 # Prebind a=1, invoke with b=1 (default).
assert Sum()(1, 2) == 3 # Bind a=1, b=2 at call time.
Usage on subclassed functors is the same as functors created from functions.
Constructor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
Any
|
prebound positional arguments. |
()
|
root_path
|
KeyPath | None
|
The symbolic path for current object. |
None
|
override_args
|
bool
|
If True, allows arguments provided during |
False
|
ignore_extra_args
|
bool
|
If True, unsupported arguments can be passed in
during |
False
|
**kwargs
|
Any
|
prebound keyword arguments. |
{}
|
Raises:
| Type | Description |
|---|---|
TypeError
|
constructor got unexpected or duplicate arguments. |
Source code in pygx/symbolic/_functor.py
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |
non_default_args
property
¶
Returns the names of bound arguments whose values are not the default.
default_args
property
¶
Returns the names of bound arguments whose values are the default.
is_fully_bound
property
¶
Returns True if all arguments of the functor are bound.
is_subclassed_functor
classmethod
¶
partial
classmethod
¶
sym_inferred
¶
Overrides method to allow member overrides during call.
Source code in pygx/symbolic/_functor.py
on_sym_change
¶
on_sym_change(field_updates: dict[KeyPath, FieldUpdate])
Custom handling field change to update bound args.
Source code in pygx/symbolic/_functor.py
sym_missing
¶
Returns missing values for the functor.
Semantically, unbound arguments are not missing; only bound arguments that are themselves partial count. As a result, a functor is partial only when any of its bound arguments is partial.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of missing key (or path) to missing value. |
Source code in pygx/symbolic/_functor.py
InferredValue
¶
InferredValue(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: Object, Inferential
Base class for inferred values.
An inferred value is a placeholder assignable to any symbolic field:
attribute reads of that field return the result of its infer method
(computed at read time) while the placeholder itself remains the stored
value (returned by sym_getattr). See
ValueFromParentChain for the
common case of resolving a value from the object's parent chain.
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
ValueFromParentChain
¶
ValueFromParentChain(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: InferredValue
A value that could be inferred from the parent chain.
Inference walks the topo_parent chain, so the class holding the
inferential field must be declared with topo=True (contextual
attribute reads on a flat class raise TopoNotEnabled).
For example:
class A(pg.Object, topo=True):
x: int
y: int = pg.symbolic.ValueFromParentChain()
# Not okay (raises): `x` is not inferential and is not specified.
A()
# Okay: both `x` and `y` are specified.
A(x=1, y=2)
# Okay: `y` is inferential, hence optional.
a = A(x=1)
# Raises AttributeError: `y` is neither specified during __init__
# nor provided from the context.
a.y
d = pg.Dict(y=2, z=pg.Dict(a=a))
# `a.y` now resolves to `d.y` since `d` is in its symbolic parent chain,
# aka. context.
assert a.y == 2
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
inference_key
property
¶
Returns the key for attribute inference from parents.
An inferential value resolves BY ITS OWN KEY, so it must be adopted
into a keyed slot. A ROOT path means it was never adopted — the
assign= policies that hold by reference are exempted for exactly
this reason (#686), so reaching here unkeyed means some other path
placed it without adoption. Name that, rather than letting
KeyPath.key raise a bare KeyError from three frames down.
value_from
¶
value_from(parent: Symbolic, **kwargs) -> Any
Looks up this value's inference_key on an ancestor.
Returns pg.typing.MISSING_VALUE when parent cannot provide the
value (including the immediate parent, whose key points back at this
inferential value itself).
Source code in pygx/symbolic/_inferred.py
Insertion
dataclass
¶
List
¶
List(
items: Iterable[Any] | None = None,
*,
value_spec: List | None = None,
wrap: bool = True,
assign: str | None = None,
onchange_callback: (
None | Callable[[dict[KeyPath, FieldUpdate]], None]
) = None,
allow_partial: bool = False,
accessor_writable: bool = True,
sealed: bool = False,
pass_through: bool = False,
root_path: KeyPath | None = None
)
Bases: list[V], Symbolic, CustomTyping
Symbolic list.
pg.List implements a list type whose instances are symbolically
programmable. It is a subclass of the built-in Python list and of
pygx.Symbolic.
pg.List can be used as a regular list:
It also supports symbolic validation through the value_spec argument:
l = pg.List([1, 2, 3], value_spec=pg.typing.List(
pg.typing.Int(min_value=1),
max_size=10
))
# Raises ValueError: 0 is not in the acceptable range.
l.append(0)
And can be symbolically manipulated:
l = pg.List([{'foo': 1}])
l.sym_rebind({
'[0].foo': 2
})
assert l[0].foo == 2
assert pg.query(l, where=lambda x: isinstance(x, int)) == {
'[0].foo': 2
}
Users can also subscribe to changes of its sub-nodes:
def on_change(updates):
print(updates)
l = pg.List([{'foo': 1}], onchange_callback=on_change)
# `on_change` will be triggered on item insertion.
l.append({'bar': 2})
# `on_change` will be triggered on item removal.
l.pop(0)
# `on_change` will also be triggered on subtree change.
l.sym_rebind({'[0].bar': 3})
Elements stored in a pg.List become part of its symbolic tree: a
plain dict / list element is wrapped into a pg.Dict /
pg.List (see the wrap constructor argument), and a symbolic
element is held by reference and parented under this list. A node can
hold only one position, so storing a value that is already held
elsewhere raises SharedHoldError —
resolve it explicitly with pg.maybe_ref
(share one node between the holders) or
pg.maybe_clone (store an independent
copy).
pg.List[E] can also be used as a field annotation on a
pg.Object subclass to declare a schema-validated symbolic list
field:
class Batch(pg.Object):
sizes: pg.List[int] = []
b = Batch(sizes=[1, 2])
assert isinstance(b.sizes, pg.List)
Writes through [] item assignment and del can be disabled
with accessor_writable=False, in which case such mutations must
go through sym_rebind; sealed=True seals the list (recursing
into its symbolic children) against any modification, including the
mutating list methods (append, insert, extend, ...).
Constructor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
Iterable[Any] | None
|
An optional iterable as the initial value for this list. |
None
|
value_spec
|
List | None
|
Value spec that applies to this List. |
None
|
wrap
|
bool
|
Container-level default for whether nested raw
|
True
|
assign
|
str | None
|
Container-level ownership policy for the elements this
list is given, applied when the element spec declares none:
|
None
|
onchange_callback
|
None | Callable[[dict[KeyPath, FieldUpdate]], None]
|
Callback when sub-tree has been modified. |
None
|
allow_partial
|
bool
|
Whether to allow unbound or partial fields. This takes effect only when value_spec is not None. |
False
|
accessor_writable
|
bool
|
Whether to allow modification of this List using accessors (operator[]). |
True
|
sealed
|
bool
|
Whether to seal this List after creation. |
False
|
pass_through
|
bool
|
When True (and |
False
|
root_path
|
KeyPath | None
|
KeyPath of this List in its object tree. |
None
|
Source code in pygx/symbolic/_list.py
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 | |
partial
classmethod
¶
partial(
items: Iterable[Any] | None = None,
*,
value_spec: List | None = None,
onchange_callback: (
None | Callable[[dict[KeyPath, FieldUpdate]], None]
) = None,
**kwargs
) -> Self
Creates a partial List (a shortcut for allow_partial=True).
A partial list may hold partial elements (e.g. objects with
unbound required fields; see sym_missing). Size constraints
of value_spec still apply.
Source code in pygx/symbolic/_list.py
from_json
classmethod
¶
from_json(
json_value: Any,
*,
value_spec: List | None = None,
allow_partial: bool = False,
root_path: KeyPath | None = None,
**kwargs: Any
) -> Self
Loads a symbolic List from a JSON value.
Elements carrying a _type marker are deserialized into their
symbolic objects, as with pg.from_json.
Example:
class Foo(pg.Object):
f1: int = 0
l = pg.List.from_json([
{
'_type': Foo.__type_name__,
'f1': 1,
},
1,
])
assert l.value_spec is None
assert isinstance(l[0], Foo)
assert l[0].f1 == 1
# Okay: the list itself is schemaless.
l.append('abc')
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
json_value
|
Any
|
Input JSON value, only JSON list is acceptable. |
required |
value_spec
|
List | None
|
An optional |
None
|
allow_partial
|
bool
|
Whether to allow elements of the list to be partial. |
False
|
root_path
|
KeyPath | None
|
KeyPath of loaded object in its object tree. |
None
|
**kwargs
|
Any
|
Allow passing through keyword arguments that are not applicable. |
{}
|
Returns:
| Type | Description |
|---|---|
Self
|
A schema-less symbolic list, but its items maybe symbolic. |
Source code in pygx/symbolic/_list.py
use_value_spec
¶
use_value_spec(value_spec: List | None, allow_partial: bool = False) -> Self
Applies a pg.typing.List as the value spec for current list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value_spec
|
List | None
|
A |
required |
allow_partial
|
bool
|
Whether allow partial list based on the schema. This flag will override allow_partial flag in init for spec-less List. |
False
|
Returns:
| Type | Description |
|---|---|
Self
|
Self. |
Raises:
| Type | Description |
|---|---|
ValueError
|
schema validation failed due to value error. |
RuntimeError
|
List is already bound with a different value_spec. |
TypeError
|
type errors during validation. |
KeyError
|
key errors during validation. |
Source code in pygx/symbolic/_list.py
sym_attr_field
¶
sym_attr_field(key: str | int) -> Field | None
Returns the field definition for a symbolic attribute.
sym_hasattr
¶
sym_keys
¶
sym_values
¶
sym_items
¶
sym_hash
¶
Symbolically hashing.
Source code in pygx/symbolic/_list.py
sym_missing
¶
Returns unbound (missing) fields from symbolic elements.
With flatten=True (default) the result is keyed by key paths
relative to this list (e.g. '[0].y'); otherwise it mirrors
the hierarchy, keyed by element index.
Source code in pygx/symbolic/_list.py
sym_nondefault
¶
Returns values in this list's subtree that differ from defaults.
List elements themselves have no defaults, so non-symbolic
elements are always reported; symbolic elements contribute their
own non-default fields. With flatten=True (default) the result
is keyed by key paths relative to this list; otherwise it mirrors
the hierarchy, keyed by element index.
Source code in pygx/symbolic/_list.py
sym_seal
¶
Seal or unseal current object (recursing into children).
Source code in pygx/symbolic/_list.py
on_sym_change
¶
on_sym_change(field_updates: dict[KeyPath, FieldUpdate])
Handles subtree change events.
Compacts the list (removing items that became MISSING_VALUE),
refreshes shifted children's paths, then invokes the
onchange_callback if one is set.
Source code in pygx/symbolic/_list.py
copy
¶
copy() -> List
Returns a shallow copy of this list.
Symbolic elements owned by this list are cloned into the copy rather than shared (two containers cannot hold the same tree-owned element); primitives and reference-held values are shared.
Source code in pygx/symbolic/_list.py
append
¶
Appends an item, validated against the value spec if present.
Raises:
| Type | Description |
|---|---|
WritePermissionError
|
If this list is sealed. |
ValueError
|
If the list has reached its max size, or the value is not acceptable per the value spec. |
Source code in pygx/symbolic/_list.py
insert
¶
Inserts an item at a given position.
The item is validated against the value spec if present.
Raises:
| Type | Description |
|---|---|
WritePermissionError
|
If this list is sealed. |
ValueError
|
If the list has reached its max size, or the value is not acceptable per the value spec. |
Source code in pygx/symbolic/_list.py
pop
¶
Pops the item at index (last by default) and returns it.
Raises:
| Type | Description |
|---|---|
IndexError
|
If |
WritePermissionError
|
If this list is sealed. |
Source code in pygx/symbolic/_list.py
remove
¶
Removes the first occurrence of the value.
Like the other mutating list methods (pop, append, sort,
...), remove works under accessor_writable=False — only the
accessor spellings (l[i] = v / del l[i]) are gated
(#773 ruling 2).
Raises:
| Type | Description |
|---|---|
ValueError
|
If the value is not in the list, or removal would shrink the list below its value spec's min size. |
WritePermissionError
|
If this list is sealed. |
Source code in pygx/symbolic/_list.py
extend
¶
Extends the list by appending elements from other.
Elements already owned by another symbolic container are cloned
in — extending reads from other, it does not take its elements.
Raises:
| Type | Description |
|---|---|
WritePermissionError
|
If this list is sealed. |
ValueError
|
If the extension would exceed the list's max size, or an element is not acceptable per the value spec. |
Source code in pygx/symbolic/_list.py
clear
¶
Clears the list.
Raises:
| Type | Description |
|---|---|
WritePermissionError
|
If this list is sealed. |
ValueError
|
If the value spec declares a min size greater than zero. |
Source code in pygx/symbolic/_list.py
sort
¶
Sorts the items of the list in place.
reverse
¶
custom_apply
¶
custom_apply(
path: KeyPath,
value_spec: ValueSpec,
allow_partial: bool,
child_transform: None | Callable[[KeyPath, Field, Any], Any] = None,
) -> tuple[bool, List]
Implement pg.typing.CustomTyping interface.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
KeyPath
|
KeyPath of current object. |
required |
value_spec
|
ValueSpec
|
Origin value spec of the field. |
required |
allow_partial
|
bool
|
Whether allow partial object to be created. |
required |
child_transform
|
None | Callable[[KeyPath, Field, Any], Any]
|
Function to transform child node values in dict_obj into their final values. Transform function is called on leaf nodes first, then on their containers, recursively. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[bool, List]
|
A tuple (proceed_with_standard_apply, transformed value) |
Source code in pygx/symbolic/_list.py
sym_jsonify
¶
sym_jsonify(
use_inferred: bool = False, omit_symbolic_marker: bool = True, **kwargs
) -> WireValue
Converts current list to a list of plain Python objects.
Source code in pygx/symbolic/_list.py
format
¶
format(
compact: bool = False,
verbose: bool = True,
root_indent: int = 0,
*,
python_format: bool = False,
use_inferred: bool = False,
cls_name: str | None = None,
bracket_type: BracketType = SQUARE,
**kwargs
) -> str
Formats this List.
Source code in pygx/symbolic/_list.py
1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 | |
Object
¶
Object(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: Symbolic
Base class for symbolic user classes.
PyGX allows symbolic programming interfaces to be added to most Python classes in two ways:
- Developing a dataclass-like symbolic class by subclassing
pg.Object. - Developing a class as usual and decorating it with
pygx.symbolize. This also works with existing classes.
By directly subclassing pg.Object, programmers can create new symbolic
classes with the least effort. For example:
class Greeting(pg.Object):
# Each annotated class attribute defines a symbolic field for
# `__init__`, which is keyword-only.
name: str | None
time_of_day: str = pg.field(
value_spec=pg.typing.Enum(
['morning', 'afternoon', 'evening'], 'morning'
)
)
def __call__(self):
# Values for symbolic fields can be accessed
# as public data members of the symbolic object.
print(f'Good {self.time_of_day}, {self.name}')
# Create an object of Greeting and invoke it,
# which shall print 'Good morning, Bob'.
Greeting(name='Bob')()
Flat vs. symbolic-tree mode. By default (topo=False) a
pg.Object subclass is a flat, validated, dataclass-shaped value
type: fields are validated at construction and on every write,
attribute assignment (obj.x = v) and sym_rebind both work,
change events (on_sym_change) fire, and an object held in another
object's field is held by reference. A flat object has no tree
position — the topo_* APIs (topo_parent / topo_path /
topo_root / topo_setparent / ...) raise TopoNotEnabled, as
does resolving an Inferential whose infer() walks the tree (one
that does not — pg.Ref — resolves in either mode; see
Inferential.needs_topo). Declaring class
X(pg.Object, topo=True) opts instances into the bi-directional
symbolic tree: topo_parent / topo_path / topo_root become
available, a node can be held at only one tree position (a second hold
raises SharedHoldError; resolve with pg.maybe_ref /
pg.maybe_clone or the assign= policy), contextual values
resolve through the parent chain, and attribute writes default off
(attr_write=False — sym_rebind is the write surface).
Class keywords. Per-class behavior is configured via keyword arguments on the class statement. Each option is inherited by subclasses, which may overlay their own:
topo— the flat-vs-tree axis above (defaultFalse).attr_read—obj.xreads fieldx(defaultTrue; whenFalse, usesym_getattr/sym_init_args).attr_write—obj.x = vwrites fieldx; followsnot topounless explicitly set. Gates only the dotted surface —sym_rebindis sealed byfrozenalone.frozen— seal instances at construction; later writes raiseWritePermissionErrorunless underpg.as_sealed(False)(defaultFalse).eq—==/hash()delegate tosym_eq/sym_hash(value semantics);eq=Falsefalls back to identity (defaultTrue).order— synthesize</<=/>/>=over thecompare=Truefields, à la@dataclass(order=True)(defaultFalse).validate— run values through each field'sValueSpec.applyat construction and on every write;Falselands values verbatim (defaultTrue).strict— reject implicit coercions (e.g.int→float) across all the class's fields (defaultFalse).assign— how fields hold the values they are given:'adopt'/'clone'/'ref'. Undeclared (the default), holding atopo=Truenode that already has a tree position raisesSharedHoldError.extra— unknown-init-key posture:'forbid'(default —TypeError),'allow'(store extras as wildcard fields;extra=pg.field(...)also types them), or'ignore'(silently drop them).slots— store fields in fixed per-class slots instead of a per-instance dict; a memory optimization for closed schemas.init— PEP 681 static-typing hint; a runtime no-op.
Serialization keywords (serialization_key, additional_keys,
add_to_registry) configure the wire registry. The full
per-option contract is on help(pg.Object.__init_subclass__).
Symbolic fields can be inherited from the base symbolic class: the fields from the base class will be copied to the subclass in their declaration order, while the subclass can override the inherited fields with more restricted validation rules or different default values. For example:
class Foo(pg.Object):
x: int = pg.field(value_spec=pg.typing.Int(max_value=10))
y: float = pg.field(value_spec=pg.typing.Float(min_value=0))
class Bar(Foo):
x: int = pg.field(value_spec=pg.typing.Int(min_value=1, default=1))
z: str | None
# Printing Bar's schema will show that there are 3 fields defined,
# with `x`'s spec merged with the inherited constraints:
# x : pg.typing.Int(min_value=1, max_value=10, default=1)
# y : pg.typing.Float(min_value=0)
# z : pg.typing.Str().noneable()
print(Bar.__schema__)
Field defaults. A default of a recognized copyable shape — a
builtin container (list / dict / set / tuple), a
symbolic value, a dataclass instance, or a type extending
pg.CopyIfDefault — is deep-copied per instance, so mutating one
instance's value never leaks into another (z: list = [] is
safe). An arbitrary-type default is shared by reference across
instances; use pg.field(default_factory=...) when each instance
must own its value.
Overriding only a default on a subclass. When a subclass overrides an inherited field's default, always re-annotate the name so static type checkers stay in sync:
class Message(pg.Object):
text: str
sender: str = pg.MISSING_VALUE # required at construction time
class UserMessage(Message):
sender: str = 'User' # re-annotate — pyright sees this
A bare assignment (sender = 'User') works at runtime — pygx still
rewrites the default — but PEP 681 dataclass_transform synthesizes
__init__ from class-level annotations only, so pyright would still
see sender as required. pygx emits a UserWarning at class creation
when it detects this pattern; silence it via
pg.warn_on_field_override_no_annotation(False).
Runtime generics. A typing.Generic subclass may declare
TypeVar-typed fields. Subscripting it with concrete types mints a
cached, real subclass whose substituted fields validate at
construction, while the unparameterized class stays gradual:
import typing
T = typing.TypeVar('T')
class Box(pg.Object, typing.Generic[T]):
v: T | None = None
Box[int](v=5) # OK — a cached subclass named 'Box[int]'.
Box[int](v='x') # TypeError: Expect <class 'int'> but encountered ...
Box(v='x') # OK — the unparameterized class stays gradual.
Create an Object instance.
pg.Object synthesizes a keyword-only __init__. Subclasses
that need positional arguments must override __init__
explicitly and translate to keyword arguments before forwarding
to super().__init__(**kwargs).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allow_partial
|
bool
|
If True, the object can be partial. |
False
|
sealed
|
bool | None
|
If True, seal the object from future modification (unless under
a |
None
|
root_path
|
KeyPath | None
|
The symbolic path for current object. By default it's None, which indicates that newly constructed object does not have a parent. |
None
|
explicit_init
|
bool
|
Should set to |
False
|
**kwargs
|
Any
|
key/value arguments that align with the schema. All required keys in the schema must be specified, and values should be acceptable according to their value spec. |
{}
|
Raises:
| Type | Description |
|---|---|
TypeError
|
When a required field is missing, a value has an unacceptable type, or an unknown keyword argument is given. |
ValueError
|
When a value violates its value spec's constraints (e.g. out of range). |
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
sym_init_args
property
¶
Returns the symbolic attributes which are also the __init__ args.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A plain dict of the evaluated symbolic attributes, in schema order.
Every inferential value (e.g.
|
sym_partial
property
¶
Whether any field (here or in a child) is unbound.
True for an object constructed with partial /
allow_partial=True whose required fields are still held as
MISSING_VALUE, or whose symbolic children are themselves
partial — equivalent to bool(self.sym_missing(flatten=False)).
partial
classmethod
¶
Creates a partial object — required fields may be left unbound.
Equivalent to cls(allow_partial=True, **kwargs): a required
field not provided is held as MISSING_VALUE instead of
raising, sym_partial is True and sym_missing() lists the
unbound fields until they are filled (e.g. via sym_rebind).
Source code in pygx/symbolic/_object.py
from_json
classmethod
¶
from_json(
json_value: Any,
*,
allow_partial: bool = False,
root_path: KeyPath | None = None,
**kwargs: Any
) -> Self
Loads an instance of this class from a JSON dict.
Example:
class Foo(pg.Object):
f1: int
f2: dict
foo = Foo.from_json({
'f1': 1,
'f2': {'f21': True},
})
# Or via the generic entry point, dispatching on `_type`:
foo2 = pg.from_json({
'_type': '__main__.Foo',
'f1': 1,
'f2': {'f21': True},
})
assert foo == foo2
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
json_value
|
Any
|
Input JSON value; only a JSON dict is acceptable. |
required |
allow_partial
|
bool
|
Whether to allow the loaded object to be partial
(required fields may be left unbound as |
False
|
root_path
|
KeyPath | None
|
KeyPath of the loaded object in its object tree. |
None
|
**kwargs
|
Any
|
Additional deserialization kwargs, passed through when
decoding child values (e.g. |
{}
|
Returns:
| Type | Description |
|---|---|
Self
|
An instance of |
Source code in pygx/symbolic/_object.py
2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 | |
on_sym_post_init
¶
Event triggered at the end of __init__.
This is the construction-time entry point for the bind sequence: the
default implementation triggers on_sym_bound (and then on_sym_ready
once the object is concrete). An override that does not call
super().on_sym_post_init() therefore suppresses on_sym_bound and
on_sym_ready at construction — call super() unless that is
intended. For most needs, prefer overriding on_sym_ready (or
on_sym_bound) directly rather than this funnel.
Source code in pygx/symbolic/_object.py
on_sym_bound
¶
Event triggered on each (re)bind, even when the subtree is partial.
Fires at the end of __init__ and at most once per rebind (no matter how
many fields changed), regardless of whether every field is present. Use
this only for logic that must run even on a partial or pure-symbolic
object.
For the common case — (re)computing derived members from schema fields —
override on_sym_ready instead: it fires right after on_sym_bound but only
once the object is concrete, so all fields are guaranteed present.
When derived members are expensive to recompute, override on_sym_change
instead: it receives the exact field_updates (both per-field mutations
and batched rebinds, anywhere in the subtree) so you can refresh only
the members impacted by the change.
Source code in pygx/symbolic/_object.py
on_sym_ready
¶
Event triggered after on_sym_bound once the object is concrete.
Fires at the end of __init__ and after each rebind, but only when the
object is not abstract (not sym_abstract — neither partial nor
pure-symbolic). Unlike on_sym_bound, every schema field is guaranteed
present, making this the natural place to set derived members.
It is level-triggered: it fires on every (re)bind that leaves the object concrete, not only on the abstract -> concrete transition. For one-shot setup, guard with your own flag.
Source code in pygx/symbolic/_object.py
on_sym_preinit
classmethod
¶
Model-level before hook: transform raw init kwargs (#516).
Called on every fresh construct (including from_json) with the
raw field kwargs — before unknown-key/missing-required resolution —
and must return the mapping to construct from (mutating and
returning kwargs is fine). The reserved init args
(allow_partial / sealed / root_path / explicit_init)
are never included. Not called on clones (their values come
formalized from a validated source) nor on rebinds (per-field
transform is the write-path before-hook).
The default implementation returns kwargs unchanged.
Source code in pygx/symbolic/_object.py
on_sym_validate
¶
Model-level after validator: check cross-field invariants (#516).
Called with all fields validated and committed — raise to reject.
Unlike on_sym_ready (a lifecycle notification), a raise here is
part of validation:
- Construct: fires after fields are stored, before
on_sym_post_init/on_sym_bound/on_sym_ready, and only when the object is concrete (theon_sym_readygate) — a failing check aborts the construct. - Writes (
self.x = .../sym_rebind): fires after the update commits and before anyon_sym_changenotification; a raise rolls the update back (observers never see the invalid state) and propagates to the writer. It also fires when notifications are suppressed (pg.notify_on_change(False)/skip_notification=True) — those flags mute observers, never validation.
Validate only — do not mutate. A write from inside this hook
re-enters validation (an unconditionally-writing hook recurses
without bound) and escapes the failing write's rollback. Normalize
values with the field-level transform, and compute derived
state in on_sym_ready / on_sym_change.
The default implementation is a no-op.
Source code in pygx/symbolic/_object.py
on_sym_change
¶
on_sym_change(field_updates: dict[KeyPath, FieldUpdate]) -> None
Event that is triggered when field values in the subtree are updated.
This event will be called
* On per-field basis when object is modified via attribute.
* In batch when multiple fields are modified via sym_rebind method.
When a field in an object tree is updated, all ancestors' on_sym_change event
will be triggered in order, from the nearest one to furthest one.
The default implementation triggers on_sym_bound (and then on_sym_ready
when the object is concrete).
Any functools.cached_property (including the pg.typing.cached_property
shim) declared on the class is invalidated automatically just before this
event fires, so a cached value can never outlive a change to the fields it
derives from. The invalidation is a framework guarantee — it runs whether or
not this method is overridden, and whether or not an override calls
super().on_sym_change(...) — so subclasses no longer need to pop their
cached values by hand.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
field_updates
|
dict[KeyPath, FieldUpdate]
|
Updates made to the subtree. Key path is relative to current object. |
required |
Source code in pygx/symbolic/_object.py
on_topo_path_change
¶
Event triggered after this object's topo_path changes.
Fires only under topo=True — a flat object has no tree
position to change.
Source code in pygx/symbolic/_object.py
on_topo_parent_change
¶
on_topo_parent_change(
old_parent: TopologyAware | None, new_parent: TopologyAware | None
)
Event triggered after this object's topo_parent changes.
Fires only under topo=True — a flat object has no tree
position to change.
Source code in pygx/symbolic/_object.py
sym_hasattr
¶
Tests if a symbolic attribute exists.
Source code in pygx/symbolic/_object.py
sym_getattr
¶
Gets a symbolic attribute's stored value.
Unlike obj.x, this returns the value AS STORED: an
Inferential placeholder (e.g. a contextual value) comes back
as itself rather than being resolved through the parent chain,
and keys attribute access cannot serve — e.g. an extra= key
that collides with a method name — are still readable. When the
key is absent, returns default if given, else raises
AttributeError.
Source code in pygx/symbolic/_object.py
sym_attr_field
¶
sym_attr_field(key: str | int) -> Field | None
Returns the field definition for a symbolic attribute.
Source code in pygx/symbolic/_object.py
sym_keys
¶
Iterates the keys of symbolic attributes.
sym_values
¶
Iterates the values of symbolic attributes.
sym_items
¶
Iterates the (key, value) pairs of symbolic attributes.
sym_eq
¶
Tests symbolic equality: same type, equal field values.
Fields declared compare=False are ignored. Backs == (and,
with sym_hash, hash()) unless the class sets eq=False.
Source code in pygx/symbolic/_object.py
sym_lt
¶
Tests symbolic less-than.
Same-type comparison is lexicographic over the enable_compare
fields in declaration order — the same fields that feed sym_eq.
This keeps sym_eq / sym_lt / sym_gt mutually coherent: for two
same-type values exactly one of == / < / > holds (trichotomy),
and a compare=False field is ignored by all three. Cross-type
comparison defers to pygx.lt, which orders
by type bucket.
Source code in pygx/symbolic/_object.py
sym_hash
¶
Returns the symbolic hash over the class and field values.
Fields declared hash=False are ignored. Coherent with
sym_eq: symbolically-equal objects hash equal.
Source code in pygx/symbolic/_object.py
topo_setparent
¶
topo_setparent(parent: TopologyAware | None)
Sets the parent of current node in the symbolic tree.
Available only when topo=True; a topo=False object is a flat,
reference-held leaf with no tree to attach to and raises
TopoNotEnabled.
Source code in pygx/symbolic/_object.py
topo_setpath
¶
topo_setpath(path: str | KeyPath | None) -> None
Sets the path of current node in its symbolic tree.
Available only when topo=True; a topo=False object has no tree
position (topo_path is always the empty KeyPath()) and raises
TopoNotEnabled.
Source code in pygx/symbolic/_object.py
sym_missing
¶
Returns the unbound (missing) values in the subtree.
With flatten=True (default) nested entries are keyed by
their key path ({'inner.a': MISSING_VALUE}); with
flatten=False they nest ({'inner': {'a': MISSING_VALUE}}).
Empty when the object is fully bound.
Source code in pygx/symbolic/_object.py
sym_nondefault
¶
Returns the values that differ from their schema defaults.
With flatten=True (default) nested entries are keyed by
their key path ({'inner.a': 9}); with flatten=False they
nest ({'inner': {'a': 9}}). Empty when every field is at its
default.
Source code in pygx/symbolic/_object.py
sym_seal
¶
Seal or unseal current object (recursing into fields).
Source code in pygx/symbolic/_object.py
to_json
¶
to_json(**kwargs) -> WireValue
Serializes this object to a plain-Python JSON value.
Returns {'_type': <serialization key>, **fields} with every
field value converted recursively — a dict pg.from_json (or
this class's from_json) loads back into an equal object. A
container shared across fields / positions (or referenced
cyclically) is emitted once and referenced through the wire's
__ref__ / __context__ form.
Dump options (#519, threaded through the recursion like by_alias):
exclude_none=True drops None-valued mapping entries — object
fields, pg.Dict entries, and RAW dict values alike (list items are
never dropped); exclude_defaults=True drops fields equal to their
schema default; exclude_frozen (default True) drops frozen fields;
type_info=False omits the _type class marker
everywhere — the plain-dict export (dataclasses.asdict analog;
one-way — from_json of the result yields plain containers, not the
original classes). Under type_info=False, markers that ARE the
payload still emit: structural wire forms (__tuple__, __ref__),
UnknownTypedObject / opaque-pickle _types, the pg.Ref wrapper,
and classes with a custom sym_jsonify; an unresolved Inferential
dumps to {}, so pair with use_inferred=True when dumping
contextual trees. Pydantic's exclude_unset has
no pygx equivalent — defaults materialize at construction, so
provided-at-default and defaulted are indistinguishable afterwards.
Source code in pygx/symbolic/_object.py
4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 | |
sym_jsonify
¶
sym_jsonify(**kwargs) -> WireValue
Converts this object to a dict of plain Python values.
The per-object serialization hook to_json dispatches through:
returns {'_type': <serialization key>, **fields} with each
field value converted recursively, honoring the dump options
documented on to_json (type_info / by_alias /
exclude_none / ...). Subclasses may override it to emit a
custom wire form.
Source code in pygx/symbolic/_object.py
4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 | |
format
¶
Formats this object as a human-readable string.
Backs repr(obj) (compact=True) and str(obj) (the
multi-line form). compact selects the single-line rendering;
verbose requests extra detail from values that support it
(most render identically); root_indent is the starting
indent level. Further formatting kwargs (exclude_keys,
use_inferred, markdown, ...) are forwarded to the shared
formatter — see pygx.formatting.format.
Source code in pygx/symbolic/_object.py
4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 | |
ObjectMeta
¶
Bases: ABCMeta
Metaclass for pg.Object — the class-level symbolic surface.
Every pg.Object subclass gets this metaclass automatically; user
code rarely names it. At class-creation time it builds the class's
schema from annotations and class keywords, and afterwards it is the
class-level introspection / registration surface: cls.__schema__,
sym_fields, init_arg_list, apply_schema / update_schema, and
register_for_deserialization.
sym_fields
property
¶
sym_fields: Dict
The pg.typing.Dict spec carrying the class's symbolic fields.
Only init-enabled fields are included; init=False fields
live on the instance outside the symbolic attributes.
init_arg_list
property
¶
The ordered names of __init__'s (keyword-only) arguments.
Read from the schema's init_arg_list metadata; follows field
declaration order.
apply_schema
¶
apply_schema(schema: Schema | None = None) -> None
Applies a schema to a symbolic class.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema
|
Schema | None
|
The schema to apply to the class. If |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
When REPLACING the schema ( |
Source code in pygx/symbolic/_object_meta.py
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 | |
update_schema
¶
update_schema(
fields: list[Field | list[FieldDef] | dict[FieldKeyDef, FieldValueDef]],
extend: bool = True,
*,
init_arg_list: Sequence[str] | None = None,
metadata: dict[str, Any] | None = None
) -> None
Updates the schema of the class.
Only legal BEFORE any instance of the class (or a subclass) exists,
and never for a slots=True class, whose inline-slot layout is
generated from the schema at class creation — both enforced by
apply_schema (#432).
Source code in pygx/symbolic/_object_meta.py
register_for_deserialization
¶
register_for_deserialization(
serialization_key: str | None = None,
additional_keys: list[str] | None = None,
) -> None
Register current symbolic class for deserialization.
Also mirrors the resolved keys onto cls.__json_options__ so
callers that later read the class's JSON conversion options see
the updated state.
Source code in pygx/symbolic/_object_meta.py
Origin
¶
Origin(
source: Any,
tag: str,
stacktrace: bool | None = None,
stacklimit: int | None = None,
stacktop: int = -1,
)
Bases: Formattable
Class that represents the origin of a symbolic value.
Origin is used for debugging the creation chain of a symbolic value, as
well as keeping track of the factory or builder in creational design patterns.
An Origin object records the source value, a string tag, and optional
stack information on where a symbolic value is created.
Built-in tags are 'init', 'clone', 'deepclone' and 'return'.
Users can pass custom tags to the sym_setorigin method of a symbolic value
for tracking its source in their own scenarios.
Within a pg.track_origin(True) context, the sym_setorigin method of
symbolic values will be automatically called during object creation,
cloning or being returned from a functor. The stack information can be
obtained by origin.stack or origin.stacktrace.
Constructor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Any
|
Source value for the origin. |
required |
tag
|
str
|
A descriptive tag of the origin. Built-in tags are:
'init', 'clone', 'deepclone', 'return'. Users can manually
call |
required |
stacktrace
|
bool | None
|
If True, capture a stack trace for the origin. If None,
a stack trace is captured when within a |
None
|
stacklimit
|
int | None
|
An optional integer to limit the stack depth. If None, it's
determined by the value passed to |
None
|
stacktop
|
int
|
A negative index into the captured stack marking the
innermost frame to present; deeper frames are dropped. The
default |
-1
|
Source code in pygx/symbolic/_origin.py
history
¶
Returns a history of origins with an optional filter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
condition
|
Callable[[Origin], bool] | None
|
An optional callable object with signature (origin) -> should_list. If None, all origins will be listed. |
None
|
Returns:
| Type | Description |
|---|---|
list[Origin]
|
A list of filtered origin from the earliest (root) to the most recent. |
Source code in pygx/symbolic/_origin.py
chain
¶
chain(tag: str | None = None) -> list[Origin]
Returns origins from the nearest to the farthest, filtered by tag.
Source code in pygx/symbolic/_origin.py
format
¶
Formats this object.
Source code in pygx/symbolic/_origin.py
NonDeterministic
¶
Bases: PureSymbolic
Base class for classes whose objects are considered non-deterministic.
A non-deterministic value represents a value that will be decided later.
In PyGX, the values created by pg.oneof, pg.manyof and pg.floatv
are non-deterministic. See NonDeterministic subclasses for more
details.
PureSymbolic
¶
Bases: CustomTyping
Base class for classes whose objects are considered pure symbolic.
Pure symbolic objects can be used for representing abstract concepts - for example, a search space of objects - which cannot be executed but are solely representational.
Having pure symbolic objects is a key differentiator of symbolic OOP
from regular OOP: they can placehold values in an object as a high-level
expression of ideas. Later, with symbolic manipulation, the pure symbolic
objects are replaced with material values so the object can be evaluated.
This effectively decouples the expression of ideas from the
implementation of ideas. For example, pg.oneof(['a', 'b', 'c']) will
be manipulated into 'a', 'b' or 'c' based on the decision of a search
algorithm, letting the program evolve itself.
custom_apply
¶
custom_apply(
path: KeyPath,
value_spec: ValueSpec,
allow_partial: bool,
child_transform: None | Callable[[KeyPath, Field, Any], Any] = None,
) -> tuple[bool, Any]
Custom apply on a value based on its original value spec.
This implements pg.typing.CustomTyping, allowing a pure symbolic
value to be assigned to any field. To customize this behavior, override
this method in subclasses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
KeyPath
|
KeyPath of current object under its object tree. |
required |
value_spec
|
ValueSpec
|
Original value spec for this field. |
required |
allow_partial
|
bool
|
Whether allow partial object to be created. |
required |
child_transform
|
None | Callable[[KeyPath, Field, Any], Any]
|
Function to transform child node values into their final values. Transform function is called on leaf nodes first, then on their parents, recursively. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[bool, Any]
|
A tuple (proceed_with_standard_apply, value_to_proceed). If proceed_with_standard_apply is set to False, value_to_proceed will be used as final value. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If the value is not compatible with the value spec. |
Source code in pygx/symbolic/_pure_symbolic.py
Ref
¶
Bases: Object, Inferential, Extension
Symbolic reference.
In a topology-enabled symbolic tree (e.g. pg.Dict, pg.List and
topo=True objects), a node can hold only one position: adding a value
that already belongs to a tree raises SharedHoldError. Additionally,
list and dict values are automatically converted to pg.List and
pg.Dict, respectively, to enable symbolic operability.
pg.Ref relaxes both conventions: it allows a symbolic node to refer
to a value without transformation or adoption, so the same value can be
shared across multiple holders — and stays shared even when a holder is
copied. For example:
class A(pg.Object):
x: int
a = A(x=1)
r = pg.Ref(a)
b = pg.Dict(x=r)
c = pg.Dict(y=r)
# Reads through the holders resolve to the shared referent.
assert b.x is a
assert c.y is a
assert b.sym_clone().x is a
assert c.sym_clone(deep=True).y is a
In this example, pg.Ref is used to create a symbolic reference to the
object a, and the pg.Dict objects b and c can then share a
without creating additional copies. This mechanism not only mitigates the
runtime cost but also facilitates seamless sharing of states among
various symbolic objects.
Another useful scenario arises when we wish to utilize regular Python
list and dict objects. In this case, pg.Ref enables us to access the
list/dict object as fields in the symbolic tree without requiring them to
be transformed into pg.List and pg.Dict. This allows for seamless
integration of standard Python containers within the symbolic structure:
d = pg.Dict(x=pg.Ref({1: 2}))
assert isinstance(d.x, dict)
assert not isinstance(d.x, pg.Dict)
e = pg.Dict(x=pg.Ref([0, 1, 2]))
assert isinstance(e.x, list)
assert not isinstance(e.x, pg.List)
A Ref may also point at a node inside the holder's own tree — a
reference to a sibling is a shared (DAG) edge, not a cycle, and is
allowed.
A Ref may be held by a topo=False object. It is Inferential,
but its infer() returns its own referent rather than walking the tree,
so needs_topo() is False and reading the field resolves in either mode
(unlike a contextual value, which still requires topo=True).
Please be aware that pg.Ref objects are treated as leaf nodes in the
symbolic tree, even when they reference other symbolic objects. As a
result, the sym_rebind() method cannot modify the value they are
pointing to.
For primitive types, pg.Ref() returns their values directly without
creating a reference. For example, pg.Ref(1) and pg.Ref('abc')
will simply return the values 1 and 'abc', respectively, without any
additional referencing.
Source code in pygx/symbolic/_ref.py
infer
¶
needs_topo
¶
A Ref carries its referent — inference reads no tree state.
The topo=True pin on this class is about the WRAPPER (per-site
cloning via _sym_clone), not about where it may be held. A Ref in
a topo=False holder resolves fine: infer() returns self._value.
Source code in pygx/symbolic/_ref.py
custom_apply
¶
custom_apply(
path: KeyPath,
value_spec: ValueSpec,
allow_partial: bool = False,
child_transform: None | Callable[[KeyPath, Field, Any], Any] = None,
) -> tuple[bool, Any]
Validate candidates during value_spec binding time.
Source code in pygx/symbolic/_ref.py
UnknownCallable
¶
UnknownCallable(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: UnknownSymbol
Symbolic object for representing unknown callables.
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
UnknownFunction
¶
UnknownFunction(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: UnknownCallable
Symbolic object for representing unknown functions.
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
UnknownMethod
¶
UnknownMethod(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: UnknownCallable
Symbolic object for representing unknown methods.
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
UnknownSymbol
¶
UnknownSymbol(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: Object, CustomTyping
Base class for symbolic stand-ins of unknown symbols.
When pg.from_json / pg.load encounter a serialized _type
that is not registered and cannot be auto-imported, passing
convert_unknown=True deserializes it into one of these stand-ins
(UnknownType, UnknownTypedObject, UnknownFunction,
UnknownMethod) instead of raising TypeError. Stand-ins keep the
original payload, so the value can be inspected and re-serialized
without the defining module being importable.
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
UnknownType
¶
UnknownType(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: UnknownSymbol
Symbolic object for representing unknown types.
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
UnknownTypedObject
¶
UnknownTypedObject(
*,
allow_partial: bool = False,
sealed: bool | None = None,
root_path: KeyPath | None = None,
explicit_init: bool = False,
**kwargs: Any
)
Bases: UnknownSymbol
Symbolic object for representing objects of unknown-type.
Source code in pygx/symbolic/_object.py
3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 | |
sym_jsonify
¶
sym_jsonify(**kwargs) -> WireValue
Converts current object to a dict of plain Python objects.
Source code in pygx/symbolic/_unknown_symbols.py
clone
¶
clone(
x: _CloneT,
deep: bool = False,
memo: Any | None = None,
override: Mapping[Any, Any] | None = None,
) -> _CloneT
Clones a value, using symbolic clone if possible.
Example:
from typing import Any
import pygx as pg
class A(pg.Object):
x: int
y: Any
# B is not a symbolic object.
class B:
pass
# Shallow copy on non-symbolic values (by reference).
a = A(x=1, y=B())
b = pg.clone(a)
assert pg.eq(a, b)
assert a.y is b.y
# Deepcopy on non-symbolic values.
c = pg.clone(a, deep=True)
assert pg.ne(a, c)
assert a.y is not c.y
# Copy with override
d = pg.clone(a, override={'x': 2})
assert d.x == 2
assert d.y is a.y
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
_CloneT
|
value to clone. |
required |
deep
|
bool
|
If True, use deep clone, otherwise use shallow clone. |
False
|
memo
|
Any | None
|
Optional memo object for deep clone. |
None
|
override
|
Mapping[Any, Any] | None
|
Value to override if value is symbolic. |
None
|
Returns:
| Type | Description |
|---|---|
_CloneT
|
Cloned instance. |
Source code in pygx/symbolic/_base.py
3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 | |
contains
¶
contains(
x: Any,
value: Any = None,
type: type[Any] | tuple[type[Any], ...] | None = None,
) -> bool
Returns if a value contains values of specific type.
Example:
from typing import Any
import pygx as pg
class A(pg.Object):
x: Any
y: Any
# Test if a symbolic tree contains a value.
assert pg.contains(A(x='a', y='b'), 'a')
assert not pg.contains(A(x='a', y='b'), A)
# Test if a symbolic tree contains a type.
assert pg.contains({'x': A(x=1, y=2)}, type=A)
assert pg.contains({'x': A(x=1, y=2)}, type=int)
assert pg.contains({'x': A(x=1, y=2)}, type=(int, float))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
The source value to query against. |
required |
value
|
Any
|
Value of sub-node to contain. Applicable when |
None
|
type
|
type[Any] | tuple[type[Any], ...] | None
|
A type or a tuple of types for the sub-nodes. Applicable if not None. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if |
bool
|
is an instance of |
Source code in pygx/symbolic/_base.py
eq
¶
Compares two values for equality, using symbolic equality if possible.
Example:
from typing import Any
import pygx as pg
class A(pg.Object):
x: Any
def sym_eq(self, right):
if super().sym_eq(right):
return True
return pg.eq(self.x, right)
class B:
pass
assert pg.eq(1, 1)
assert pg.eq(A(x=1), A(x=1))
# This is True since A has override `sym_eq`.
assert pg.eq(A(x=1), 1)
# Objects of B are compared by references.
assert not pg.eq(A(x=B()), A(x=B()))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
Any
|
The left-hand value to compare. |
required |
right
|
Any
|
The right-hand value to compare. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if left and right are equal or symbolically equal. Otherwise False. |
Source code in pygx/symbolic/_base.py
3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 | |
from_json
¶
from_json(
json_value: Any,
*,
expected_type: type[T],
context: WireConversionContext | None = ...,
wrap: bool = ...,
auto_import: bool = ...,
convert_unknown: bool = ...,
allow_partial: bool = ...,
root_path: KeyPath | None = ...,
value_spec: ValueSpec | None = ...,
**kwargs: Any
) -> T
from_json(
json_value: Any,
*,
expected_type: Any = ...,
context: WireConversionContext | None = ...,
wrap: bool = ...,
auto_import: bool = ...,
convert_unknown: bool = ...,
allow_partial: bool = ...,
root_path: KeyPath | None = ...,
value_spec: ValueSpec | None = ...,
**kwargs: Any
) -> Any
from_json(
json_value: Any,
*,
expected_type: Any = Any,
context: WireConversionContext | None = None,
wrap: bool = True,
auto_import: bool = True,
convert_unknown: bool = False,
allow_partial: bool = False,
root_path: KeyPath | None = None,
value_spec: ValueSpec | None = None,
**kwargs: Any
) -> Any
Deserializes a (maybe) symbolic value from JSON value.
Example:
from typing import Any
import pygx as pg
class A(pg.Object):
x: Any
a1 = A(x=1)
json = a1.to_json()
a2 = pg.from_json(json)
assert pg.eq(a1, a2)
# Narrow the static return type and assert at runtime:
a3 = pg.from_json(a1.to_json(), expected_type=A) # statically typed as ``A``.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
json_value
|
Any
|
Input JSON value. |
required |
expected_type
|
Any
|
When provided (anything other than the default |
Any
|
context
|
WireConversionContext | None
|
JSON conversion context. |
None
|
wrap
|
bool
|
If True (default), schemaless JSON |
True
|
auto_import
|
bool
|
If True, when a '_type' is not registered, PyGX will identify its parent module and automatically import it. For example, if the type is 'foo.bar.A', PyGX will try to import 'foo.bar' and find the class 'A' within the imported module. |
True
|
convert_unknown
|
bool
|
If True, when a '_type' is not registered and cannot
be imported, PyGX will create objects of:
- |
False
|
allow_partial
|
bool
|
Whether to allow elements of the list to be partial. |
False
|
root_path
|
KeyPath | None
|
KeyPath of loaded object in its object tree. |
None
|
value_spec
|
ValueSpec | None
|
The value spec for the symbolic list or dict. |
None
|
**kwargs
|
Any
|
Allow passing through keyword arguments to from_json of specific types. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
Deserialized value, which is |
Any
|
|
Any
|
|
Any
|
|
Any
|
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
Source code in pygx/symbolic/_base.py
4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 | |
from_json_str
¶
from_json_str(
json_str: bytes | str,
*,
expected_type: type[T],
context: WireConversionContext | None = ...,
auto_import: bool = ...,
convert_unknown: bool = ...,
allow_partial: bool = ...,
root_path: KeyPath | None = ...,
value_spec: ValueSpec | None = ...,
format: str | WireFormat = ...,
**kwargs: Any
) -> T
from_json_str(
json_str: bytes | str,
*,
expected_type: Any = ...,
context: WireConversionContext | None = ...,
auto_import: bool = ...,
convert_unknown: bool = ...,
allow_partial: bool = ...,
root_path: KeyPath | None = ...,
value_spec: ValueSpec | None = ...,
format: str | WireFormat = ...,
**kwargs: Any
) -> Any
from_json_str(
json_str: bytes | str,
*,
expected_type: Any = Any,
context: WireConversionContext | None = None,
auto_import: bool = True,
convert_unknown: bool = False,
allow_partial: bool = False,
root_path: KeyPath | None = None,
value_spec: ValueSpec | None = None,
format: str | WireFormat = "json",
**kwargs: Any
) -> Any
Deserialize (maybe) symbolic object from JSON string.
Example:
from typing import Any
import pygx as pg
class A(pg.Object):
x: Any
a1 = A(x=1)
json_str = a1.to_json_str()
a2 = pg.from_json_str(json_str)
assert pg.eq(a1, a2)
# Narrow the static return type and assert at runtime:
a3 = pg.from_json_str(json_str, expected_type=A)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
json_str
|
bytes | str
|
JSON string. |
required |
expected_type
|
Any
|
When provided (anything other than the default |
Any
|
context
|
WireConversionContext | None
|
JSON conversion context. |
None
|
auto_import
|
bool
|
If True, when a '_type' is not registered, PyGX will identify its parent module and automatically import it. For example, if the type is 'foo.bar.A', PyGX will try to import 'foo.bar' and find the class 'A' within the imported module. |
True
|
convert_unknown
|
bool
|
If True, when a '_type' is not registered and cannot
be imported, PyGX will create objects of:
- |
False
|
allow_partial
|
bool
|
If True, allow a partial symbolic object to be created. Otherwise error will be raised on partial value. |
False
|
root_path
|
KeyPath | None
|
The symbolic path used for the deserialized root object. |
None
|
value_spec
|
ValueSpec | None
|
The value spec for the symbolic list or dict. |
None
|
format
|
str | WireFormat
|
The wire format to decode with -- a registered format name
(e.g. |
'json'
|
**kwargs
|
Any
|
Additional keyword arguments that will be passed to
|
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
A deserialized value. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
Source code in pygx/symbolic/_base.py
4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 | |
gt
¶
Returns True if a value is symbolically greater than the other value.
Refer to pygx.lt for the definition of symbolic comparison.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
Any
|
The left-hand value to compare. |
required |
right
|
Any
|
The right-hand value to compare. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the left value is symbolically greater than the right value. |
Source code in pygx/symbolic/_base.py
is_abstract
¶
Returns if the input value is abstract.
Example:
@pg.symbolize
class Foo:
def __init__(self, x):
pass
class Bar(pg.PureSymbolic):
pass
assert not pg.is_abstract(1)
assert not pg.is_abstract(Foo(1))
assert pg.is_abstract(Foo.partial())
assert pg.is_abstract(Bar())
assert pg.is_abstract(Foo(Bar()))
assert pg.is_abstract(Foo(pg.oneof([1, 2])))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
Value to query against. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if value itself is partial/PureSymbolic or its child and nested |
bool
|
child fields contain partial/PureSymbolic values. |
Source code in pygx/symbolic/_base.py
is_deterministic
¶
Returns if the input value is deterministic.
Example:
@pg.symbolize
def foo(x, y):
pass
assert pg.is_deterministic(1)
assert pg.is_deterministic(foo(1, 2))
assert not pg.is_deterministic(pg.oneof([1, 2]))
assert not pg.is_deterministic(foo(pg.oneof([1, 2]), 3))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
Value to query against. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if value itself is not NonDeterministic and its child and nested |
bool
|
child fields do not contain NonDeterministic values. |
Source code in pygx/symbolic/_base.py
is_pure_symbolic
¶
Returns if the input value is pure symbolic.
Example:
class Bar(pg.PureSymbolic):
pass
@pg.symbolize
def foo(x, y):
pass
assert not pg.is_pure_symbolic(1)
assert not pg.is_pure_symbolic(foo(1, 2))
assert pg.is_pure_symbolic(Bar())
assert pg.is_pure_symbolic(foo(Bar(), 1))
assert pg.is_pure_symbolic(foo(pg.oneof([1, 2]), 1))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
Value to query against. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if value itself is PureSymbolic or its child and nested |
bool
|
child fields contain PureSymbolic values. |
Source code in pygx/symbolic/_base.py
lt
¶
Returns True if a value is symbolically less than the other value.
Symbolic values are comparable by their symbolic representations. For common types such as numbers and string, symbolic comparison returns the same value as value comparisons. For example:
assert pg.lt(False, True) == (False < True)
assert pg.lt(0.1, 1) == (0.1 < 1)
assert pg.lt('a', 'ab') == ('a' < 'ab')
However, symbolic comparison can be applied on hierarchical values, for example:
class A(pg.Object):
x: int
assert pg.lt(['a'], ['a', 'b'])
assert pg.lt(['a', 'b', 'c'], ['b'])
assert pg.lt({'x': 1}, {'x': 2})
assert pg.lt({'x': 1}, {'y': 1})
assert pg.lt(A(x=1), A(x=2))
Also, symbolic values of different types can be compared, for example:
assert pg.lt(pg.MISSING_VALUE, None)
assert pg.lt(None, 1)
assert pg.lt(1, 'abc')
assert pg.lt('abc', [])
assert pg.lt([], {})
assert pg.lt([], A(x=1))
The high-level idea is that a value with lower information entropy is less
than a value with higher information entropy. As a result, we know that
pg.MISSING_VALUE is the smallest among all values.
The order of symbolic representation are defined by the following rules:
1) If x and y are comparable by their values, they will be compared using
operator <. (e.g. bool, int, float, str)
2) If x and y are not directly comparable and are different in their types,
they will be compared based on their types. The order of different types
are: pg.MISSING_VALUE, NoneType, bool, int, float, str, list, tuple, set,
dict, functions/classes. When different functions/classes compare, their
order is determined by their qualified name.
3) If x and y are of the same type, which are symbolic containers (e.g. list,
dict, pg.Symbolic objects), their order will be determined by the order of
their first sub-nodes which are different. Therefore ['b'] is greater than
['a', 'b'], though the latter has 2 elements.
4) Non-symbolic classes can define method sym_lt to enable symbolic
comparison.
5) Two pg.Object values of the same type are ordered over their
compare=True fields only (the same fields that feed pg.eq), so a
compare=False field never affects pg.lt / pg.gt — keeping
equality and ordering consistent (exactly one of < / == / >
holds).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
Any
|
The left-hand value to compare. |
required |
right
|
Any
|
The right-hand value to compare. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the left value is symbolically less than the right value. |
Source code in pygx/symbolic/_base.py
3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 | |
maybe_clone
¶
Returns a clone if a symbolic value already has a parent.
The copy counterpart of pg.maybe_ref. Both
answer the same question — this value may already belong to a tree, and
I am about to hold it — and differ only in the resolution they pick:
pg.maybe_ref(v)— keep ONE node, SHARED. The holders observe each other's writes; the value keeps its original position.pg.maybe_clone(v)— keep the holders INDEPENDENT. Each gets its own copy, positioned under itself.
A parentless value is returned as-is by both: there is no conflict to
resolve, and the caller's hold adopts it in place. A topo=False
value never has a parent — it is reference-held data — so it always
passes through unchanged.
Example:
import pygx as pg
class A(pg.Object, topo=True):
x: int
a = A(x=1)
h1 = pg.Dict(v=a) # adopts `a` — no conflict
# `a` is held now, so a second hold has to choose:
shared = pg.Dict(v=pg.maybe_ref(a)) # sees `a`'s later writes
snapshot = pg.Dict(v=pg.maybe_clone(a)) # frozen at today's value
Writing this explicitly says which one was intended. Assigning a held value directly is ambiguous — the same expression adopts a free value but copies a held one, so its meaning depends on whether some unrelated holder got there first, and on construction order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
_CloneT
|
The value about to be held. |
required |
deep
|
bool
|
If True, deep-clone a held symbolic value; otherwise shallow. |
False
|
memo
|
Any | None
|
Optional memo object for deep clone. |
None
|
Returns:
| Type | Description |
|---|---|
_CloneT
|
|
_CloneT
|
with no parent), otherwise a clone of it. |
Source code in pygx/symbolic/_base.py
ne
¶
Compares two values for inequality, using symbolic equality if possible.
Example:
from typing import Any
import pygx as pg
class A(pg.Object):
x: Any
def sym_eq(self, right):
if super().sym_eq(right):
return True
return pg.eq(self.x, right)
class B:
pass
assert pg.ne(1, 2)
assert pg.ne(A(x=1), A(x=2))
# A has override `sym_eq`.
assert not pg.ne(A(x=1), 1)
# Objects of B are compared by references.
assert pg.ne(A(x=B()), A(x=B()))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
Any
|
The left-hand value to compare. |
required |
right
|
Any
|
The right-hand value to compare. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if left and right are not equal or not symbolically equal. |
bool
|
Otherwise False. |
Source code in pygx/symbolic/_base.py
query
¶
query(
x: Any,
path_regex: str | None = None,
where: None | (Callable[[Any], bool] | Callable[[Any, Any], bool]) = None,
enter_selected: bool = False,
custom_selector: None | (
Callable[[KeyPath, Any], bool] | Callable[[KeyPath, Any, Any], bool]
) = None,
) -> dict[str, Any]
Queries a (maybe) symbolic value.
Example:
class A(pg.Object):
x: int
y: int
value = {
'a1': A(x=0, y=1),
'a2': [A(x=1, y=1), A(x=1, y=2)],
'a3': {
'p': A(x=2, y=1),
'q': A(x=2, y=2)
}
}
# Query by path regex.
# Shall print:
# {'a3.p': A(x=2, y=1)}
print(pg.query(value, r'.*p'))
# Query by value.
# Shall print:
# {
# 'a2[1].y': 2,
# 'a3.p.x': 2,
# 'a3.q.x': 2,
# 'a3.q.y': 2,
# }
print(pg.query(value, where=lambda v: v==2))
# Query by path, value and parent.
# Shall print:
# {
# 'a2[1].y': 2,
# }
print(pg.query(
value, r'.*y',
where=lambda v, p: v > 1 and isinstance(p, A) and p.x == 1))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
A nested structure that may contains symbolic value. |
required |
path_regex
|
str | None
|
Optional regex expression to constrain path. |
None
|
where
|
None | (Callable[[Any], bool] | Callable[[Any, Any], bool])
|
Optional callable to constrain value and parent when path matches
with
|
None
|
enter_selected
|
bool
|
If True, if a node is selected, enter the node and query its sub-nodes. |
False
|
custom_selector
|
None | (Callable[[KeyPath, Any], bool] | Callable[[KeyPath, Any, Any], bool])
|
Optional callable object as custom selector. When
|
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of key path to value as results for selected values. |
Source code in pygx/symbolic/_base.py
3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 | |
hash
¶
Returns the hash of a value, using symbolic hashing if possible.
Symbolic values hash by VALUE (two equal symbolic trees share one hash),
while non-symbolic objects fall back to hash. Raw list / dict /
tuple containers — unhashable by hash — are hashed structurally,
element by element, mirroring the element-wise comparison pg.eq
performs on them.
Example:
from typing import Any
import pygx as pg
class A(pg.Object):
x: Any
class B:
pass
assert pg.hash(A(x=1)) == pg.hash(A(x=1))
assert pg.hash(pg.Dict(x=[A(x=1)])) == pg.hash(pg.Dict(x=[A(x=1)]))
# Non-symbolic objects hash by identity (via `hash`).
assert pg.hash(B()) != pg.hash(B())
# Raw containers hash structurally (`hash([1, 2])` would raise).
assert pg.hash([1, 2]) == pg.hash([1, 2])
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
Value for computing hash. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The hash value for |
Source code in pygx/symbolic/_base.py
3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 | |
to_json
¶
Serializes a (maybe) symbolic value into a plain Python object.
Example:
from typing import Any
import pygx as pg
class A(pg.Object):
x: Any
a1 = A(x=1)
json = pg.to_json(a1)
a2 = pg.from_json(json)
assert pg.eq(a1, a2)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
value to serialize. Applicable value types are:
|
required |
**kwargs
|
Any
|
Keyword arguments to pass to value.to_json if value is WireConvertible. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
JSON value. |
Source code in pygx/symbolic/_base.py
to_json_str
¶
to_json_str(
value: Any,
*,
json_indent: int | None = None,
format: str | WireFormat = "json",
**kwargs: Any
) -> str
Serializes a (maybe) symbolic value into a wire-format string.
Example:
from typing import Any
import pygx as pg
class A(pg.Object):
x: Any
a1 = A(x=1)
json_str = pg.to_json_str(a1)
a2 = pg.from_json_str(json_str)
assert pg.eq(a1, a2)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Value to serialize. |
required |
json_indent
|
int | None
|
The size of indentation passed to the format's encoder. |
None
|
format
|
str | WireFormat
|
The wire format to encode with -- a registered format name
(e.g. |
'json'
|
**kwargs
|
Any
|
Additional keyword arguments that are passed to |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
A serialized string. |
Source code in pygx/symbolic/_base.py
traverse
¶
traverse(
x: Any,
preorder_visitor_fn: (
None | Callable[[KeyPath, Any, Any], TraverseAction | None]
) = None,
postorder_visitor_fn: (
None | Callable[[KeyPath, Any, Any], TraverseAction | None]
) = None,
root_path: KeyPath | None = None,
parent: Any | None = None,
) -> bool
Traverse a (maybe) symbolic value using visitor functions.
Example:
class A(pg.Object):
x: int
v = [{'a': A(x=1)}, A(x=2)]
integers = []
def track_integers(k, v, p):
if isinstance(v, int):
integers.append((k, v))
return pg.TraverseAction.ENTER
pg.traverse(v, track_integers)
assert integers == [('[0].a.x', 1), ('[1].x', 2)]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Any
|
Maybe symbolic value. |
required |
preorder_visitor_fn
|
None | Callable[[KeyPath, Any, Any], TraverseAction | None]
|
Visitor called before entering each node's
sub-tree. Signature is |
None
|
postorder_visitor_fn
|
None | Callable[[KeyPath, Any, Any], TraverseAction | None]
|
Visitor called after a node's sub-tree has been
traversed. Same signature; |
None
|
root_path
|
KeyPath | None
|
KeyPath of root value. |
None
|
parent
|
Any | None
|
Optional parent of the root node. |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if the traversal visited every node — i.e. no visitor returned
|
Source code in pygx/symbolic/_base.py
3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 | |
boilerplate_class
¶
boilerplate_class(
cls_name: str,
value: Object,
init_arg_list: list[str] | None = None,
**kwargs: Any
) -> type[Object]
Create a boilerplate class using a symbolic object.
As the name indicates, a boilerplate class is a class that can be used as a boilerplate to create objects.
Implementation-wise it's a class that extends the type of the input value, while setting the default values of its (inherited) schema using the value from the input.
An analogy to a boilerplate class is a prebound function. For example:
# A regular function: corresponds to a pg.Object subclass.
def f(a, b, c):
return a + b + c
# A partially bound function: corresponds to a boilerplate class created
# from a partially bound object.
def g(c):
return f(1, 2, c)
# A fully bound function: corresponds to a boilerplate class created from
# a fully bound object.
def h():
return f(1, 2, 3)
A boilerplate class can be created with a value that is fully bound
(like function h above), or partially bound (like function g above).
Here are a few examples:
class A(pg.Object):
a: str # Field A.
b: int # Field B.
# From a partially bound object: `a` is frozen to 'foo', `b` stays open.
A1 = pg.boilerplate_class('A1', A.partial(a='foo'))
x = A1(b=1)
assert isinstance(x, A)
assert x.a == 'foo' and x.b == 1
# From a fully bound object: all fields have defaults.
A2 = pg.boilerplate_class('A2', A(a='bar', b=2))
y = A2()
assert y.a == 'bar' and y.b == 2
Note that symbolic equality is class-sensitive: a boilerplate instance compares equal to other instances of the same boilerplate class, but not to an instance of the original class with the same field values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cls_name
|
str
|
Name of the boilerplate class. |
required |
value
|
Object
|
Value that is used as the default value of the boilerplate class. |
required |
init_arg_list
|
list[str] | None
|
An optional list of strings as init positional arguments names. |
None
|
**kwargs
|
Any
|
Keyword arguments for infrequently used options. Acceptable
keywords are |
{}
|
Returns:
| Type | Description |
|---|---|
type[Object]
|
A class which extends the input value's type, with its schema's default values set from the input value. |
Raises:
| Type | Description |
|---|---|
ValueError
|
|
TypeError
|
an unsupported keyword argument is provided. |
Source code in pygx/symbolic/_boilerplate.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | |
apply_wrappers
¶
apply_wrappers(
wrapper_classes: Sequence[type[ClassWrapper]] | None = None,
where: Callable[[type[ClassWrapper]], bool] | None = None,
) -> AbstractContextManager[Any]
Context manager for swapping user classes with their class wrappers.
This helper method is a handy tool to swap user classes with their wrappers within a code block, without modifying existing code.
For example:
class A:
def __init__(self, x=1):
self.x = x
def foo():
return A()
APrime = pg.wrap(A)
with pg.apply_wrappers([APrime]):
# Direct creation of an instance of `A` will be detoured to `APrime`.
assert isinstance(A(), APrime)
# Indirect creation of an instance of `A` will be detoured too.
assert isinstance(foo(), APrime)
# Out of the scope, direct/indirect creation of `A` will be restored.
assert not isinstance(A(), APrime)
assert not isinstance(foo(), APrime)
pg.apply_wrappers can be nested, under which the inner context will
apply the wrappers from the outer context. pg.apply_wrappers is NOT
thread-safe.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wrapper_classes
|
Sequence[type[ClassWrapper]] | None
|
Wrapper classes to use. If None, sets it to all registered wrapper classes. |
None
|
where
|
Callable[[type[ClassWrapper]], bool] | None
|
An optional filter function in signature (wrapper_class) -> bool.
If not None, only filtered |
None
|
Returns:
| Type | Description |
|---|---|
AbstractContextManager[Any]
|
A context manager that detours the original classes to the wrapper classes. |
Source code in pygx/symbolic/_class_wrapper.py
wrap
¶
wrap(
cls: type[Any],
init_args: (
list[Field | FieldDef] | dict[FieldKeyDef, FieldValueDef] | None
) = None,
*,
reset_state_fn: Callable[[Any], None] | None = None,
repr: bool = True,
eq: bool = False,
class_name: str | None = None,
module_name: str | None = None,
auto_doc: bool = False,
auto_typing: bool = False,
serialization_key: str | None = None,
additional_keys: list[str] | None = None,
override: dict[str, Any] | None = None,
topo: bool | None = None
) -> type[ClassWrapper]
Makes a symbolic class wrapper from a regular Python class.
pg.wrap is called by pygx.symbolize for symbolizing existing
Python classes. For example:
class A:
def __init__(self, x):
self.x = x
# The following two lines are equivalent.
A1 = pg.symbolize(A)
A2 = pg.wrap(A)
Besides passing the source class, pg.wrap allows the user to pass symbolic
field definitions for the init arguments. For example:
Moreover, multiple flags are provided to determine whether or not to use the symbolic operations as the default behaviors. For example:
A4 = pg.wrap(
A,
[],
# Instead of clearing out all internal states (default),
# do not reset internal state.
reset_state_fn=lambda self: None,
# Use symbolic representation for __repr__ and __str__.
repr=True,
# Use symbolic equality for __eq__, __ne__ and __hash__.
eq=True,
# Customize the class name (the default behavior
# is to use the source class name).
class_name='A4',
# Customize the module name for the created class (the default
# behavior is to use the source module name).
module_name='my_module')
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cls
|
type[Any]
|
Class to wrap. |
required |
init_args
|
list[Field | FieldDef] | dict[FieldKeyDef, FieldValueDef] | None
|
An optional list of field definitions for the arguments of
init. It can be a sparse value specifications for argument in the
init method of |
None
|
reset_state_fn
|
Callable[[Any], None] | None
|
An optional callable object to reset the internal state of the user class when rebind happens. |
None
|
repr
|
bool
|
Options for generating |
True
|
eq
|
bool
|
Options for generating |
False
|
class_name
|
str | None
|
An optional string used as class name for the wrapper class. If None, the wrapper class will use the class name of the wrapped class. |
None
|
module_name
|
str | None
|
An optional string used as module name for the wrapper class. If None, the wrapper class will use the module name of the wrapped class. |
None
|
auto_doc
|
bool
|
If True, the descriptions for init argument fields will be extracted from docstring if present. |
False
|
auto_typing
|
bool
|
If True, PyGX typing (runtime-typing) will be enabled based
on type annotations inspected from the |
False
|
serialization_key
|
str | None
|
An optional string to be used as the serialization key
for the class during |
None
|
additional_keys
|
list[str] | None
|
An optional list of strings as additional keys to deserialize an object of the registered class. This can be useful when we need to relocate or rename the registered class while being able to load existing serialized JSON values. |
None
|
override
|
dict[str, Any] | None
|
Additional class attributes to override. |
None
|
topo
|
bool | None
|
Whether the wrapper joins the symbolic tree. |
None
|
Returns:
| Type | Description |
|---|---|
type[ClassWrapper]
|
A subclass of |
Raises:
| Type | Description |
|---|---|
TypeError
|
input |
Source code in pygx/symbolic/_class_wrapper.py
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 | |
wrap_module
¶
wrap_module(
module: ModuleType,
names: Sequence[str] | None = None,
where: Callable[[type[Any]], bool] | None = None,
export_to: ModuleType | None = None,
**kwargs: Any
) -> list[type[ClassWrapper]]
Wraps classes from a module.
For example, users can wrap all subclasses of xxx.Base under module
xxx:
Aliased names resolving to the same class share one wrapper class rather than producing duplicates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module
|
ModuleType
|
The module that contains the classes to wrap. |
required |
names
|
Sequence[str] | None
|
An optional list of class names. If not provided, all classes under
|
None
|
where
|
Callable[[type[Any]], bool] | None
|
An optional filter function in signature (user_class) -> bool.
Only the classes under |
None
|
export_to
|
ModuleType | None
|
An optional module to export the wrapper classes to, under their original names. |
None
|
**kwargs
|
Any
|
Keyword arguments passed to |
{}
|
Returns:
| Type | Description |
|---|---|
list[type[ClassWrapper]]
|
A list of the created wrapper classes. |
Source code in pygx/symbolic/_class_wrapper.py
compound
¶
compound(
base_class: type[Object] | None = None,
args: (
None
| list[
tuple[tuple[str, KeySpec], ValueSpec, str]
| tuple[tuple[str, KeySpec], ValueSpec, str, Any]
]
) = None,
**kwargs: Any
) -> Callable[[Callable[..., Any]], type[Compound]]
Function decorator to create a compound class.
A compound class subclasses the factory function's return type, and
delegates attribute access to the object built by the factory
(its decomposed), which is rebuilt whenever the compound's symbolic
arguments change.
Example:
import dataclasses
import pygx as pg
@dataclasses.dataclass
class Foo:
x: int
y: int
def sum(self):
return self.x + self.y
@pg.compound
def foo_with_equal_x_y(v: int) -> Foo:
return Foo(v, v)
f = foo_with_equal_x_y(1)
# First of all, the objects of compound classes can be used as an in-place
# replacement for the objects of regular classes, as they are subclasses of
# the regular classes.
assert issubclass(foo_with_equal_x_y, Foo)
assert isinstance(f, Foo)
# We can access symbolic attributes of the compound object.
assert f.v == 1
# We can also access the public APIs of the decomposed object.
assert f.x == 1
assert f.y == 1
assert f.sum() == 2
# Or explicitly access the decomposed object.
assert f.decomposed == Foo(1, 1)
# Moreover, symbolic power is fully unleashed to the compound class.
f.sym_rebind(v=2)
assert f.x == 2
assert f.y == 2
assert f.sum() == 4
# Raises with runtime type check: 2.5 is not an integer.
f.sym_rebind(v=2.5)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_class
|
type[Object] | None
|
The base class of the compound class, which should be a
|
None
|
args
|
None | list[tuple[tuple[str, KeySpec], ValueSpec, str] | tuple[tuple[str, KeySpec], ValueSpec, str, Any]]
|
Symbolic args specification. See |
None
|
**kwargs
|
Any
|
Keyword arguments. See |
{}
|
Returns:
| Type | Description |
|---|---|
Callable[[Callable[..., Any]], type[Compound]]
|
A compound class (bare-decorator form), or a decorator that converts a |
Callable[[Callable[..., Any]], type[Compound]]
|
factory function into a compound class. |
Source code in pygx/symbolic/_compounding.py
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 | |
compound_class
¶
compound_class(
factory_fn: FunctionType,
base_class: type[Object] | None = None,
args: (
list[Field | FieldDef] | dict[FieldKeyDef, FieldValueDef] | None
) = None,
*,
lazy_build: bool = True,
auto_doc: bool = True,
auto_typing: bool = True,
serialization_key: str | None = None,
additional_keys: list[str] | None = None,
add_to_registry: bool = False
) -> type[Compound]
Creates a compound class from a factory function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factory_fn
|
FunctionType
|
A function that produces a compound object. |
required |
base_class
|
type[Object] | None
|
The base class of the compound class, which should be a
|
None
|
args
|
list[Field | FieldDef] | dict[FieldKeyDef, FieldValueDef] | None
|
Symbolic args specification. |
None
|
lazy_build
|
bool
|
If True, |
True
|
auto_doc
|
bool
|
If True, the descriptions of argument fields will be inherited
from |
True
|
auto_typing
|
bool
|
If True, the value spec for constraining each argument will be
inferred from its annotation. Otherwise the value specs for all arguments
will be |
True
|
serialization_key
|
str | None
|
An optional string to be used as the serialization key
for the class during |
None
|
additional_keys
|
list[str] | None
|
An optional list of strings as additional keys to deserialize an object of the registered class. This can be useful when we need to relocate or rename the registered class while being able to load existing serialized JSON values. |
None
|
add_to_registry
|
bool
|
If True, the newly created compound class will be added to the registry for deserialization. |
False
|
Returns:
| Type | Description |
|---|---|
type[Compound]
|
A compound class that subclasses both |
Source code in pygx/symbolic/_compounding.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | |
field
¶
field(
*,
value_spec: ValueSpec | None = None,
default: Any = MISSING_VALUE,
default_factory: Callable[[], Any] | None = None,
doc: str | None = None,
metadata: dict[str, Any] | None = None,
transform: Callable[[Any], Any] | None = None,
validator: Callable[[Any], None] | None = None,
alias: str | None = None,
repr: bool | None = None,
compare: bool | None = None,
hash: bool | None = None,
init: bool | None = None,
clone: bool | None = None,
validate: bool | None = None,
assign: str | None = None
) -> Any
Field descriptor for pg.Object dataclass-like fields.
Registered as a field_specifier on pg.ObjectMeta so that type
checkers honoring PEP 681 (pyright/Pylance/mypy) read the field's
static properties from the keyword arguments here.
pg.Object.__init__ is keyword-only by default
(dataclass_transform(kw_only_default=True)), so this helper exists
primarily to attach doc or metadata to an annotation-style
field — matching the third/fourth tuple slots in pg.members:
class A(pg.Object):
x: int = pg.field(
default=1,
doc='Initial count.',
metadata={'serialize_as': 'count'},
)
A value_spec may be supplied to apply richer runtime constraints
than the bare annotation can express (e.g. regex / numeric bounds):
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value_spec
|
ValueSpec | None
|
Optional explicit |
None
|
default
|
Any
|
The field's default value. If omitted (and no
|
MISSING_VALUE
|
default_factory
|
Callable[[], Any] | None
|
Zero-argument callable producing the default
value. Stored on the field's value spec and invoked once per
instance (stdlib- |
None
|
doc
|
str | None
|
Field docstring — equivalent to the third tuple slot in
|
None
|
metadata
|
dict[str, Any] | None
|
Field metadata dict — equivalent to the fourth tuple
slot in |
None
|
transform
|
Callable[[Any], Any] | None
|
Optional |
None
|
validator
|
Callable[[Any], None] | None
|
Optional |
None
|
alias
|
str | None
|
Optional wire-layer alias for this field's key. Accepted by
|
None
|
repr
|
bool | None
|
Whether the field appears in |
None
|
compare
|
bool | None
|
Whether the field participates in symbolic equality
( |
None
|
hash
|
bool | None
|
Whether the field participates in symbolic hashing. Same
|
None
|
init
|
bool | None
|
If False, drop this field from the generated |
None
|
clone
|
bool | None
|
Only meaningful when |
None
|
validate
|
bool | None
|
Whether values are validated against the field's value
spec. Same |
None
|
assign
|
str | None
|
How this field takes the values it is given, overriding the
class-level |
None
|
Returns:
A FieldDescriptor carrying the resolved default plus
doc/metadata/flags. The return is typed as Any so the field's
declared annotation is what type checkers surface.
Source code in pygx/symbolic/_dataclass_field.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | |
diff
¶
diff(
left: Any,
right: Any,
flatten: bool = False,
collapse: bool | str | Callable[[Any, Any], bool] = "same_type",
mode: str = "diff",
) -> Nestable[Diff]
Inspect the symbolic diff between two objects.
For example:
from typing import Any
import pygx as pg
class A(pg.Object):
x: Any
y: Any
class B(A):
z: Any = None
# Diff the same object.
print(pg.diff(A(x=1, y=2), A(x=1, y=2)))
# No diff
# Diff the same object with mode 'same'.
print(pg.diff(A(x=1, y=2), A(x=1, y=2), mode='same'))
# A(
# x = 1,
# y = 2
# )
# Diff different objects of the same type.
print(pg.diff(A(x=1, y=2), A(x=1, y=3)))
# A(
# y = Diff(
# left = 2,
# right = 3
# )
# )
# Diff objects of different type.
print(pg.diff(A(x=1, y=2), B(x=1, y=3)))
# Diff(
# left = A(
# x = 1,
# y = 2
# ),
# right = B(
# x = 1,
# y = 3,
# z = None
# )
# )
# Diff objects of different type with collapse.
print(pg.diff(A(x=1, y=2), B(x=1, y=3), collapse=True))
# A|B(
# y = Diff(
# left = 2,
# right = 3
# ),
# z = Diff(
# left = MISSING,
# right = None
# )
# )
# Diff objects of different type with collapse and flatten.
# The type difference is included under key '_type'.
d = pg.diff(
A(x=1, y=pg.Dict(a=1)), B(x=1, y=pg.Dict(a=2)),
collapse=True, flatten=True)
assert list(d) == ['y.a', 'z', '_type']
assert d['y.a'] == pg.Diff(1, 2)
assert d['z'] == pg.Diff(pg.Diff.MISSING, None)
assert d['_type'] == pg.Diff(A, B)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
Any
|
The left object to compare. |
required |
right
|
Any
|
The right object to compare. |
required |
flatten
|
bool
|
If True, returns a level-1 dict with diff keys flattened. Otherwise preserve the hierarchy of the diff result. |
False
|
collapse
|
bool | str | Callable[[Any, Any], bool]
|
One of a boolean value, string or a callable object that indicates whether to collapse two different values. The default value 'same_type' means only collapse when the two values are of the same type. |
'same_type'
|
mode
|
str
|
Diff mode, should be one of ['diff', 'same', 'both']. For 'diff' mode (the default), the return value contains only different values. For 'same' mode, the return value contains only same values. For 'both', the return value contains both different and same values. |
'diff'
|
Returns:
| Type | Description |
|---|---|
Nestable[Diff]
|
A |
Nestable[Diff]
|
to |
Source code in pygx/symbolic/_diff.py
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 | |
allow_empty_field_description
¶
Sets whether symbolic fields may be declared without a description.
Empty descriptions are allowed by default. Call with allow=False
to make class creation raise ValueError for any field that lacks a
description — useful for enforcing documented schemas in a codebase.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allow
|
bool
|
If True (the process default), fields may omit descriptions; if False, every field must carry one. |
True
|
Source code in pygx/symbolic/_flags.py
allow_partial
¶
Returns a context manager that allows partial values in scope.
This function is thread-safe and can be nested. In the nested use case, the allow flag of the innermost scope takes effect.
Example:
class A(pg.Object):
x: int
y: int
with pg.allow_partial(True):
a = A(x=1) # Missing `y`, but OK
with pg.allow_partial(False):
a.sym_rebind(x=pg.MISSING_VALUE) # NOT OK
a.sym_rebind(x=pg.MISSING_VALUE) # OK
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allow
|
bool | None
|
If True, allow partial symbolic values in scope.
If False, do not allow partial symbolic values in scope even if
individual objects allow so. If None, honor object-level
|
True
|
Returns:
| Type | Description |
|---|---|
ContextManager[None]
|
A context manager that allows/disallow partial symbolic values in scope.
After leaving the scope, the |
Source code in pygx/symbolic/_flags.py
allow_repeated_class_registration
¶
Sets whether a serialization type name may be registered twice.
Symbolic classes are registered under their type name (e.g.
'my_module.MyClass') for deserialization. This flag governs explicit
register_for_deserialization calls — including those made by
symbolizers such as pg.wrap and pg.functor — for a type name that is
already taken. Allowed by default: the newer class silently replaces the
older entry. Call with allow=False to make such a registration raise
KeyError, catching accidental type-name collisions. Auto-registration
at class-creation time always replaces the previous entry (so reloading a
module never raises), regardless of this flag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allow
|
bool
|
If True (the process default), re-registering a type name
replaces the previous class; if False, it raises |
True
|
Source code in pygx/symbolic/_flags.py
allow_writable_accessors
¶
Returns a context manager that makes accessors writable in scope.
This function is thread-safe and can be nested. In the nested use case, the writable flag of the innermost scope takes effect.
Example:
sd1 = pg.Dict()
sd2 = pg.Dict(accessor_writable=False)
with pg.allow_writable_accessors(False):
sd1.a = 2 # NOT OK
sd2.a = 2 # NOT OK
with pg.allow_writable_accessors(True):
sd1.a = 2 # OK
sd2.a = 2 # OK
with pg.allow_writable_accessors(None):
sd1.a = 1 # OK
sd2.a = 1 # NOT OK
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
writable
|
bool | None
|
If True, allow write access with accessors (setattr,
setitem) for all symbolic values in scope.
If False, disallow write access via accessors for all symbolic values
in scope, even if individual objects allow so.
If None, honor object-level |
True
|
Returns:
| Type | Description |
|---|---|
ContextManager[None]
|
A context manager that allows/disallows writable accessors of all
symbolic values in scope. After leaving the scope, the
|
Source code in pygx/symbolic/_flags.py
as_sealed
¶
Returns a context manager to treat symbolic values as sealed/unsealed.
While Symbolic.sym_seal seals or unseals an individual object, this
context manager is useful to create a readonly zone for operations on
all existing symbolic objects.
This function is thread-safe and can be nested. In the nested use case, the sealed flag of the innermost scope takes effect.
Example:
sd1 = pg.Dict()
sd2 = pg.Dict().sym_seal()
with pg.as_sealed(True):
sd1.a = 2 # NOT OK
sd2.a = 2 # NOT OK
with pg.as_sealed(False):
sd1.a = 2 # OK
sd2.a = 2 # OK
with pg.as_sealed(None):
sd1.a = 1 # OK
sd2.a = 1 # NOT OK
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sealed
|
bool | None
|
If True, treats all symbolic values as sealed in scope.
If False, treats all as unsealed.
If None, honor object-level |
True
|
Returns:
| Type | Description |
|---|---|
ContextManager[None]
|
A context manager that treats all symbolic values as sealed/unsealed in scope. After leaving the scope, the sealed state of individual objects will remain intact. |
Source code in pygx/symbolic/_flags.py
auto_call_functors
¶
Returns a context manager to enable or disable auto call for functors.
auto_call_functors is thread-safe and can be nested. For example:
@pg.symbolize
def foo(x, y):
return x + y
with pg.auto_call_functors(True):
a = foo(1, 2)
assert a == 3
with pg.auto_call_functors(False):
b = foo(1, 2)
assert isinstance(b, foo)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
If True, enable auto call for functors. Otherwise, auto call will be disabled. |
True
|
Returns:
| Type | Description |
|---|---|
ContextManager[None]
|
A context manager for enabling/disabling auto call for functors. |
Source code in pygx/symbolic/_flags.py
enable_type_debug
¶
Toggle inclusion of pygx-internal frames in type-error tracebacks.
When disabled (the default), tracebacks for type-related errors raised
during pg.Object construction (and similar entry points) are
rewritten to drop frames that live inside the pygx package, leaving
only the user's call sites visible. When enabled, the full traceback
(including every pygx-internal frame) is preserved — useful when
debugging the typing machinery itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
If True, keep pygx-internal frames; if False, hide them. |
True
|
Source code in pygx/symbolic/_flags.py
get_load_handler
¶
get_origin_stacktrace_limit
¶
get_save_handler
¶
is_change_notification_enabled
¶
is_empty_field_description_allowed
¶
is_repeated_class_registration_allowed
¶
is_tracking_origin
¶
is_type_debug_enabled
¶
is_under_accessor_writable_scope
¶
Returns the scoped accessor-writability override for current thread.
True/False if an enclosing allow_writable_accessors scope forces
accessor writes on/off; None if no scope is active (object-level
flags apply).
Source code in pygx/symbolic/_flags.py
is_under_partial_scope
¶
Returns the scoped allow-partial override for current thread.
True/False if an enclosing allow_partial scope allows/disallows
partial values; None if no scope is active (object-level
allow_partial applies).
Source code in pygx/symbolic/_flags.py
is_under_sealed_scope
¶
Returns the scoped sealed-state override for current thread.
True/False if an enclosing as_sealed scope treats all symbolic values
as sealed/unsealed; None if no scope is active (object-level sealed
state applies).
Source code in pygx/symbolic/_flags.py
is_warn_on_capability_narrowing_enabled
¶
is_warn_on_field_override_no_annotation_enabled
¶
Returns True if bare-assignment field-override warnings are emitted.
notify_on_change
¶
Returns a context manager to enable or disable notification upon change.
notify_on_change is thread-safe and can be nested. For example, in the
following code, on_sym_change (thus on_sym_bound) method of a will be
triggered due to the rebind in the inner with statement, and those of b
will not be triggered as the outer with statement disables the
notification:
with pg.notify_on_change(False):
with pg.notify_on_change(True):
a.sym_rebind(x=1)
b.sym_rebind(y=2)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
If True, enable change notification in current scope. Otherwise, disable notification. |
True
|
Returns:
| Type | Description |
|---|---|
ContextManager[None]
|
A context manager for allowing/disallowing change notification in scope. |
Source code in pygx/symbolic/_flags.py
notify_once
¶
Returns a context manager that coalesces change notifications.
Within the scope, every object touched by sym_rebind (or accessor writes
such as obj.x = ...) receives its on_sym_change event when the
scope exits, instead of once per mutation. Repeated updates to the same
field collapse into a single FieldUpdate that keeps the original
old_value and the final new_value. This is useful when applying a
batch of related edits that should be observed as one change.
For example, x and y below are each notified a single time:
with pg.notify_once():
x.sym_rebind(a=2, c=3)
y.sym_rebind(p=1)
x.sym_rebind(a=1)
# x.on_sym_change fires once (with a: <orig>->1, c: <orig>->3);
# y.on_sym_change fires once (with p: <orig>->1).
Nesting. notify_once is thread-safe and nestable. Each object is
flushed by the scope it was rebound in — the scope where sym_rebind (or
an accessor write) was called on that object. So a helper that wraps its
own edits in notify_once always observes them settled when it returns,
regardless of how it was called:
def configure(x):
with pg.notify_once():
x.sym_rebind(a=1)
x.sym_rebind(b=2)
return x # `x` is fully notified here, even if called within a scope
with pg.notify_once():
configure(x) # `x` flushed when `configure` returns
y = Y(p=x.derived) # reads `x`'s settled state
y.sym_rebind(q=1)
# `y` flushed here
A parent reached only by walking up from a rebound child is not owned by the inner scope; it is notified by whichever scope rebinds the parent, or by the outermost scope:
with pg.notify_once():
p.sym_rebind(w=1) # `p` rebound here -> owned by this scope
with pg.notify_once():
p.child.sym_rebind(v=2) # `p.child` rebound here -> owned by inner
# inner exit: `p.child` is notified; `p` is not (it is the outer's)
# outer exit: `p` is notified once (with w and the child change)
Children are therefore always notified before their parents, and an object is notified once per scope that directly rebinds it (each event reports a correct, coalesced transition — no event ever carries a stale value).
Coalescing nested scopes. Pass coalesce_nested=True to absorb any
scopes nested inside this one: they no longer flush on their own exit, and
every accumulated change is flushed once when this scope exits. Use it to
coalesce edits made by nested scopes, e.g. in a loop:
with pg.notify_once(coalesce_nested=True):
for item in items:
with pg.notify_once(): # absorbed; does not flush here
y.children.append(item)
# `y` is notified once here, for all appended children.
Deferred side effects. Because notifications are deferred to scope
exit, side effects driven by on_sym_change (e.g.
pg.Object.on_sym_bound recomputation, or pg.List removal of deleted
items) are also deferred. Caches derived from content (including
functools.cached_property) are still invalidated eagerly, so most reads
within the scope stay consistent; only state recomputed as a side effect of
on_sym_change lags until the flush. When change notification is disabled
via pygx.notify_on_change(False),
there is nothing to batch and notify_once has no effect.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coalesce_nested
|
bool
|
If True, |
False
|
Yields:
| Type | Description |
|---|---|
None
|
None, within the coalescing scope. |
Source code in pygx/symbolic/_flags.py
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 | |
set_load_handler
¶
Sets global load handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
load_handler
|
Callable[..., Any] | None
|
A callable object that takes arbitrary arguments and returns
a value. |
required |
Returns:
| Type | Description |
|---|---|
Callable[..., Any] | None
|
Previous global load handler. |
Source code in pygx/symbolic/_flags.py
set_origin_stacktrace_limit
¶
Sets the maximum number of stack frames captured for origin tracking.
Applies to the stack trace an pygx.Origin
records when origin tracking is enabled (see
pygx.track_origin). The process default
is 10 frames.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
limit
|
int
|
Maximum stack depth to capture. |
required |
Source code in pygx/symbolic/_flags.py
set_save_handler
¶
Sets global save handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
save_handler
|
Callable[..., Any] | None
|
A callable object that takes at least one argument as value to
save. |
required |
Returns:
| Type | Description |
|---|---|
Callable[..., Any] | None
|
Previous global save handler. |
Source code in pygx/symbolic/_flags.py
should_call_functors_during_init
¶
Returns the scoped auto-call-functors override for current thread.
True/False if an enclosing auto_call_functors scope enables/disables
auto call during __init__; None if no scope is active.
Source code in pygx/symbolic/_flags.py
track_origin
¶
Returns a context manager to enable or disable origin tracking.
track_origin is thread-safe and can be nested. For example:
a = pg.Dict(x=1)
with pg.track_origin(False):
with pg.track_origin(True):
# b's origin will be tracked, which can be accessed by `b.sym_origin`.
b = a.sym_clone()
# c's origin will not be tracked, `c.sym_origin` returns None.
c = a.sym_clone()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
If True, the origin of symbolic values will be tracked during object creation, cloning and returning from functors under current scope. |
True
|
Returns:
| Type | Description |
|---|---|
ContextManager[None]
|
A context manager for enabling or disabling origin tracking. |
Source code in pygx/symbolic/_flags.py
warn_on_capability_narrowing
¶
Toggle the warning emitted when a subclass narrows a class capability.
A subclass that explicitly removes a class-level capability its base
granted — attr_read / attr_write / topo True → False, or
frozen False → True — breaks substitutability: code that uses an
instance through its base type may then fail (e.g. reading obj.x, or
calling sym_rebind). When enabled (the default), pygx emits a one-shot
UserWarning at class creation. See the flag-combination matrix in
docs/guide/semantics.md for the rationale.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
If True, emit the warning; if False, silence it. |
True
|
Source code in pygx/symbolic/_flags.py
warn_on_field_override_no_annotation
¶
Toggle the warning emitted for bare-assignment field overrides.
A subclass that shadows an inherited pg.Object field via bare class-level
assignment (e.g. sender = 'User' instead of sender: str = 'User') is
handled correctly at runtime, but pyright/PEP 681 dataclass_transform
synthesizes __init__ from class-level annotations only — the override
is invisible to static type checkers. When enabled (the default), pygx emits
a one-shot UserWarning at class creation pointing at the offending name
and suggesting the re-annotated form.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
If True, emit the warning; if False, silence it. |
True
|
Source code in pygx/symbolic/_flags.py
as_functor
¶
as_functor(func: Callable, ignore_extra_args: bool = False) -> Functor
Makes a functor object from a regular Python function.
This function is designed for creating one-off functor objects, usually
from lambdas. To create a reusable functor class, use
pygx.functor_class instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
Callable
|
A regular Python function. |
required |
ignore_extra_args
|
bool
|
If True, extra arguments that are not acceptable by
|
False
|
Returns:
| Type | Description |
|---|---|
Functor
|
A functor object (an instance of an ad-hoc |
Functor
|
calls |
Source code in pygx/symbolic/_functor.py
functor
¶
functor(args: Callable[..., Any]) -> type[Functor]
functor(
args: (
None
| list[
tuple[str | KeySpec, ValueSpec, str]
| tuple[str | KeySpec, ValueSpec, str, Any]
]
| Callable[..., Any]
) = None,
returns: ValueSpec | None = None,
base_class: type[Functor] | None = None,
**kwargs: Any
) -> Callable[..., type[Functor]] | type[Functor]
Function/Decorator for creating a symbolic function from a function.
Used bare (@pg.functor), it converts the decorated function into a
pygx.Functor subclass directly. Called with
arguments (@pg.functor([...])), it returns a decorator configured
with the given argument schema.
Example:
# Create a symbolic function without specifying the
# validation rules for arguments.
@pg.functor
def foo(x, y):
return x + y
f = foo(1, 2)
assert f() == 3
With validation rules for arguments 'a', 'b', 'args' and other keyword arguments:
@pg.functor([
('a', pg.typing.Int()),
('b', pg.typing.Float()),
('args', pg.typing.List(pg.typing.Int())),
(pg.typing.StrKey(), pg.typing.Int())
])
def bar(a, b, c, *args, **kwargs):
return a * b / c + sum(args) + sum(kwargs.values())
See pygx.Functor for more details on symbolic
functions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
args
|
None | list[tuple[str | KeySpec, ValueSpec, str] | tuple[str | KeySpec, ValueSpec, str, Any]] | Callable[..., Any]
|
The function to decorate (bare-decorator form), or a list of
tuples that defines the schema for function arguments. Please see
|
None
|
returns
|
ValueSpec | None
|
Optional value spec for the return value. |
None
|
base_class
|
type[Functor] | None
|
Optional base class derived from |
None
|
**kwargs
|
Any
|
Keyword arguments forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
Callable[..., type[Functor]] | type[Functor]
|
A |
Callable[..., type[Functor]] | type[Functor]
|
converts a function into a |
Source code in pygx/symbolic/_functor.py
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 | |
functor_class
¶
functor_class(
func: FunctionType,
args: (
list[Field | FieldDef] | dict[FieldKeyDef, FieldValueDef] | None
) = None,
returns: ValueSpec | None = None,
base_class: type[Functor] | None = None,
*,
auto_doc: bool = False,
auto_typing: bool = False,
serialization_key: str | None = None,
additional_keys: list[str] | None = None,
add_to_registry: bool = False,
topo: bool | None = None
) -> type[Functor]
Returns a functor class from a function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
FunctionType
|
Function to be wrapped into a functor. |
required |
args
|
list[Field | FieldDef] | dict[FieldKeyDef, FieldValueDef] | None
|
Symbolic args specification. |
None
|
returns
|
ValueSpec | None
|
Optional schema specification for the return value. |
None
|
base_class
|
type[Functor] | None
|
Optional base class (derived from |
None
|
auto_doc
|
bool
|
If True, the descriptions of argument fields will be inherited
from the function docstring if they are not explicitly specified
through |
False
|
auto_typing
|
bool
|
If True, the value spec for constraining each argument will be
inferred from its annotation. Otherwise the value specs for all arguments
will be |
False
|
serialization_key
|
str | None
|
An optional string to be used as the serialization key
for the class during |
None
|
additional_keys
|
list[str] | None
|
An optional list of strings as additional keys to deserialize an object of the registered class. This can be useful when we need to relocate or rename the registered class while being able to load existing serialized JSON values. |
None
|
add_to_registry
|
bool
|
If True, the newly created functor class will be added to the registry for deserialization. |
False
|
topo
|
bool | None
|
Whether the functor joins the symbolic tree. |
None
|
Returns:
| Type | Description |
|---|---|
type[Functor]
|
|
Raises:
| Type | Description |
|---|---|
KeyError
|
names of symbolic arguments are not compatible with function signature. |
TypeError
|
types of symbolic arguments are not compatible with function signature. |
ValueError
|
default values of symbolic arguments are not compatible with function signature. |
Source code in pygx/symbolic/_functor.py
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 | |
default_load_handler
¶
default_load_handler(
path: str, file_format: str | WireFormat = "json", **kwargs: Any
) -> Any
Default load handler from file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
File path to read. |
required |
file_format
|
str | WireFormat
|
The literal |
'json'
|
**kwargs
|
Any
|
Forwarded to |
{}
|
Source code in pygx/symbolic/_io.py
default_save_handler
¶
default_save_handler(
value: Any,
path: str,
*,
indent: int | None = None,
file_format: str | WireFormat = "json",
**kwargs: Any
) -> None
Default save handler to file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Value to save. |
required |
path
|
str
|
File path to write. |
required |
indent
|
int | None
|
Indentation passed to the format's encoder. |
None
|
file_format
|
str | WireFormat
|
The literal |
'json'
|
**kwargs
|
Any
|
Forwarded to |
{}
|
Source code in pygx/symbolic/_io.py
load
¶
Load a symbolic value using the global load handler.
Example:
class A(pg.Object):
x: int
a1 = A(x=1)
file = 'my_file.json'
a1.save(file)
a2 = pg.load(file)
assert pg.eq(a1, a2)
# Narrow the static return type and assert at runtime:
a3 = pg.load(file, expected_type=A)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
A path string for loading an object. |
required |
*args
|
Any
|
Positional arguments that will be passed through to the global load handler. |
()
|
expected_type
|
Any
|
When provided (anything other than the default |
Any
|
**kwargs
|
Any
|
Keyword arguments that will be passed through to the global load handler. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
Return value from the global load handler. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
Source code in pygx/symbolic/_io.py
open_jsonl
¶
open_jsonl(path: str, mode: str = 'r', **kwargs: Any) -> Sequence
Open a JSONL file for reading or writing.
Example:
with pg.open_jsonl('my_file.jsonl', 'w') as f:
f.add(1)
f.add('foo')
f.add(dict(x=1))
with pg.open_jsonl('my_file.jsonl', 'r') as f:
for value in f:
print(value)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
The path to the file. |
required |
mode
|
str
|
The mode of the file. |
'r'
|
**kwargs
|
Any
|
Additional keyword arguments that will be passed to
|
{}
|
Returns:
| Type | Description |
|---|---|
Sequence
|
A sequence for PyGX objects. |
Source code in pygx/symbolic/_io.py
save
¶
Save a symbolic value using the global save handler.
Example:
class A(pg.Object):
x: int
a1 = A(x=1)
file = 'my_file.json'
a1.save(file)
a2 = pg.load(file)
assert pg.eq(a1, a2)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
value to save. |
required |
path
|
str
|
A path string for saving |
required |
*args
|
Any
|
Positional arguments that will be passed through to the global save handler. |
()
|
**kwargs
|
Any
|
Keyword arguments that will be passed through to the global save handler. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
Return value from the global save handler. |
Source code in pygx/symbolic/_io.py
mark_as_insertion
¶
mark_as_insertion(value: Any) -> Insertion
Marks a value as an insertion into a List.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Value to insert. |
required |
Returns:
| Type | Description |
|---|---|
Insertion
|
An |
members
¶
members(
fields: list[Field | FieldDef] | dict[FieldKeyDef, FieldValueDef],
metadata: dict[str, Any] | None = None,
serialization_key: str | None = None,
additional_keys: list[str] | None = None,
add_to_registry: bool = True,
) -> Decorator
Function/Decorator for declaring symbolic fields for pg.Object.
Example:
@pg.members([
# Declare symbolic fields. Each field produces a symbolic attribute
# for its object, which can be accessed by `self.<field_name>`.
# Description is optional.
('x', pg.typing.Int(min_value=0, default=0), 'Description for `x`.'),
('y', pg.typing.Float(), 'Description for `y`.')
])
class A(pg.Object):
def sum(self):
return self.x + self.y
@pg.members([
# Override field 'x' inherited from class A and make it more restrictive.
('x', pg.typing.Int(max_value=10, default=5)),
# Add field 'z'.
('z', pg.typing.Bool().noneable())
])
class B(A):
pass
@pg.members([
# Declare dynamic fields: any keyword can be acceptable during `__init__`
# and can be accessed using `self.<field_name>`.
(pg.typing.StrKey(), pg.typing.Int())
])
class D(B):
pass
@pg.members([
# Declare dynamic fields: keywords starting with 'foo' are acceptable.
(pg.typing.StrKey('foo.*'), pg.typing.Int())
])
class E(pg.Object):
pass
See pygx.typing.ValueSpec for supported value specifications.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fields
|
list[Field | FieldDef] | dict[FieldKeyDef, FieldValueDef]
|
A list of pg.typing.Field or equivalent tuple representation as
( |
required |
metadata
|
dict[str, Any] | None
|
Optional dict of user objects as class-level metadata which will be attached to class schema. |
None
|
serialization_key
|
str | None
|
An optional string to be used as the
serialization key for the class during |
None
|
additional_keys
|
list[str] | None
|
An optional list of strings as additional keys to deserialize an object of the registered class. This can be useful when we need to relocate or rename the registered class while being able to load existing serialized JSON values. |
None
|
add_to_registry
|
bool
|
If True, register serialization keys and additional keys with the class. |
True
|
Returns:
| Type | Description |
|---|---|
Decorator
|
A decorator function that registers the class with a schema created from the fields. |
Raises:
| Type | Description |
|---|---|
TypeError
|
Decorator cannot be applied on target class or keyword argument provided is not supported. |
KeyError
|
If type has already been registered in the registry. |
ValueError
|
schema cannot be created from fields. |
Source code in pygx/symbolic/_object.py
4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 | |
deref
¶
deref(value: Symbolic, recursive: bool = False) -> Any
Dereferences a symbolic value that may contain pg.Ref.
With recursive=True, every pg.Ref in the tree is replaced (via
rebind, in place) with its referenced value, which the tree then adopts
as a regular child.
Example:
inner = pg.Dict(y=1)
a = pg.Dict(x=pg.Ref(inner))
assert isinstance(a.sym_getattr('x'), pg.Ref)
b = pg.symbolic.deref(a, recursive=True)
assert b is a # Rebinds in place.
assert not isinstance(a.sym_getattr('x'), pg.Ref)
assert a.x is inner
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Symbolic
|
The input symbolic value. |
required |
recursive
|
bool
|
If True, dereference |
False
|
Returns:
| Type | Description |
|---|---|
Any
|
The dereferenced root, or dereferenced tree if recursive is True. |
Source code in pygx/symbolic/_ref.py
maybe_ref
¶
Returns value as-is if it is free to adopt, else a pg.Ref to it.
The share counterpart of pg.maybe_clone.
Both answer the same question — this value may already belong to a
tree, and I am about to hold it — and differ in the resolution they
pick: maybe_ref SHARES one node between the holders (writes through
either are visible through the other), while maybe_clone gives the
new holder its own independent copy.
A parentless symbolic value passes through unchanged — the caller's
hold simply adopts it. An already-held symbolic value — and any raw
list / dict, which adoption would otherwise convert into a
pg.List / pg.Dict — comes back wrapped in a pg.Ref, so the
holder points at it without taking it. Primitives pass through
unchanged.
Example:
import pygx as pg
a = pg.Dict(x=1)
h1 = pg.Dict(v=pg.maybe_ref(a)) # `a` is free: adopted as-is.
h2 = pg.Dict(v=pg.maybe_ref(a)) # `a` is now held: shared via `pg.Ref`.
assert h1.v is a
assert h2.v is a
Source code in pygx/symbolic/_ref.py
symbolize
¶
Make a symbolic class/function out of a regular Python class/function.
pg.symbolize is introduced for the purpose of making existing
classes/functions symbolically programmable. For use cases that build
symbolic classes from scratch (native PyGX classes), extending pg.Object
with dataclass-like class annotations that declare the symbolic properties
is the recommended way, which automatically generates the __init__
method and allows symbolic attributes to be accessed via self.<member>.
pg.symbolize can be invoked as a class/function decorator, or as a
function. When it is used as a decorator, the decorated class or function
will be converted to a symbolic type (via pygx.wrap and
pygx.functor_class). This is preferred when user can modify the
files of existing classes/functions. For example:
@pg.symbolize
def foo(a, b):
return a + b
f = foo(1, 2)
f.sym_rebind(a=2)
f() # Returns 4
@pg.symbolize([
# (Optional) add symbolic constraint for __init__ argument 'a'.
('a', pg.typing.Int(min_value=0), 'Description for `a`.')
])
class Foo:
def __init__(self, a, b):
self._a = a
self._b = b
def result(self):
return self._a + self._b
f = Foo(1, 2)
f.sym_rebind(a=2, b=3)
f.result() # Returns 5
When it is used as a function, the input class or function is not modified. Instead, a new symbolic type will be created and returned. This is helpful when users want to create new symbolic types from existing classes/functions without modifying their original source code. For example:
def foo(a, b):
return a + b
# Create a new symbolic type with constraint on 'a'.
symbolic_foo = pg.symbolize(foo, [
('a', pg.typing.Int(min_value=0))
], returns=pg.typing.Int())
foo(1, 2) # Returns 3 (foo is kept intact).
f = symbolic_foo(1, 2)
f.sym_rebind(a=2)
f() # Returns 4.
class Foo:
def __init__(self, a, b):
self._a = a
self._b = b
def result(self):
return self._a + self._b
SymbolicFoo = pg.symbolize(Foo)
f = SymbolicFoo(2, 2)
f.sym_rebind(a=3)
f.result() # Returns 5.
As special cases, pg.symbolize(dict) returns pg.Dict and
pg.symbolize(list) returns pg.List; constraints are not supported
for them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
Any
|
The positional arguments for
Where |
()
|
**kwargs
|
Any
|
Keyword arguments will be passed through to
|
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
A Symbolic subclass for the decorated/input type. |
Raises:
| Type | Description |
|---|---|
TypeError
|
input type cannot be symbolized, or it's not a type. |
Source code in pygx/symbolic/_symbolize.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | |
from_uri
¶
from_uri(
uri: str,
class_resolver: ClassSource[T],
*,
case_sensitive: bool = True,
allow_scheme_names: bool = False
) -> T
Build a symbolic object from a URI-like string.
Two equivalent top-level forms are accepted:
- URI form:
<ClassName>?<arg1>=<v1>&<arg2>=<v2> - Call form:
<ClassName>(<arg1>=<v1>, <arg2>=<v2>)
Use whichever reads better at the call site. The forms can also be
mixed: nested values inside a URI-form call always use call form, since
& is the URI's arg separator (see Special characters below).
Positional arguments are accepted (mapped to the class's
init_arg_list) and may appear before any keyword arguments.
Suppose we have:
class Optimizer(pg.Object):
lr: float
momentum: float = 0.0
class Schedule(pg.Object):
name: str
steps: list[int] = []
class Trainer(pg.Object):
optimizer: Optimizer
epochs: int = 10
schedules: list[Schedule] = []
tags: dict[str, str] = {}
notes: str | None = None
The class_resolver argument can be a dict, a list, or a callable:
# 1. Mapping: explicit name -> class lookup.
registry = {
'Optimizer': Optimizer,
'Schedule': Schedule,
'Trainer': Trainer,
}
# 2. Sequence: each class is keyed by ``cls.__name__``.
registry = [Optimizer, Schedule, Trainer]
# 3. Callable: resolve a name on demand (e.g. import lazily).
def registry(name):
return {'Optimizer': Optimizer, 'Trainer': Trainer}.get(name)
All three are interchangeable in the examples below.
Primitives — ints, floats, bools (True/False, yes/no),
None, and strings (quoted or unquoted):
pg.from_uri('Optimizer?lr=0.1', registry)
# Optimizer(lr=0.1, momentum=0.0)
pg.from_uri('Optimizer?lr=0.1&momentum=0.9', registry)
# Optimizer(lr=0.1, momentum=0.9)
pg.from_uri('Schedule?name=cosine', registry)
pg.from_uri('Schedule?name="cosine annealing"', registry)
# Schedule(name='cosine annealing', steps=[])
Positional arguments map through the class's init_arg_list:
pg.from_uri('Optimizer?0.1&0.9', registry)
pg.from_uri('Optimizer(0.1, 0.9)', registry)
# Optimizer(lr=0.1, momentum=0.9)
Lists use [...]; dicts use {...} with : or =:
pg.from_uri('Schedule?name=warmup&steps=[1, 2, 3]', registry)
# Schedule(name='warmup', steps=[1, 2, 3])
pg.from_uri('Trainer?optimizer=Optimizer(lr=0.1)&'
'tags={env: "prod", team: "ml"}', registry)
# Trainer(optimizer=Optimizer(lr=0.1), tags={'env': 'prod', 'team': 'ml'})
Nested objects are spelled with the call form anywhere a value is expected; arbitrary depth is supported:
pg.from_uri(
'Trainer?epochs=5&optimizer=Optimizer(lr=0.1, momentum=0.9)&'
'schedules=[Schedule(name="warmup", steps=[1, 2]),'
' Schedule(name="cosine", steps=[10, 20])]',
registry,
)
Equivalently, in call form:
pg.from_uri(
'Trainer(epochs=5,'
' optimizer=Optimizer(lr=0.1, momentum=0.9),'
' schedules=[Schedule(name="warmup", steps=[1, 2])])',
registry,
)
None and noneable fields:
Case-insensitive matching for both class and field names:
pg.from_uri('TRAINER?optimizer=OPTIMIZER(LR=0.1, MoMentum=0.9)',
registry, case_sensitive=False)
# Trainer(optimizer=Optimizer(lr=0.1, momentum=0.9))
Special characters in string values:
- Inside double or single quotes, content is preserved verbatim —
spaces, real newlines, parens, commas, and
&are all fine:
pg.from_uri('Schedule(name="my favorite schedule")', registry)
pg.from_uri('Schedule(name="line1\nline2")', registry)
# Note: ``\n`` here is a real newline in the Python source. The
# parser does NOT interpret escape sequences — only ``\"`` (an
# escaped quote) and ``\\`` (an escaped backslash) are useful.
# To embed a newline, pass a real newline character.
- Unquoted top-level URI-form values may contain spaces (the URI form
splits only on
&):
However, inside any structured context ([...], {...},
Name(...)), whitespace is a token separator — quote any value
that contains spaces.
&is the URI form's arg separator and is not quote-aware. If a value contains&, use the call form (or wrap the value in[...]/Name(...)so the surrounding context isn't URI-style):
# Doesn't work — URI form splits on ``&`` before quotes are seen:
pg.from_uri('Schedule?name="a&b"', registry) # ValueError
# Works — call form splits on ``,``:
pg.from_uri('Schedule(name="a&b")', registry)
# Schedule(name='a&b', steps=[])
Scheme-style names for registry-style factories — pass
allow_scheme_names=True and a callable resolver:
def resolve(model_id):
factory = lookup(model_id) # exact + regex registry, etc.
def instantiate(**kw):
return factory(model=model_id, **kw)
return instantiate
pg.from_uri('mock://test?temperature=0.1', resolve,
allow_scheme_names=True)
pg.from_uri('claude-opus-4-6@latest(temperature=0.1)', resolve,
allow_scheme_names=True)
The leading name extends to the first ? or ( and is passed
verbatim to the resolver. When the resolver returns a plain callable
(no __schema__), value-spec validation is skipped — kwargs are
forwarded with best-effort coercion so the callable can accept
**kwargs for fields it routes itself.
Note
pg.patching.from_uri is a different function that resolves a URI
to a registered pygx.patching.Patcher; this
function builds a symbolic object instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
uri
|
str
|
URI-like string. Either |
required |
class_resolver
|
ClassSource[T]
|
Source used to resolve both the top-level name and
any nested object calls. The return type
|
required |
case_sensitive
|
bool
|
When |
True
|
allow_scheme_names
|
bool
|
When The resolver may return either a Only the top-level name is affected; nested |
False
|
Returns:
| Type | Description |
|---|---|
T
|
A new instance of |
T
|
constructed from the parsed arguments. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If the top-level class name is not resolvable via the resolver. |
ValueError
|
If the URI cannot be parsed or violates the class schema. |
Source code in pygx/symbolic/_uri.py
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 | |
parse_uri_value
¶
parse_uri_value(
raw: str,
value_spec: ValueSpec | None = None,
class_resolver: ClassSource[Any] | _ClassResolver | None = None,
*,
source_kind: str = "Source",
source_id: str = "",
arg_name: str = "",
case_sensitive: bool = True
) -> Any
Parse one value string into a typed Python value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
raw
|
str
|
The raw value string. |
required |
value_spec
|
ValueSpec | None
|
If provided, the parsed value is validated/coerced through
|
None
|
class_resolver
|
ClassSource[Any] | _ClassResolver | None
|
Optional source for resolving nested object calls
|
None
|
source_kind
|
str
|
Label used in error messages (e.g. |
'Source'
|
source_id
|
str
|
Identifier (patcher/class name) used in error messages. |
''
|
arg_name
|
str
|
Argument name used in error messages. |
''
|
case_sensitive
|
bool
|
When |
True
|
Returns:
| Type | Description |
|---|---|
Any
|
The parsed value (after optional |
Raises:
| Type | Description |
|---|---|
ValueError
|
If parsing fails or the value violates |
Source code in pygx/symbolic/_uri.py
options: show_root_heading: false show_root_toc_entry: false members_order: source heading_level: 2