EventKit is a Python library for event-driven data pipelines, providing a comprehensive framework for creating reactive, asynchronous data processing systems. It enables loosely coupled components to communicate through events and offers a rich set of operators for building complex data flow pipelines.
The central component is the Event class (eventkit/event.py), which implements the Observer pattern with these key features:
- Event Emission: Events can emit values to connected listeners using
emit(*args) - Listener Management: Connect/disconnect listeners with
connect()/disconnect()or+=/-=operators - Weak References: Automatic cleanup of garbage-collected listeners (unless
keep_ref=True) - Error Handling: Built-in error propagation through
error_eventsub-events - Completion Tracking: Event lifecycle management with
done_eventandset_done()
Events use a sophisticated slot system (Slots class in eventkit/event.py:34-117) for managing listeners:
- Slot Objects: Store listener references, weak references, and function pointers
- Callback Execution: Efficiently iterate through and call all connected listeners
- Automatic Cleanup: Remove dead weak references during iteration
- Async Support: Automatically schedule coroutines in the event loop
The Op base class (eventkit/ops/op.py) provides the foundation for all event operators:
- Source Management: Operators connect to source events and process their emissions
- Event Forwarding: Default behavior passes through source events unchanged
- Error Propagation: Forwards errors from source to operator's error event
- Lifecycle Management: Handles source completion and cleanup
Generate events from various sources:
- Timer: Emit at regular intervals
- Sequence: Emit values from an iterable with optional timing
- Range: Emit integer sequences
- Timerange: Emit datetime values at specified intervals
- Wait: Convert Futures/awaitables to events
- Aiterate: Convert async iterators to events
Modify emitted values:
- Map: Apply functions to values (sync/async)
- Enumerate: Add index counters
- Pluck: Extract nested properties or positional arguments
- Star/Pack: Unpack/pack tuples
- Constant: Replace values with constants
- Timestamp: Add timestamps to values
Filter which values pass through:
- Filter: Apply predicates to values
- Take/Skip: Limit number of values
- TakeWhile/DropWhile: Conditional value passing
- TakeUntil: Stop on notifier event
- Changes/Unique: Eliminate duplicates
- Last: Emit only final value
Accumulate values over time:
- Sum/Product: Mathematical accumulation
- Min/Max/Mean: Statistical operators
- Count: Track emission count
- Reduce: Custom accumulation functions
- List/Deque: Collect values in containers
- Ema: Exponential moving average
NumPy integration for array operations:
- Array: Maintain sliding windows of values
- ArraySum/ArrayMean/ArrayStd: Array-specific aggregations
- ArrayMin/ArrayMax: Array extrema operations
Merge multiple event streams:
- Merge: Combine events as they occur
- Zip: Synchronize events by position
- ZipLatest: Combine using latest values
- Chain: Sequential event processing
- Concat: Concatenate event streams
- Switch: Switch between event sources
Control event timing:
- Delay: Time-shift events
- Debounce: Filter rapid successive events
- Throttle: Rate-limit events
- Sample: Sample at specific intervals
- Timeout: Handle event timeouts
Advanced event composition:
- Emap: Map to new events with custom joining
- Mergemap/Concatmap/Chainmap/Switchmap: Specialized higher-order mapping
EventKit provides seamless asyncio integration:
- get_event_loop() (
eventkit/util.py:19): Get or create event loop - Coroutine Handling: Automatic scheduling of async listeners
- Thread Safety:
emit_threadsafe()for cross-thread communication
Events can be converted to/from standard Python async primitives:
- Async Iterator: Use
async forwith events - Awaitable: Use
awaitto get next emission - Future Integration: Convert Futures to events and vice versa
Multiple equivalent syntaxes for building pipelines:
# Method chaining (most common)
event = source.map(func).filter(pred).take(10)
# Pipe operator
event = source | Map(func) | Filter(pred) | Take(10)
# Explicit piping
event = source.pipe(Map(func), Filter(pred), Take(10))
# Constructor chaining
event = Take(10, Filter(pred, Map(func, source)))Split event streams and recombine them:
# Fork with square bracket syntax
result = source[Min, Max, Mean].zip()
# Explicit forking
fork = source.fork(Min(), Max(), Mean())
result = fork.zip()Implement backpressure and flow control:
# Feedback loops for pacing
pacer = Event()
pipeline = pacer.iterate(data).map(process).connect(pacer.emit)
pacer.emit() # Kickstart- Listeners are stored as weak references by default
- Automatic cleanup when objects are garbage collected
keep_ref=Truefor strong references when needed
- Events properly disconnect from sources when done
- Async tasks are cancelled on event destruction
- Error events and done events are automatically cleaned up
- Slot iteration uses copies to handle dynamic listener changes
- Async coroutines are scheduled efficiently with
asyncio.ensure_future() - Weak reference cleanup is performed during slot execution
- No global state - events are independent
- Operators can be chained indefinitely
- Concurrent processing through async operators
Create custom operators by subclassing Op:
class CustomOp(Op):
def on_source(self, *args):
# Process args
self.emit(processed_args)
def on_source_error(self, source, error):
# Handle errors
pass
def on_source_done(self, source):
# Handle completion
self.set_done()Extend the base Event class for specialized event types with custom behavior.
- Comprehensive test suite in
tests/directory - Tests organized by operator category
- Both sync and async test patterns
- Poetry: Dependency management and packaging
- Ruff: Code formatting and linting
- MyPy: Type checking
- Pytest: Testing framework with asyncio support