Metadata-Version: 2.4
Name: eventsource-py
Version: 0.1.0
Summary: Production-ready event sourcing library for Python
Project-URL: Homepage, https://github.com/tyevans/eventsource-py
Project-URL: Documentation, https://tyevans.github.io/eventsource-py
Project-URL: Repository, https://github.com/tyevans/eventsource-py
Project-URL: Changelog, https://github.com/tyevans/eventsource-py/blob/main/CHANGELOG.md
Author-email: Ty Evans <tyevans@gmail.com>
License: MIT
License-File: LICENSE
Keywords: cqrs,ddd,domain-driven-design,event-sourcing,events
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: pydantic<3.0,>=2.0
Requires-Dist: redis>=5.0
Requires-Dist: sqlalchemy<3.0,>=2.0
Provides-Extra: all
Requires-Dist: aiosqlite<1.0,>=0.19.0; extra == 'all'
Requires-Dist: asyncpg<1.0,>=0.27.0; extra == 'all'
Requires-Dist: opentelemetry-api<2.0,>=1.0; extra == 'all'
Requires-Dist: opentelemetry-sdk<2.0,>=1.0; extra == 'all'
Requires-Dist: redis<6.0,>=5.0; extra == 'all'
Provides-Extra: all-backends
Requires-Dist: aiosqlite<1.0,>=0.19.0; extra == 'all-backends'
Requires-Dist: asyncpg<1.0,>=0.27.0; extra == 'all-backends'
Provides-Extra: dev
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pre-commit>=4.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.3; extra == 'dev'
Requires-Dist: testcontainers>=4.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocs>=1.5; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
Requires-Dist: pymdown-extensions>=10.0; extra == 'docs'
Provides-Extra: postgresql
Requires-Dist: asyncpg<1.0,>=0.27.0; extra == 'postgresql'
Provides-Extra: redis
Requires-Dist: redis<6.0,>=5.0; extra == 'redis'
Provides-Extra: sqlite
Requires-Dist: aiosqlite<1.0,>=0.19.0; extra == 'sqlite'
Provides-Extra: telemetry
Requires-Dist: opentelemetry-api<2.0,>=1.0; extra == 'telemetry'
Requires-Dist: opentelemetry-sdk<2.0,>=1.0; extra == 'telemetry'
Description-Content-Type: text/markdown

# eventsource-py

