Records and data¶
Named, immutable containers for structured non-random data, plus the
batched (RecordArray) and parameter-sweep (Design) variants built on
top.
Field access is bracket-only: record["x"], array["x"]. Slash-delimited
strings index nested paths: record["params/intercept"].
Records¶
Record(_dict=None, /, *, name=None, **fields)
¶
Named, immutable, pytree-registered container for structured values.
Fields iterate in insertion order and are returned verbatim;
Record performs no coercion between backends (numpy, JAX, xarray,
Python scalars, strings, nested Records are all accepted). Use
NumericRecord when you want a uniform jax.Array leaf
type and flatten / unflatten support.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**fields
|
_FieldValue
|
Named values. Values may be JAX or numpy arrays, Python scalars,
strings, xarray / pandas objects, nested |
{}
|
name
|
str
|
Name for provenance / introspection. Auto-generated from field names if not provided. |
None
|
Source code in probpipe/core/record.py
name
property
¶
Name of this Record.
source
property
¶
Provenance describing how this Record was created, or None.
fields
property
¶
Field names in insertion order.
with_source(source)
¶
Attach provenance to this Record (write-once).
Mirrors Distribution.with_source — _source is set once and
subsequent calls raise. Semantic transformations (replace,
merge, without, map, map_with_names) return a
new Record with an empty source; the caller attaches fresh
provenance there if desired.
Notes
_source is runtime-only metadata — it is not serialised into
the JAX pytree aux (a Provenance parent is a Distribution
or Record, neither of which is hashable by structure).
Round-tripping through jax.tree_util.tree_flatten /
tree_unflatten therefore drops the source; re-attach it on
the reconstructed Record if you need to preserve the chain.
Source code in probpipe/core/record.py
items()
¶
keys()
¶
values()
¶
select(*fields, **mapping)
¶
Select fields as a dict, for splatting into function calls.
Positional args use the field name as the key (identity mapping).
Keyword args remap: select(x="field_name") → {"x": self.field_name}.
Usage::
predict(**params.select("r", "K"), x=x_grid)
predict(**params.select(growth_rate="r"), x=x_grid)
Source code in probpipe/core/record.py
select_all()
¶
Return every field as a dict, for splatting into function calls.
Sugar for select(*self.fields). Subclasses whose
__getitem__ returns a view (RecordArray →
_RecordArrayView, RecordDistribution →
_RecordDistributionView) inherit this method and return
per-field views — so f(**ra.select_all()) triggers the
parent-identity zip sweep in WorkflowFunction, and
f(**dist.select_all()) similarly preserves cross-field
correlation.
Source code in probpipe/core/record.py
replace(**updates)
¶
Return a new Record with specified fields replaced.
Returns an instance of type(self) so that subclasses
(NumericRecord) preserve their class through the update.
Source code in probpipe/core/record.py
merge(other)
¶
Return a new Record combining fields from self and other.
Raises ValueError if any field names overlap. Returns an
instance of type(self).
Source code in probpipe/core/record.py
without(*names)
¶
Return a new Record with the specified fields removed.
Returns an instance of type(self).
Source code in probpipe/core/record.py
to_dict()
¶
Return a dict of stored values (recursive for nested Record).
Leaves are returned verbatim; no coercion to numpy or JAX.
Source code in probpipe/core/record.py
to_numpy()
¶
Return a dict of numpy arrays (recursive for nested Record).
Each numeric leaf is converted via np.asarray. Non-numeric
leaves (strings, opaque objects) are returned as-is. Backend
metadata (xarray dims / coords, pandas index) is stripped — use
to_numeric followed by NumericRecord.to_native
if you need a metadata-preserving round-trip.
Source code in probpipe/core/record.py
to_numeric()
¶
Convert to a NumericRecord with every leaf a jax.Array.
Per-field metadata that jnp.asarray would drop (xarray
dims / coords / attrs, pandas index / columns / dtypes) is
captured via the aux registry in
probpipe.core._array_backend and stored on the resulting
NumericRecord. Calling NumericRecord.to_native
on the result reverses the conversion, restoring each leaf to
its original backend type. Nested Record children recurse
— every level becomes a NumericRecord.
Equivalent to NumericRecord.from_record.
Raises:
| Type | Description |
|---|---|
TypeError
|
If any leaf is not coercible via |
Source code in probpipe/core/record.py
ensure(x)
classmethod
¶
Coerce x to Record if it isn't already.
Record→ pass throughdict→Record(**x)- array-like →
Record(data=x)
Source code in probpipe/core/record.py
from_dict(d)
classmethod
¶
map(fn)
¶
Apply fn to each leaf, returning a new Record.
Nested Record objects are traversed and rebuilt with the same
class. fn sees leaves as stored (no coercion).
Source code in probpipe/core/record.py
map_with_names(fn)
¶
Apply fn(name, value) to each leaf, returning a new Record.
Source code in probpipe/core/record.py
NumericRecord(_dict=None, /, *, name=None, **fields)
¶
Bases: Record
Record where every leaf is a jax.Array.
Adds flatten / unflatten / flat_size for
serialising the record to / from a flat 1-D vector. Construction
validates that every leaf is a numeric value (or a nested
NumericRecord) and coerces scalar / numpy / xarray /
pandas leaves to jnp.ndarray so downstream code sees a uniform
JAX array type. Backend-specific metadata (xarray dims / coords /
attrs, pandas index / columns / dtypes) is captured via the aux
registry in probpipe.core._array_backend and stored on the
instance; to_native reverses the conversion.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**fields
|
ArrayLike | NumericRecord
|
Named values. Every leaf must be a numeric array ( |
{}
|
Notes
NumericRecord(**fields) and Record(**fields).to_numeric()
are semantically identical — both consult the aux registry to
capture metadata, both coerce leaves via jnp.asarray, both
raise TypeError on non-coercible leaves.
Validation and coercion happen before the underlying Record is
constructed, so _store is populated exactly once and remains
immutable from the moment __init__ returns — consistent with the
__slots__ + __setattr__ guard on the base class.
Source code in probpipe/core/_numeric_record.py
flat_size
property
¶
Total number of scalar elements across all numeric leaves.
aux
property
¶
Captured backend metadata blobs, keyed by field name (or None).
Each entry is the opaque aux_blob returned by the registered
capture hook for that field's original leaf type. Fields whose
leaf type wasn't in the registry (plain numpy / jax / Python
scalars) are absent.
The hook pair is intentionally not exposed here — call
to_native to materialise the original backend objects.
flatten()
¶
Concatenate all leaf arrays into a single 1-D vector.
Fields are traversed in insertion order; nested NumericRecord
are traversed depth-first. Each leaf is raveled before
concatenation.
Source code in probpipe/core/_numeric_record.py
unflatten(flat, *, template)
classmethod
¶
Reconstruct a NumericRecord from a flat array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
flat
|
array
|
1-D array of concatenated scalars. |
required |
template
|
RecordTemplate
|
Provides field names and shapes for reconstruction. |
required |
Source code in probpipe/core/_numeric_record.py
from_record(record)
classmethod
¶
Convert a Record to NumericRecord, validating leaves.
Equivalent to record.to_numeric(); both paths consult the
aux registry, coerce every leaf via jnp.asarray, and raise
TypeError on non-coercible leaves. Nested Record
children recurse, preserving structure.
Source code in probpipe/core/_numeric_record.py
to_native()
¶
Restore each leaf to its original backend type, returning a Record.
Fields whose original leaf type was registered in
probpipe.core._array_backend are restored via
hooks.restore(jax_array, aux). Fields without captured aux
pass through as their stored jax.Array. Nested
NumericRecord fields recurse.
The result is a permissive Record, not a
NumericRecord — restored xarray / pandas leaves are no
longer jax.Array and would fail the numeric invariant.
Source code in probpipe/core/_numeric_record.py
RecordTemplate(_dict=None, /, **field_specs)
¶
Structural description of a Record: field names, leaf shapes, nesting.
Stores the skeleton of a Record without data — field names, per-field
shapes (for numeric leaves) or None (for opaque leaves), and
optional nested RecordTemplate for hierarchical structure.
Inspired by JAX's PyTreeDef: a template can reconstruct a Record
from flat data, and describes the expected structure for type-checking
and flattening.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**field_specs
|
_FieldSpec
|
Named fields. Each value is one of:
|
{}
|
Examples:
::
RecordTemplate(x=(), y=(3,)) # -> NumericRecordTemplate
RecordTemplate(label=None, x=()) # -> RecordTemplate (mixed)
RecordTemplate(physics=RecordTemplate(force=(), mass=()), obs=())
Notes
Calling RecordTemplate(...) directly auto-promotes to a
NumericRecordTemplate when every spec is numeric (and
every nested sub-template is itself all-numeric). That keeps
flat_size and numeric_leaf_shapes reachable in the common
all-numeric case without requiring the caller to name the subclass.
Mixed templates (any None spec) stay as plain RecordTemplate
and do not expose flat_size — it isn't a meaningful quantity
once opaque leaves are in the mix.
Source code in probpipe/core/record.py
fields
property
¶
Field names in insertion order.
leaf_shapes
property
¶
Per-field leaf shapes. None for opaque (non-array) leaves.
For nested RecordTemplate fields, returns the nested
template's leaf_shapes (not the template itself), keyed by
/-delimited paths so the keys round-trip with
Record.__getitem__'s path syntax.
from_record(record, *, batch_shape=())
classmethod
¶
Infer a template from an existing Record.
Numeric leaves are recorded with their shape (after stripping the
leading batch_shape). Python numeric scalars are treated as
shape-() leaves. Non-numeric leaves (strings, opaque objects)
are recorded as None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
Record
|
Source record whose fields define the template structure. |
required |
batch_shape
|
tuple of int
|
Leading dimensions to strip from field shapes to get event
shapes. For a single-sample Record, use |
()
|
Notes
A Python list or tuple leaf has no .shape / .dtype
and is treated as opaque (None) even if it contains numbers.
Wrap it in np.asarray(...) or jnp.asarray(...) before
putting it in the Record if you want a numeric template entry.
Downstream operations that call NumericRecord.unflatten will
otherwise raise on the opaque field.
Source code in probpipe/core/record.py
NumericRecordTemplate(_dict=None, /, **field_specs)
¶
Bases: RecordTemplate
RecordTemplate where every leaf is numeric.
Extends RecordTemplate by requiring each spec to be a shape
tuple (or a nested NumericRecordTemplate) — no opaque
None leaves are allowed. That restriction is what makes
flat_size and numeric_leaf_shapes meaningful:
flat_size is the total number of scalar elements across every
numeric leaf, and the unflatten machinery (NumericRecord.unflatten
/ NumericRecordArray.unflatten) requires a template of this
class so that every field can be reconstructed from a slice of the
flat buffer.
Use RecordTemplate.from_record on a NumericRecord
(it auto-promotes) or call this constructor directly when you have
the shape specs in hand.
Source code in probpipe/core/record.py
numeric_leaf_shapes
property
¶
Per-field shapes for numeric leaves.
On NumericRecordTemplate every leaf is numeric, so this
is equivalent to leaf_shapes. Kept as a distinct name for
symmetry with historical callers that used it as a filter.
flat_size
property
¶
Total number of scalar elements across all numeric leaves.
Record arrays¶
RecordArray(_dict=None, /, *, batch_shape, template, name=None, **fields)
¶
Bases: Record
Batch of Records with consistent field structure.
Each field stores values with shape (*batch_shape, *leaf_shape).
A RecordArray is a Record — the batched variant,
parallel to the way DistributionArray is a
Distribution. Consolidating the two in a single hierarchy
means:
isinstance(x, Record)accepts both scalar and batched Records. Code that needs to distinguish usesisinstance(x, RecordArray)for the batched case, orisinstance(x, Record) and not isinstance(x, RecordArray)for scalar-only..source/.with_source/.nameare inherited from Record (stored on the_name/_sourceslots declared on Record).replace/merge/without/map/map_with_namesare overridden here because the base constructor signature doesn't carrybatch_shape/template; RecordArray versions preserve those.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch_shape
|
tuple of int
|
Shape of the batch dimensions. |
required |
template
|
RecordTemplate
|
Structural description of each element. |
required |
name
|
str
|
Human-readable name for provenance / introspection. Defaults
to |
None
|
**fields
|
Any
|
Named values, each with shape |
{}
|
Notes
Construct from a list of Records with RecordArray.stack.
Indexing is either integer (arr[i] → single Record) or
field name (arr["x"] → batched leaf array).
Source code in probpipe/core/_record_array.py
batch_shape
property
¶
Shape of the batch dimensions.
template
property
¶
Structural description of each element.
view(field)
¶
Return a single-field view carrying parent identity.
Unlike ra[field] (which returns the raw column), a view
remembers the parent RecordArray. When multiple views of
the same parent land in a single WorkflowFunction call,
the sweep layer groups them by parent identity and iterates
them in lockstep (zip) rather than cartesian-producting.
Used internally by select_all.
Direct construction by end-users is supported but rarely needed.
Source code in probpipe/core/_record_array.py
keys()
¶
values()
¶
items()
¶
select(*fields, **mapping)
¶
Select fields as a dict of single-field views.
Mirrors Record.select but each entry is a
_RecordArrayView rather than the raw column. The views
carry this RecordArray as their parent, so splatting the
result into a @workflow_function triggers the
parent-identity zip sweep (one inner call per row, matching
f(p=self)) instead of cartesian-producting the fields as
independent axes.
For raw-column access, use self["field"] per field or
iterate self.items().
Source code in probpipe/core/_record_array.py
stack(records, *, template=None)
classmethod
¶
Stack a list of Records into a RecordArray with batch_shape=(n,).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
records
|
list of Record
|
Records with consistent field structure. |
required |
template
|
RecordTemplate
|
If not provided, inferred from the first record. |
None
|
Notes
Any backend metadata captured on the source NumericRecord
instances (xarray dims / coords, pandas index) is dropped — the
stacked leaves are plain jax.Array objects. RecordArray
does not currently carry per-row aux.
Source code in probpipe/core/_record_array.py
NumericRecordArray(_dict=None, /, *, batch_shape, template, name=None, **fields)
¶
Bases: RecordArray
Batch of NumericRecords — all leaves are numeric arrays.
Adds flatten/unflatten, mean, var operations.
Construction validates that every leaf has a numeric dtype and
shape (*batch_shape, *event_shape) matching the template, so
pytree round-trips (jax.tree.map) cannot silently produce a
NumericRecordArray with non-numeric or ill-shaped leaves.
Each field has shape (*batch_shape, *event_shape).
Source code in probpipe/core/_record_array.py
flatten()
¶
Flatten event dimensions into a single trailing axis.
Returns array of shape (*batch_shape, flat_event_size).
Source code in probpipe/core/_record_array.py
unflatten(flat, *, template, batch_shape=None)
classmethod
¶
Reconstruct from a flat array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
flat
|
array
|
Shape |
required |
template
|
RecordTemplate
|
Structural description providing field names and event shapes. |
required |
batch_shape
|
tuple of int
|
If not provided, inferred as |
None
|
Source code in probpipe/core/_record_array.py
mean(axis=0)
¶
Mean over a batch axis.
Returns NumericRecord if no batch dims remain, else
NumericRecordArray.
var(axis=0)
¶
Variance over a batch axis.
Returns NumericRecord if no batch dims remain, else
NumericRecordArray.
Weights¶
Weights(*, n=None, weights=None, log_weights=None)
¶
Normalized probability weights over n items.
Weights stores log-unnormalized weights internally for numerical
stability and provides lazy-cached access to normalized weights and
log-weights.
It implements the JAX array protocol (__jax_array__), so a
Weights object can be passed directly to any JAX operation that
expects an array — it will automatically convert to its normalized
weight vector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
Number of items. When provided alone (without weights or log_weights), creates uniform weights. When provided alongside an array, validates that the array length matches. |
None
|
weights
|
ArrayLike, Weights, or None
|
Non-negative weights. |
None
|
log_weights
|
ArrayLike, Weights, or None
|
Log-unnormalized weights. Preferred when weights span many
orders of magnitude (e.g. importance sampling). |
None
|
Examples:
Create from raw weights or log-weights:
>>> w = Weights(weights=jnp.array([1.0, 2.0, 1.0]))
>>> w = Weights(log_weights=jnp.array([-1.0, 0.0, -1.0]))
Create uniform weights:
Use as a JAX array — returns normalized weights automatically:
>>> jnp.sum(w) # -> ~1.0
>>> jnp.einsum("n,n...->...", w, vals) # weighted sum
>>> w * values # element-wise product
This means Weights can be passed anywhere a weight array is
expected, including jax.random.choice(..., p=w).
Access different representations explicitly when needed:
>>> w.normalized # Array, shape (n,) — probabilities summing to 1
>>> w.log_normalized # Array — log-probabilities (always an array)
>>> w.log_unnormalized # Array | None — raw stored log-weights (None if uniform)
>>> w.is_uniform # bool — True when all items are equally weighted
Compute weighted statistics directly:
>>> w.mean(values) # weighted mean along leading axis
>>> w.variance(values) # weighted variance
>>> w.covariance(values) # weighted covariance matrix
>>> w.choice(key, shape=(10,)) # draw 10 weighted random indices
Passing to distribution constructors — all ProbPipe distribution
constructors that accept weights or log_weights also accept
a pre-built Weights object for either parameter. When a
Weights object is passed, it is used as-is (no re-validation).
The behavior is the same regardless of which parameter it is passed
to, since the Weights object already encapsulates its
representation::
w = Weights(log_weights=log_w)
EmpiricalDistribution(samples, weights=w) # OK
EmpiricalDistribution(samples, log_weights=w) # also OK, same result
JAX compatibility — Weights is registered as a JAX pytree
whose single leaf is the normalized weight array, so it works
transparently inside jax.jit, jax.vmap, jnp.sum,
jnp.einsum, and other JAX operations.
Notes
Zero weights and -inf in log-space. When a weight array
contains zeros (e.g. [0.0, 1.0, 0.0]), the internal
log-representation stores -inf for those entries. All
Weights operations handle this correctly:
normalizedproduces0.0for those items (via softmax).log_normalizedcontains-infentries (mathematically correct:log(0) = -inf).effective_sample_sizeis unaffected (logsumexphandles-infinputs).choicenever selects zero-weight items.
Code that consumes log_normalized directly should be aware
that -inf values may be present.
Source code in probpipe/_weights.py
n
property
¶
Number of items.
is_uniform
property
¶
True when all items are equally weighted.
normalized
property
¶
Normalized weights, shape (n,). Cached after first access.
log_normalized
property
¶
Normalized log-weights, shape (n,).
Returns -log(n) for uniform weights, matching the behavior
of normalized which always returns an array.
log_unnormalized
property
¶
Raw log-unnormalized weights as stored. None when uniform.
effective_sample_size
property
¶
Kish's effective sample size (ESS).
.. math::
n_{\mathrm{eff}} = \frac{1}{\sum_i w_i^2}
where :math:w_i are the normalized weights. For uniform
weights this equals n exactly.
Computed in log-space for numerical stability:
.. math::
n_{\mathrm{eff}}
= \exp\!\bigl(-\log \sum_i \exp(2 \log w_i)\bigr)
= \exp\!\bigl(-\mathrm{logsumexp}(2\,\log\mathbf{w})\bigr)
Returns:
| Type | Description |
|---|---|
Array
|
Scalar effective sample size ( |
shape
property
¶
Shape of the weight vector: (n,).
dtype
property
¶
Data type of the underlying log-weights array.
For uniform weights, returns JAX's current default float dtype.
uniform(n)
staticmethod
¶
Create uniform weights over n items.
Equivalent to Weights(n=n) but avoids keyword overhead in
hot internal paths.
Source code in probpipe/_weights.py
__jax_array__()
¶
Return normalized weights as a JAX array.
This allows Weights to be used directly in JAX operations
(jnp.sum(w), jnp.einsum(..., w, ...), w * arr, etc.).
mean(values)
¶
variance(values, mean=None)
¶
Compute weighted variance over the leading axis.
covariance(values, mean=None)
¶
Compute weighted covariance matrix over the leading axis.
choice(key, *, shape=())
¶
Draw weighted random indices from 0..n-1.
subsample(indices)
¶
Return a new Weights for a subset, re-normalized.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
indices
|
Array
|
Integer indices selecting a subset of items. |
required |
Returns:
| Type | Description |
|---|---|
Weights
|
New |
Source code in probpipe/_weights.py
tree_flatten()
¶
Flatten for JAX pytree.
The single leaf is the normalized weight array so that JAX
operations (jnp.sum, jnp.einsum, etc.) receive a plain
array when unpacking the pytree.
Source code in probpipe/_weights.py
tree_unflatten(aux, children)
classmethod
¶
Unflatten from JAX pytree.
Source code in probpipe/_weights.py
Parameter-sweep designs¶
FullFactorialDesign(**marginals) materialises the Cartesian product of
per-field marginals as a sweep-ready RecordArray.
Design(_dict=None, /, *, batch_shape, template, name=None, **fields)
¶
Bases: RecordArray
RecordArray that carries its per-field marginals.
A Design is not meant to be instantiated directly — concrete
subclasses (FullFactorialDesign) assemble the underlying
rows in __init__ and stash the originating marginals for
introspection.
Two equivalent ways to drive a sweep through a @workflow_function::
@workflow_function
def fit(p): ...
result = fit(p=design) # one row per call
@workflow_function
def fit(r, K): ...
result = fit(**design.select_all()) # zip across sibling views
select_all() returns one view per field; views that share the
Design as their parent zip across rows in the WF sweep layer (so
the two shapes above produce identical outputs). For raw columns
(no sweep — just JAX broadcasting), index with design["r"].
Attributes:
| Name | Type | Description |
|---|---|---|
marginals |
Mapping[str, Any]
|
The per-field marginals this design was built from, in construction (insertion) order. Kept for introspection; read-only. |
Source code in probpipe/core/_record_array.py
marginals
property
¶
Per-field marginals this design was built from.
FullFactorialDesign(**marginals)
¶
Bases: Design
Cartesian product over all marginals — one row per combination.
Each marginal is a Python sequence (list, tuple, numpy / jax
array). Numeric marginals become jnp.ndarray columns and
categorical / string marginals become numpy.ndarray(dtype=object)
columns. Row order is row-major over the marginals in insertion
order — i.e., the last-listed marginal varies fastest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**marginals
|
Sequence
|
Candidate values for each field. Must pass at least one marginal; each must be non-empty. |
{}
|
Examples:
Cartesian grid of two numeric fields:
>>> ff = FullFactorialDesign(r=[1.5, 1.8], K=[60.0, 80.0])
>>> ff.batch_shape
(4,)
>>> ff.fields
('r', 'K')
Mixed numeric / categorical marginals are supported — columns fall
out as object-dtype arrays for the categorical fields:
Source code in probpipe/record/design.py
Auxiliary-metadata registry¶
Record → NumericRecord conversion drops backend-specific metadata
(xarray dims / coords / attrs, pandas index / columns /
dtypes); the auxiliary registry round-trips it so to_native()
reproduces the original container.
register_aux(leaf_type, *, capture, restore)
¶
Register (capture, restore) hooks for a backend leaf type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
leaf_type
|
type
|
The Python type of the leaves whose metadata should be
preserved across a |
required |
capture
|
callable
|
|
required |
restore
|
callable
|
|
required |
Notes
Re-registering an existing leaf_type overwrites the previous
hook silently. Lookup uses aux_for which walks the MRO of
type(obj), so registering a base class also covers its
subclasses.
Source code in probpipe/core/_array_backend.py
aux_for(obj)
¶
Return the registered hooks for obj, or None if absent.
Walks the MRO of type(obj) so subclass instances pick up
base-class registrations.
Notes
Exact-type lookup is checked before the MRO walk so the common
np.ndarray / jax.Array leaves on the
NumericRecord.__init__ / pytree-unflatten hot path skip a
multi-step MRO traversal.
Source code in probpipe/core/_array_backend.py
AuxHooks(capture, restore)
dataclass
¶
A pair of (capture, restore) hooks for one backend type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
capture
|
callable
|
|
required |
restore
|
callable
|
|
required |