API reference¶
Everything public is importable from the package root:
from action0.pipeline import Buffer, ErrorPolicy, Pipeline, StepDefinition
Pipeline¶
An asyncio pipeline: a chain of transformer steps that all run at the same time, each with its own number of concurrent workers and its own thread pool.
- type action0.pipeline.pipeline.Produced = Iterable | AsyncIterable¶
What a transformer returns: any number of output items for the one input item.
- type action0.pipeline.pipeline.Transformer = Callable[[In], Produced | Awaitable[Produced]]¶
A step’s work function. Takes one item and returns the items it produced, so a step can fan out (several items), map (exactly one) or filter (none). It may be a plain function, an
async deffunction or an async generator function.
- type action0.pipeline.pipeline.Source = Iterable | AsyncIterable¶
Items to read, synchronously or asynchronously.
- type action0.pipeline.pipeline.InitialData = Source | Callable[[], Source | Awaitable[Source]]¶
The data a pipeline starts from: an (async) iterable, or a callable returning one — a callable is only invoked when the pipeline starts, so it is the way to feed a pipeline that is built before there is a running event loop.
- type action0.pipeline.pipeline.ErrorHandler = Callable[[Exception, In], Produced | None]¶
Per-step error callback: gets the exception and the item that caused it, and returns replacement items to emit instead (or
Noneto drop the item).
- class action0.pipeline.pipeline.ErrorPolicy(*values)[source]¶
What a step does when its transformer raises.
- RAISE = 'raise'¶
Tear the whole pipeline down and re-raise at the consumer. The default.
- SKIP = 'skip'¶
Log a warning, drop the item and carry on with the next one.
- type action0.pipeline.pipeline.ErrorPolicyLike = ErrorPolicy | Literal['raise', 'skip']¶
An
ErrorPolicy, or the plain string spelling of one.
- class action0.pipeline.pipeline.StepDefinition(transformer, buffer=None, maxsize=0, async_workers=1, thread_pool_size=1, on_error=ErrorPolicy.RAISE, name=None)[source]¶
One step of a pipeline: what to run, how often, and where to put the results.
Created by
Pipeline.add_step(); you only meet it when inspecting a pipeline you have already built.- Parameters:
transformer (
Callable[[TypeVar(In)],Iterable[TypeVar(Out)] |AsyncIterable[TypeVar(Out)] |Awaitable[Iterable[TypeVar(Out)] |AsyncIterable[TypeVar(Out)]]])maxsize (
int, default:0)async_workers (
int, default:1)thread_pool_size (
int, default:1)on_error (
ErrorPolicy|Callable[[Exception,TypeVar(In)],Iterable[TypeVar(Out)] |AsyncIterable[TypeVar(Out)] |None], default:<ErrorPolicy.RAISE: 'raise'>)
- transformer: Transformer¶
The work function, called once per input item.
- buffer: Buffer | None = None¶
The step’s output buffer; a fresh
asyncio.QueuewhenNone.
- thread_pool_size: int = 1¶
Size of this step’s thread pool, or
0to run the transformer in the event loop.
- on_error: ErrorPolicy | ErrorHandler = 'raise'¶
What to do when the transformer raises.
- class action0.pipeline.pipeline.Pipeline(initial, *, buffer=None, maxsize=0, initial_in_thread=False, name=None)[source]¶
A chain of steps that processes items concurrently from end to end.
Build a pipeline from some initial data, append steps, then read the results as an async iterator. Every step is resident and working at the same time: while step 2 handles the first item, step 1 is already producing the second.
Each step gets its own concurrency settings.
async_workersis how many asyncio tasks consume the step’s input buffer, andthread_pool_sizeis how many threads those tasks may hand blocking work to — which is what makes a blocking function usable without stalling the event loop. Usethread_pool_size=0for a transformer that is already non-blocking.>>> import asyncio >>> def double(value: int) -> list[int]: ... return [value * 2] >>> async def main() -> list[int]: ... async with Pipeline(range(5)).add_step(double, async_workers=3) as pipeline: ... return sorted([item async for item in pipeline]) >>> asyncio.run(main()) [0, 2, 4, 6, 8]
Items come out in whatever order they finish, not in input order: as soon as a step has more than one worker, a quick item overtakes a slow one. Sort the results, or keep a key on the items, if you need the original order.
async withis the recommended form. It guarantees that the workers are torn down and the thread pools closed even when the consumer stops early or raises; plainasync forover the pipeline works too and cleans up once the last item has been read.A pipeline is single-use: reading it a second time yields nothing, because its buffers have been shut down.
- Parameters:
initial (
Iterable[TypeVar(Item)] |AsyncIterable[TypeVar(Item)] |Callable[[],Iterable[TypeVar(Item)] |AsyncIterable[TypeVar(Item)] |Awaitable[Iterable[TypeVar(Item)] |AsyncIterable[TypeVar(Item)]]])maxsize (
int, default:0)initial_in_thread (
bool, default:False)initial – the items the pipeline starts from, or a callable returning them (see
InitialData).buffer – buffer for the initial items; a fresh
asyncio.Queueis created whenNone.maxsize – bound of the created initial buffer,
0for unbounded. Ignored whenbufferis given.initial_in_thread – iterate a synchronous
initialin a thread. Set this when advancing the source blocks (a database cursor, a file, a paging HTTP client) — otherwise it blocks the event loop, and with it every step of the pipeline.name – optional label, used in task names and log messages.
- property steps: tuple[StepDefinition[Any, Any], ...]¶
The steps appended so far, in order.
- add_step(transformer, *, buffer=None, maxsize=0, async_workers=1, thread_pool_size=1, on_error=ErrorPolicy.RAISE, name=None)[source]¶
Append a step that consumes this pipeline’s current output.
Returns a new pipeline parameterised by the new step’s output type, so the static type follows the chain as it is built. The original is left as it was.
- Parameters:
transformer (
Callable[[TypeVar(Item)],Iterable[TypeVar(New)] |AsyncIterable[TypeVar(New)] |Awaitable[Iterable[TypeVar(New)] |AsyncIterable[TypeVar(New)]]]) – the work function, called once per item (seeTransformer).buffer (
Buffer[TypeVar(New)] |None, default:None) – the step’s output buffer; a freshasyncio.Queueis created whenNone.maxsize (
int, default:0) – bound of the created output buffer,0for unbounded. Ignored whenbufferis given. Bound it to apply backpressure to the steps upstream.async_workers (
int, default:1) – how many asyncio tasks run this step concurrently.thread_pool_size (
int, default:1) – size of the step’s own thread pool. A synchronous transformer is dispatched to it, so blocking calls do not stall the event loop;0runs the transformer in the loop instead. Ignored forasync defand async generator transformers, which always run in the loop.on_error (
ErrorPolicy|Literal['raise','skip'] |Callable[[Exception,TypeVar(Item)],Iterable[TypeVar(New)] |AsyncIterable[TypeVar(New)] |None], default:<ErrorPolicy.RAISE: 'raise'>) – anErrorPolicy, or a callback returning replacement items (seeErrorHandler).name (
str|None, default:None) – optional label, used in task names and log messages.
- Return type:
- Returns:
a new pipeline ending in this step.
- buffer_sizes()[source]¶
How much is queued up right now, one entry per buffer, source buffer first.
Useful for spotting the step that is holding everything else up.
- async start()[source]¶
Create the buffers, thread pools and worker tasks and let them run.
Called automatically by
async withand by the first iteration, so there is rarely a reason to call it yourself.- Raises:
RuntimeError – if the pipeline has already been started.
- Return type:
- async aclose()[source]¶
Shut everything down: stop the workers, close the thread pools.
Safe to call more than once, and safe to call on a pipeline that was never started.
- Return type:
- async __aexit__(exc_type, exc, traceback)[source]¶
- Parameters:
exc_type (
type[BaseException] |None)exc (
BaseException|None)traceback (
TracebackType|None)
- Return type:
- __aiter__()[source]¶
- Return type:
AsyncIterator[TypeVar(Item)]
Buffer¶
The buffer that hands items from one pipeline step to the next.
- class action0.pipeline.buffer.Buffer(*args, **kwargs)[source]¶
The slice of
asyncio.Queuea pipeline needs between two steps.asyncio.Queuesatisfies this protocol as it is, which is why the pipeline creates plain queues when no buffer is supplied. Implement the protocol yourself to buffer somewhere other than the event loop’s memory.Implementations must reproduce the queue’s shutdown semantics, because the pipeline detects “this stage has run dry” from them and nothing else:
after
shutdown(),put()raisesasyncio.QueueShutDownright away,get()raisesasyncio.QueueShutDownonce the items still buffered have been handed out — or immediately, when shut down withimmediate=True.
A bounded buffer is what applies backpressure: while it is full, the producing step’s workers wait in
put()instead of reading more input.- async put(item)[source]¶
Append an item, waiting while the buffer is full.
- Parameters:
item (
TypeVar(Item)) – the item to buffer.- Raises:
asyncio.QueueShutDown – if the buffer has been shut down.
- Return type:
- async get()[source]¶
Take the oldest buffered item, waiting while the buffer is empty.
- Return type:
TypeVar(Item)- Returns:
the item.
- Raises:
asyncio.QueueShutDown – if the buffer is shut down and drained.