Build your own mini stream processor · Stage 1 of 8
Operator graph + push-based execution
You are building a mini stream processor in pure Python — the engine behind
Flink's DataStream API. You grow a single file, processor.py, across 8 stages.
This stage lays the dataflow foundation: a chain of operators that a record
is pushed through, one element at a time.
The public API (stable for every stage)
The dataclasses, operator classes and function signatures are defined in the starter. Do not rename them — the tests call them directly. The surface grows each stage; this stage uses:
# records / markers that flow downstream
Event(key=None, value=None, ts=None) # a data record
Watermark(t) # event-time marker (stage 3)
Barrier(id) # checkpoint marker (stage 6)
# build + run a job
Environment().source(records) # -> Stream
stream.map(fn).filter(fn).sink() # fluent chain of operators
collect(stream) -> list # run it; return the Sink's data events
This stage's task
Make the operator chain actually execute, push-based (like Flink): the Source
emits each record to a downstream callback; each operator's process(elem, out)
transforms and forwards; the Sink collects.
Source.run(out)pushes every record downstream in order.MapOpappliesfnto data events and forwardsWatermark/Barriermarkers unchanged.FilterOpkeeps data events for whichfnis true, and always forwards markers.SinkOpcollects data events and drops markers._drive_chain(ops)wires the chain (compose each operator's callback into the next) and runs the Source through it; it returns(sink_or_None, terminal_out).collect(stream)returns the Sink's collected data events; with no Sink, the terminal data events (markers excluded). An empty source yields[].
Constraints
- Push model, element-at-a-time: downstream order equals source order.
- Markers pass through transforms but never reach a Sink.
- Pure stdlib, deterministic, no sleeps. The suite runs in well under 60 s.
Run it
de start FLK-BYO/01 # copies the cumulative starter into your workspace
de test # runs this stage (later: earlier stages too)
de hint # progressive hints
Hints
Hint 1
A job is a linear chain of operators starting at a Source. Build it with the Stream builder (map/filter/sink already append operator objects to self.ops). Your job is to make the operators actually run.
Hint 2
Push model: the Source calls a downstream callback once per record; each operator's process(elem, out) does its work and calls out(...) 0+ times. Markers (Watermark, Barrier) must be FORWARDED by map/filter and DROPPED by the sink. Wire the chain in _drive_chain by composing per-operator callbacks from the last operator backwards.
Hint 3
collect() runs _drive_chain and returns the Sink's collected list (data events only). With no Sink, return the terminal data events (filter out Watermark/Barrier). An empty source yields an empty list.
uv run de start FLK-BYO/01-operator-graph
uv run de test