Operator catalogue
One table per group.
The columns are:
- Operator: the constructor, with its configuration arguments.
- Signature: what
__call__ takes and returns, then what compute_output_signature reports (None means "not tracked", as for Fanout and the parallel maps today).
- Semantics: one line; the detail is in operators.md and execution.md.
- Status:
today (ships in pipekit-ops 0.2.1, file named), proposed (Pn) (the roadmap phase in ../roadmap.md that adds it), or dropped (not added, with the reason).
Phases P1 to P5 have shipped, so every row that this design adds is now today; a "rebuilt on Map" note marks a class that shipped before and was rebuilt in P2.
Conventions shared by every new operator
- Keys are plain values.
Where an argument is a key (
key, keys), it is a str, an int or a tuple of them, and it is used for item lookup (x[key]), as in toolz's getter (toolz/itertoolz.py, def getter).
The per-item key of GroupBy, CountBy, Unique and SortBy may instead be an Operator, which is called on the item.
As built, the dict operators take plain keys only, because Get(op) would just be op.
A bare Python callable is refused with a TypeError that says to wrap it in Lambda.
- Predicates and functions are operators.
Filter(pred), Until(op, pred, …), AssertThat(pred) and the rest take an Operator.
Their config is then the child's config, so they round-trip whenever their children do, and no new class sets forbid_in_yaml.
- Lists in, lists out.
Iterable operators accept any iterable and return a
list (or a dict for the group-by family), as the parallel maps do today (packages/pipekit-ops/src/pipekit_ops/parallel.py).
The two exceptions return a lazy iterator on purpose: Prefetch and Map.iter.
- Several inputs, positionally.
Merge and Zip take their inputs as positional arguments, so they work as Graph nodes (Merge()(a, b)), and also accept one sequence of inputs, so they work inside a Sequential after juxt or Fanout.
Dict plumbing
Graph and Fanout return dicts (packages/pipekit-ops/src/pipekit_ops/_base/graph.py, packages/pipekit-ops/src/pipekit_ops/combine.py), and STAC items are nested dicts.
Before P1, only Lambda reshaped them.
| Operator |
Signature |
Semantics |
Status |
Get(key, default=MISSING) |
Mapping \| Sequence → Any; None |
x[key]; a tuple of keys returns a tuple; default replaces a missing key |
today (dicts.py) |
GetIn(keys, default=MISSING) |
Mapping → Any; None |
x[k0][k1]…; default replaces a missing path |
today (dicts.py) |
Pick(keys) |
Mapping → dict; None |
keep only keys, in the order given; a missing key raises KeyError |
today (dicts.py) |
Dissoc(keys) |
Mapping → dict; None |
drop keys; missing keys are ignored |
today (dicts.py) |
Rename(mapping) |
Mapping → dict; None |
rename keys by mapping; unlisted keys pass through; a rename onto an existing key raises ValueError |
today (dicts.py) |
Merge() |
*Mapping → dict; None |
union, later inputs win (toolz merge) |
today (dicts.py) |
Assoc(key, op) |
Mapping → dict; None |
add key: op(x) computed from the whole dict |
today (dicts.py) |
UpdateIn(keys, op, default=MISSING) |
Mapping → dict; None |
replace the value at a key path by op(value); replaces the planned OnKey |
today (dicts.py) |
ValMap(op) |
Mapping → dict; None |
op on every value, keys kept (toolz valmap) |
today (dicts.py) |
Iterables
| Operator |
Signature |
Semantics |
Status |
Map(op, executor="serial", …) |
Iterable → list; None |
op on every item; the execution engine, see execution.md |
today (execution.py) |
Filter(pred) |
Iterable → list; None |
keep items where pred(item) is truthy |
today (seq.py) |
Not(pred) |
Any → bool; None |
not pred(x); the operator form of complement |
today (seq.py) |
Remove(pred) |
Iterable → list; None |
drop items where pred(item) is truthy (toolz remove) |
today (seq.py) |
Reduce(op, initial=MISSING) |
Iterable → Any; None |
left fold; op is called on the tuple (acc, item); empty input with no initial raises TypeError like functools.reduce |
today (seq.py) |
Fold(op, initial, chunksize=128, combine=None, executor="scope") |
Iterable → Any; None |
associative tree fold over chunks, parallel through Map (toolz sandbox.fold) |
today (execution.py) |
PartitionAll(n) |
Iterable → list[tuple]; None |
consecutive tuples of length n, the last may be shorter; replaces the planned Chunk |
today (seq.py) |
Concat() |
Iterable[Iterable] → list; None |
flatten one level (toolz concat); replaces the planned Flatten |
today (seq.py) |
Zip(strict=True) |
*Iterable → list[tuple]; None |
builtin zip, strict by default |
today (seq.py) |
GroupBy(key) |
Iterable → dict[Any, list]; None |
group items by key, first-seen key order (toolz groupby) |
today (seq.py) |
CountBy(key) |
Iterable → dict[Any, int]; None |
count items per key (toolz countby; CountBy(Identity()) is toolz frequencies) |
today (seq.py) |
Count() |
Iterable → int; None |
number of items, lazy iterables included (toolz count) |
today (seq.py) |
Unique(key=None) |
Iterable → list; None |
first occurrence of each item, or of each key(item) (toolz unique) |
today (seq.py) |
Take(n) |
Iterable → list; None |
the first n items (toolz take), for dry runs |
today (seq.py) |
SortBy(key, reverse=False) |
Iterable → list; None |
stable sort by key |
today (seq.py) |
Flow
| Operator |
Signature |
Semantics |
Status |
Retry(op, attempts, base_delay, on) |
Any → Any; op's |
exponential back-off, base_delay * 2 ** (i - 1) before attempt \(i+1\) |
today (control.py) |
Try(primary, fallback, on) |
Any → Any; common of both |
toolz excepts |
today (control.py) |
Timeout(op, seconds, max_abandoned=8) |
Any → Any; op's |
raise TimeoutError if op has not returned after seconds; a daemon thread per call, abandoned, not killed; warns when more than max_abandoned are still running |
today (flow.py) |
RateLimit(op, calls, period) |
Any → Any; op's |
at most calls starts of op in any half-open window of period seconds, per process |
today (flow.py) |
Until(op, pred, max_iter, on_max="raise") |
Any → Any; None |
apply op until pred(x) is truthy; stateless sibling of pipekit_cycle.Recurrence |
today (flow.py) |
Execution
| Operator |
Signature |
Semantics |
Status |
Map(op, executor="serial", n_workers=None, max_in_flight=None, on_error="raise", chunksize=1, start_method=None, progress=None) |
Iterable → list; None |
one engine over serial, thread, process, async and any concurrent.futures.Executor; progress logs counts every progress items |
today (execution.py) |
Map.iter(items, ordered=True), await Map.run(items) |
lazy iterator; awaitable list |
streaming and running-event-loop entry points; ordered lives here, not in the config |
today (execution.py) |
MapError, Failed(index, exception) |
exception; record |
the ExceptionGroup that on_error="collect" raises, and the record in each failed slot of MapError.results |
today (execution.py) |
Prefetch(op, depth=2, executor="thread") |
Iterable → Iterator; None |
read depth items ahead of the consumer, in input order |
today (execution.py) |
executor_scope(executor) |
context manager |
sets the executor that Map(executor="scope") uses (serial when none is set); not stored in any config |
today (execution.py) |
ThreadMap(op, n_workers=8) |
Iterable → list; None |
preset: Map(op, executor="thread", …); config and hash unchanged |
today (parallel.py), rebuilt on Map |
ProcessMap(op, n_workers=8, on_error="raise") |
Iterable → list; None |
preset: Map(op, executor="process", …); fails fast on "raise"; keeps the platform start method for one minor release, with a FutureWarning when it is not spawn |
today (parallel.py), rebuilt on Map |
AsyncMap(op, semaphore=8) |
Iterable → list; None |
preset: Map(op, executor="async", max_in_flight=semaphore) |
today (parallel.py), rebuilt on Map |
BatchedMap(op, batch_size=8, flatten=True) |
Iterable → list; None |
preset: serial Map(op) over lists of batch_size items, then an optional flatten; PartitionAll and the executor argument are still proposed |
today (parallel.py), rebuilt on Map |
check_pickleable(op) |
Operator → list[Operator] |
flagged operators in the tree |
today (parallel.py); Map calls it, see execution.md |
Cache and store
| Operator |
Signature |
Semantics |
Status |
Cache(inner, store=None, key=None) |
Any → Any; inner's |
memoise by input and the inner pipeline's hash; store names a Store in scope, None keeps today's private in-memory dict; key is an operator that computes the input key (toolz memoize(key=…)) |
today (cache.py) |
Memoize |
alias |
Memoize is Cache |
today (cache.py) |
Store |
protocol |
__contains__(key), load(key), save(key, value) |
today (store.py) |
MemoryStore() |
Store |
dict plus a lock; one process |
today (store.py) |
DirectoryStore(root, codec="pickle") |
Store |
one file per key, atomic rename, safe across processes on one file system; writes a pipekit-store.json format marker |
today (store.py) |
store_scope(**stores) |
context manager |
binds store names to Store instances; not stored in any config |
today (store.py) |
Checkpoint(store) |
none |
not added: it is Cache(op, store=…), and pipekit_train.Checkpoint already owns the name |
dropped |
Observe and QC
| Operator |
Signature |
Semantics |
Status |
Tap, Snapshot, ShapeTrace, Profile, Histogram |
pass-through |
closure-holding observers |
today (observe.py) |
Log(message="call {n}: {type} {signature}", level="INFO", logger="pipekit_ops") |
Any → Any; passthrough |
pass-through; logs one line per call with the call count and the input's Signature if an adapter is registered |
today (observe.py) |
Map(…, progress=every) |
argument |
log done / failed / in-flight counts and items per second every every items |
today (execution.py) |
AssertThat(pred, message="predicate returned False") |
Any → Any; passthrough |
QCError if pred(x) is falsy; the serialisable AssertCallable |
today (qc.py) |
AssertSignature(pattern, dtype=None, missing="raise") |
Any → Any; passthrough |
QCError unless signature_of(x).matches(pattern) (and the dtype agrees) |
today (qc.py) |
Quarantine, AssertShape, AssertDType, AssertHasAttribute, AssertCallable, QCError |
pass-through |
existing guards |
today (qc.py) |
Progress, observer Count |
none |
not added: Map(progress=…) and Log's call count cover them, and Count keeps toolz's meaning |
dropped |
Name clash: two BatchedMaps
pipekit_ops.BatchedMap takes an iterable of items and calls the inner operator on lists of them (packages/pipekit-ops/src/pipekit_ops/parallel.py).
The pipekit-array design plans a BatchedMap(inner, batch_size, axis) that splits one array along an axis (packages/pipekit-array/docs/design/api/operators.md, ADR A5 in packages/pipekit-array/docs/design/decisions.md).
ADR A5 says the import path disambiguates.
This design disagrees (decision O14): code that imports both packages, or a Hydra YAML that names both, meets one name with two meanings.
The core name has shipped since v0.1, so the array operator should take the sibling proposal's name, BatchAlong(inner, batch_size, dim).