[![Python Version](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![PyPI version](https://img.shields.io/pypi/v/eventsource-py.svg)](https://pypi.org/project/eventsource-py/)
[![CI](https://github.com/tyevans/eventsource-py/actions/workflows/ci.yml/badge.svg)](https://github.com/tyevans/eventsource-py/actions)
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)

A production-ready event sourcing library for Python 3.11+.

## Features

- **Event Store** - PostgreSQL, SQLite, and In-Memory backends with optimistic locking
- **Domain Events** - Immutable event classes with Pydantic validation and versioning
- **Event Registry** - Thread-safe event type registration for serialization/deserialization
- **Aggregate Pattern** - Base classes for event-sourced aggregates with state reconstruction
- **Repository Pattern** - Clean abstractions for loading and saving aggregates
- **Projection System** - Checkpoint tracking, retry logic, and dead letter queue support
- **Event Bus** - In-Memory and Redis Streams backends for event distribution
- **Transactional Outbox** - Reliable event publishing pattern
- **Multi-tenancy** - Built-in tenant isolation support
- **Observability** - Optional OpenTelemetry integration

## Installation

```bash
# Basic installation
pip install eventsource-py

# With PostgreSQL support
pip install eventsource-py[postgresql]

# With SQLite support
pip install eventsource-py[sqlite]

# With Redis support
pip install eventsource-py[redis]

# With OpenTelemetry support
pip install eventsource-py[telemetry]

# All optional dependencies
pip install eventsource-py[all]
```

## Requirements

- Python 3.11+
- pydantic >= 2.0
- sqlalchemy >= 2.0 (for PostgreSQL backend)

## Quick Start

### 1. Define Your Events

```python
from uuid import UUID
from eventsource import DomainEvent, register_event

@register_event
class OrderCreated(DomainEvent):
    """Event emitted when an order is created."""
    event_type: str = "OrderCreated"
    aggregate_type: str = "Order"
    customer_id: UUID
    total_amount: float

@register_event
class OrderShipped(DomainEvent):
    """Event emitted when an order is shipped."""
    event_type: str = "OrderShipped"
    aggregate_type: str = "Order"
    tracking_number: str
```

### 2. Define Your Aggregate State

```python
from pydantic import BaseModel

class OrderState(BaseModel):
    """State of an Order aggregate."""
    order_id: UUID
    customer_id: UUID | None = None
    total_amount: float = 0.0
    status: str = "draft"
    tracking_number: str | None = None
```

### 3. Create Your Aggregate

```python
from eventsource import AggregateRoot

class OrderAggregate(AggregateRoot[OrderState]):
    """Event-sourced Order aggregate."""
    aggregate_type = "Order"

    def _get_initial_state(self) -> OrderState:
        return OrderState(order_id=self.aggregate_id)

    def _apply(self, event: DomainEvent) -> None:
        if isinstance(event, OrderCreated):
            self._state = OrderState(
                order_id=self.aggregate_id,
                customer_id=event.customer_id,
                total_amount=event.total_amount,
                status="created",
            )
        elif isinstance(event, OrderShipped):
            if self._state:
                self._state = self._state.model_copy(
                    update={
                        "status": "shipped",
                        "tracking_number": event.tracking_number,
                    }
                )

    def create(self, customer_id: UUID, total_amount: float) -> None:
        """Command: Create the order."""
        if self.version > 0:
            raise ValueError("Order already created")

        event = OrderCreated(
            aggregate_id=self.aggregate_id,
            customer_id=customer_id,
            total_amount=total_amount,
            aggregate_version=self.get_next_version(),
        )
        self.apply_event(event)

    def ship(self, tracking_number: str) -> None:
        """Command: Ship the order."""
        if not self.state or self.state.status != "created":
            raise ValueError("Order must be created before shipping")

        event = OrderShipped(
            aggregate_id=self.aggregate_id,
            tracking_number=tracking_number,
            aggregate_version=self.get_next_version(),
        )
        self.apply_event(event)
```

### 4. Use the Repository

```python
import asyncio
from uuid import uuid4
from eventsource import InMemoryEventStore, AggregateRepository

async def main():
    # Set up event store and repository
    event_store = InMemoryEventStore()
    repo = AggregateRepository(
        event_store=event_store,
        aggregate_factory=OrderAggregate,
        aggregate_type="Order",
    )

    # Create and save a new order
    order_id = uuid4()
    order = repo.create_new(order_id)
    order.create(customer_id=uuid4(), total_amount=99.99)
    await repo.save(order)

    # Load the order and ship it
    loaded_order = await repo.load(order_id)
    loaded_order.ship(tracking_number="TRACK-123")
    await repo.save(loaded_order)

    # Verify the state
    final_order = await repo.load(order_id)
    print(f"Order status: {final_order.state.status}")
    print(f"Tracking: {final_order.state.tracking_number}")

asyncio.run(main())
```

## Architecture

```
+-------------------+     +-------------------+     +-------------------+
|                   |     |                   |     |                   |
|    Commands       |---->|    Aggregates     |---->|   Event Store     |
|                   |     |                   |     |                   |
+-------------------+     +-------------------+     +--------+----------+
                                                             |
                                                             v
+-------------------+     +-------------------+     +-------------------+
|                   |     |                   |     |                   |
|   Read Models     |<----|   Projections     |<----|   Event Bus       |
|                   |     |                   |     |                   |
+-------------------+     +-------------------+     +-------------------+
```

### Core Concepts

- **Events** - Immutable facts that capture state changes. Events are never deleted or modified.
- **Event Store** - Persists events with ordering and optimistic locking guarantees.
- **Aggregates** - Consistency boundaries that reconstruct state from event streams.
- **Repository** - Abstracts loading/saving aggregates from/to the event store.
- **Projections** - Build read-optimized views from event streams.
- **Event Bus** - Distributes events to subscribers for async processing.

## Documentation

For comprehensive documentation, see the [Documentation Index](docs/index.md).

### Getting Started

- [Getting Started Guide](docs/getting-started.md) - Installation and first steps
- [Architecture Overview](docs/architecture.md) - System design and concepts
- [FAQ](docs/faq.md) - Frequently asked questions

### Guides

- [Multi-tenant Setup](docs/guides/multi-tenant.md) - Tenant isolation patterns and configuration
- [Error Handling](docs/guides/error-handling.md) - Exception handling best practices
- [Authentication](docs/guides/authentication.md) - Security integration patterns
- [Production Deployment](docs/guides/production.md) - Production readiness checklist

### API Reference

- [Events API](docs/api/events.md) - DomainEvent and EventRegistry
- [Event Stores](docs/api/stores.md) - EventStore interface and implementations
- [Aggregates](docs/api/aggregates.md) - AggregateRoot and Repository
- [Projections](docs/api/projections.md) - Projection system
- [Event Bus](docs/api/bus.md) - Event distribution

### Examples

- [Basic Usage](docs/examples/basic-order.md) - Simple order example
- [Multi-tenant Setup](docs/examples/multi-tenant.md) - Multi-tenancy configuration
- [Projections](docs/examples/projections.md) - Building read models
- [Testing Patterns](docs/examples/testing.md) - Unit and integration testing

### Architecture Decision Records

- [ADR Index](docs/adrs/index.md) - All architectural decisions
- [ADR-0001: Async-First Design](docs/adrs/0001-async-first-design.md)
- [ADR-0002: Pydantic Event Models](docs/adrs/0002-pydantic-event-models.md)
- [ADR-0003: Optimistic Locking](docs/adrs/0003-optimistic-locking.md)

### Developer Documentation

- [Code Structure](docs/development/code-structure.md) - Project organization
- [Testing Guide](docs/development/testing.md) - Testing strategies and patterns

## Development

```bash
# Install in development mode
pip install -e ".[dev]"

# Run tests
pytest

# Run tests with coverage
pytest --cov=eventsource --cov-report=html

# Run type checking
mypy src/eventsource

# Run linting
ruff check src/eventsource

# Format code
ruff format src/eventsource
```

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
