Skip to main content

A minimal, `asyncio`-native event sourcing library

Project description

py-event-sourcing - A minimal, asyncio-native event sourcing library

CI PyPI version Python Versions License: MIT

This library provides core components for building event-sourced systems in Python. It uses SQLite for persistence and offers a simple API for write, read, and watch operations on event streams.

For a deeper dive into the concepts and design, please see:

  • Concepts: An introduction to the Event Sourcing pattern.
  • Design: An overview of the library's architecture and components.

Key Features

  • Simple & Serverless: Uses SQLite for a file-based, zero-dependency persistence layer.
  • Global Event Stream: Query all events across all streams in their global sequence using the special '@all' stream, perfect for building cross-stream read models or for auditing.
  • Idempotent Writes: Prevents duplicate events in distributed systems by using an optional id on each event.
  • Optimistic Concurrency Control: Ensures data integrity by allowing writes only against a specific, expected stream version.
  • Efficient Watching: A centralized notifier polls the database once to serve all watchers, avoiding the "thundering herd" problem and ensuring low-latency updates.
  • Snapshot Support: Accelerate state reconstruction for long-lived streams by saving and loading state snapshots. This is also supported for the '@all' stream, which is ideal for complex, system-wide projections.
  • Fully Async API: Built from the ground up with asyncio for high-performance, non-blocking I/O.
  • Extensible by Design: Core logic is decoupled from storage implementation via Protocol-based adapters. While this package provides a highly-optimized SQLite backend, you can easily create your own adapters for other databases (e.g., PostgreSQL, Firestore).

Installation

This project uses uv for dependency management.

  1. Create and activate the virtual environment:

    uv venv
    source .venv/bin/activate
    
  2. Install the package in editable mode with dev dependencies:

    uv pip install -e ".[dev]"
    

Quick Start

Here’s a quick example of writing to and reading from a stream.

import asyncio
import os
import tempfile
from py_event_sourcing import sqlite_stream_factory, CandidateEvent

async def main():
    # Use a temporary file for the database to keep the example self-contained.
    with tempfile.TemporaryDirectory() as tmpdir:
        db_path = os.path.join(tmpdir, "example.db")

        # The factory is an async context manager that handles all resources.
        async with sqlite_stream_factory(db_path) as open_stream:
            stream_id = "my_first_stream"

            # Write an event
            async with open_stream(stream_id) as stream:
                event = CandidateEvent(type="UserRegistered", data=b'{"user": "Alice"}')
                await stream.write([event])
                print(f"Event written. Stream version is now {stream.version}.")

            # Read the event back
            async with open_stream(stream_id) as stream:
                all_events = [e async for e in stream.read()]
                print(f"Read {len(all_events)} event(s) from the stream.")
                print(f"  -> Event type: {all_events[0].type}, Data: {all_events[0].data.decode()}, Version: {all_events[0].version}")

if __name__ == "__main__":
    asyncio.run(main())

Basic Usage

For a detailed, fully-commented example covering all major features (writing, reading, snapshots, and watching), please see basic_usage.py.

To run the example:

uv run python3 basic_usage.py

Sample Output:

--- Example 1: Writing and Reading Events ---
Stream 'counter_stream_1' is now at version 3.
Reading all events from the stream:
  - Event version 1: Increment
  - Event version 2: Increment
  - Event version 3: Decrement

--- Example 2: Reconstructing State from Events (Read Model & Projector) ---
Reconstructed state: Counter is 1.

--- Example 3: Watching for New Events ---
Watching for events (historical and new)...
  - Watched event: Increment (version 1)
  - Watched event: Increment (version 2)
  - Watched event: Decrement (version 3)
Writing new events to trigger the watcher...
  - Watched event: Increment (version 4)
  - Watched event: Increment (version 5)

--- Example 4: Using Snapshots for Efficiency ---
Snapshot for 'counter' projection saved at version 5 with state: count = 3
State for 'counter' projection restored from snapshot at version 5. Count is 3.
Replaying events since snapshot...
  - Applying event version 6: Increment
Final reconstructed state: Counter is 4.

Benchmarks

A benchmark script is included to measure write/read throughput and demonstrate the performance benefits of using snapshots. You can find the script at benchmark.py.

To run the benchmark:

uv run python3 benchmark.py

Sample Output:

--- Running benchmark with 1,000,000 events ---
Writing 1,000,000 events...

Finished writing 1,000,000 events in 9.41 seconds.
Write throughput: 106,320.88 events/sec.

--- Benchmark 1: Reconstructing state from all events ---
Reconstructed state (all events): Counter is 1,000,000.
Time to reconstruct from all events: 2.66 seconds.
Read throughput: 375,273.94 events/sec.

--- Benchmark 2: Reconstructing state using snapshot ---
Creating snapshot...
Snapshot created.
Writing 100 additional events...
Finished writing 100 additional events.
State restored from snapshot at version 1,000,000.
Reconstructed state (with snapshot): Counter is 1,000,100.
Time to reconstruct with snapshot: 0.00 seconds.

--- Benchmark Summary ---
Time to reconstruct from all 1,000,100 events: 2.66 seconds.
Time to reconstruct with snapshot (after 1,000,000 events): 0.0007 seconds.

Database file size: 148.13 MB

Testing

The test suite uses pytest. To run all tests, use the following command:

uv run pytest

Contributing

We welcome contributions! Please see our Contributing Guide for details on:

  • Setting up a development environment
  • Code style guidelines
  • Testing requirements
  • Pull request process

License

This project is licensed under the MIT License - see the LICENSE file for details.

Changelog

See CHANGELOG.md for a list of changes and version history.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_event_sourcing-1.0.3.tar.gz (85.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_event_sourcing-1.0.3-py3-none-any.whl (20.4 kB view details)

Uploaded Python 3

File details

Details for the file py_event_sourcing-1.0.3.tar.gz.

File metadata

  • Download URL: py_event_sourcing-1.0.3.tar.gz
  • Upload date:
  • Size: 85.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.9

File hashes

Hashes for py_event_sourcing-1.0.3.tar.gz
Algorithm Hash digest
SHA256 b3931495bf88b27aba7a90e06ebdf9b39e1738d22e61d493218f2849afb31de3
MD5 151ac80065c4c88ec1f754492336b6ec
BLAKE2b-256 b1f8a63999269e8a804dbf534e53b41c7ea7ad9bfad7f8450b8fb26f10baf6da

See more details on using hashes here.

File details

Details for the file py_event_sourcing-1.0.3-py3-none-any.whl.

File metadata

File hashes

Hashes for py_event_sourcing-1.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 5693dd7208f65d8f7bc30a88bc7aec07825f1de601f1a786f3b255c0729021e1
MD5 6a5589520ccbcf40a7b2063ca870d83f
BLAKE2b-256 2c2c5df0c06c5873872531791302e7ecdf55de6603e00f0429c806782766baa2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page