A Production-Ready Actor Model Toolkit for Python
DomoActors-Py is a sophisticated actor framework for Python that provides:
- Comprehensive Type Hints: Full type annotations with Protocol classes for IDE support and static analysis
- Fault Tolerance: Hierarchical supervision with configurable strategies
- Message-Driven Concurrency: FIFO per-actor mailboxes with async dispatch
- Flexible Addressing: UUIDv7 for distributed systems, numeric for simple scenarios
- Comprehensive Testing: Observable state, await utilities, dead letter listeners
- Zero External Dependencies: Pure Python implementation using only standard library
✅ Production Ready - Fully implemented with comprehensive test coverage (197+ tests, 86% coverage)
Complete Feature Set:
- ✅ Core actor model (Actor, Protocol, Stage)
- ✅ Hierarchical supervision (Resume, Restart, Stop, Escalate)
- ✅ Self-messaging patterns with recursive mailbox dispatch
- ✅ Bounded and unbounded mailboxes with overflow policies
- ✅ Lifecycle hooks (before_start, after_stop, before_restart, etc.)
- ✅ Scheduling (one-time and repeating tasks)
- ✅ Dead letters handling
- ✅ Observable state for testing
- ✅ Full parity with DomoActors-TS
Example Applications:
- ✅ Complete banking system with interactive CLI
- ✅ Transaction coordination with retry logic
- ✅ Context-aware error handling
Install DomoActors-Py from PyPI using pip:
pip install domo-actorsRequirements: Python 3.10 or higher (no external dependencies!)
For development or the latest unreleased features:
git clone https://github.com/VaughnVernon/DomoActors-Py.git
cd DomoActors-Py
pip install -e .from domo_actors import stage, Actor, Protocol
print("DomoActors-Py installed successfully!")import asyncio
from domo_actors import Actor, ActorProtocol, Protocol, ProtocolInstantiator
from domo_actors import Definition, stage, Uuid7Address
# 1. Define the protocol interface
class Counter(ActorProtocol):
async def increment(self) -> None: ...
async def get_value(self) -> int: ...
# 2. Implement the actor
class CounterActor(Actor):
def __init__(self):
super().__init__()
self._count = 0
async def increment(self) -> None:
self._count += 1
async def get_value(self) -> int:
return self._count
# 3. Create protocol instantiator
class CounterInstantiator(ProtocolInstantiator):
def instantiate(self, definition: Definition) -> Actor:
return CounterActor()
class CounterProtocol(Protocol):
def type(self) -> str:
return "Counter"
def instantiator(self) -> ProtocolInstantiator:
return CounterInstantiator()
# 4. Use the actor
async def main():
# Create actor (stage() returns singleton instance)
counter: Counter = stage().actor_for(
CounterProtocol(),
Definition("Counter", Uuid7Address(), ())
)
# Use actor
await counter.increment()
await counter.increment()
value = await counter.get_value()
print(f"Count: {value}") # Output: Count: 2
# Cleanup
await stage().close()
if __name__ == "__main__":
asyncio.run(main())Actors are the fundamental unit of computation. They:
- Process messages sequentially through their mailbox
- Maintain private state
- Can create child actors
- Never share state directly
Messages are asynchronous and delivered via FIFO mailboxes. DomoActors uses a dynamic proxy pattern that automatically converts method calls into messages.
Actors are organized in a hierarchy. When an actor fails, its supervisor decides what to do:
- Restart: Create a new instance
- Resume: Continue processing messages
- Stop: Terminate the actor
- Escalate: Pass the decision to the parent supervisor
Actors provide lifecycle hooks for customization:
before_start(): Initialize actor statebefore_stop(): Cleanup before terminationbefore_restart(reason): Cleanup before restartafter_restart(): Re-initialize after restart
DomoActors uses Python's __getattr__ magic method to implement a dynamic proxy pattern similar to JavaScript's Proxy API:
# Method calls are automatically converted to messages
await actor.do_something(arg1, arg2)
# Behind the scenes:
# 1. Creates a DeferredPromise
# 2. Wraps call in a LocalMessage
# 3. Enqueues in mailbox
# 4. Returns promise to callerArrayMailbox: Unbounded FIFO queue
from domo_actors import ArrayMailbox
mailbox = ArrayMailbox()BoundedMailbox: Capacity-limited with overflow policies
from domo_actors import BoundedMailbox, OverflowPolicy
mailbox = BoundedMailbox(
capacity=100,
overflow_policy=OverflowPolicy.DROP_OLDEST
)Configure supervision strategies:
from domo_actors import DefaultSupervisor, SupervisionStrategy, SupervisionScope
class MySupervisor(DefaultSupervisor):
async def supervision_strategy(self) -> SupervisionStrategy:
return CustomStrategy()
class CustomStrategy(SupervisionStrategy):
def intensity(self) -> int:
return 5 # Allow 5 restarts
def period(self) -> int:
return 10000 # Within 10 seconds
def scope(self) -> SupervisionScope:
return SupervisionScope.ONE # Only failed actorSchedule tasks using the built-in scheduler:
from datetime import timedelta
from domo_actors import stage
scheduler = stage().scheduler()
# Schedule once
scheduler.schedule_once(
delay=timedelta(seconds=5),
action=lambda: print("Executed!")
)
# Schedule repeated
scheduler.schedule_repeat(
initial_delay=timedelta(seconds=1),
interval=timedelta(seconds=5),
action=lambda: print("Tick!")
)DomoActors provides testing utilities:
from domo_actors.actors.testkit import await_assert, await_state_value
async def test_actor():
# Wait for assertion to pass
await await_assert(
lambda: assert_something(),
timeout=2.0
)
# Wait for state value
await await_state_value(
actor,
"status",
"completed",
timeout=2.0
)See the examples/ directory for complete examples:
Basic actor creation and message passing demonstrating:
- Protocol definition
- Actor implementation
- Message passing
- Async/await patterns
A complete banking system with interactive CLI (examples/bank/bank.py) demonstrating:
Core Features:
- Account management (open, deposit, withdraw)
- Inter-account transfers with 5-phase coordination
- Transaction history with self-messaging pattern
- Exponential backoff retry logic
Advanced Patterns:
- Hierarchical actors: Bank → Account → TransactionHistory
- Three supervisor types: BankSupervisor, AccountSupervisor, TransferSupervisor
- Context-aware error handling: Custom error messages with request details
- Self-messaging: Actors sending messages to themselves for state consistency
- "Let it crash" philosophy: Validation errors handled by supervisors
Run the example:
cd examples/bank
python bank.pyThe bank example is functionally equivalent to the TypeScript version, demonstrating full parity between implementations.
┌─────────────────────────────────────┐
│ LocalStage │
│ ┌───────────────────────────────┐ │
│ │ PrivateRootActor │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ PublicRootActor │ │ │
│ │ │ ┌──────────┐ │ │ │
│ │ │ │ User │ │ │ │
│ │ │ │ Actors │ │ │ │
│ │ │ └──────────┘ │ │ │
│ │ └─────────────────────┘ │ │
│ └───────────────────────────────┘ │
│ │
│ Directory (Actor Registry) │
│ Scheduler │
│ DeadLetters │
│ Supervisors │
└─────────────────────────────────────┘
- Correctness First: Type-safe interfaces, strict async/await patterns
- Fault Tolerance: Hierarchical supervision
- Developer Productivity: Protocol interfaces, lifecycle hooks, testing utilities
- Zero Dependencies: Pure Python using only standard library
DomoActors-Py is a faithful port of DomoActors-TS with Python-specific adaptations:
| Feature | TypeScript | Python |
|---|---|---|
| Proxy Pattern | ES6 Proxy API | __getattr__ magic method |
| Async Model | Promises | asyncio Futures |
| Concurrency | Single-threaded event loop (async/await) | Single-threaded event loop (async/await) |
| Type System | Compile-time static typing | Runtime type hints (PEP 484) |
| Type Checking | Built-in (tsc) | Optional tools (mypy, pyright) |
| IDE Support | Full IntelliSense | Full IntelliSense with type hints |
| Stage Access | stage() singleton |
stage() singleton |
| Mailbox Dispatch | Recursive dispatch | Recursive dispatch |
| Self-Messaging | this.selfAs<T>() |
self.self_as(T) |
| Supervision | Hierarchical supervisors | Hierarchical supervisors |
| Collections | Map/Set | dict/set |
| Actor Lifecycle | Hooks (beforeStart, etc.) | Hooks (before_start, etc.) |
Both implementations share the same core architecture:
- Actor hierarchy: PrivateRootActor → PublicRootActor → User actors
- Supervision strategies: Resume, Restart, Stop, Escalate
- Message delivery: FIFO per-actor mailboxes
- Address types: UUIDv7 for distributed, numeric for simple scenarios
- Zero external dependencies: Both use only standard library features
- Context managers: Can use
async withfor resource management - Type hints: Comprehensive PEP 484 type annotations throughout the codebase
- Static type checking: Compatible with mypy, pyright, and IDE type checkers
- Protocol classes: Structural typing similar to TypeScript interfaces
- AsyncIO integration: Native asyncio support, works with existing async code
- Testing: pytest-asyncio integration for async test cases
DomoActors-Py uses single-threaded async/await, just like JavaScript/TypeScript:
# Single event loop thread
async def actor_method(self):
await self.some_operation() # Yields to event loop
# Continues on same threadKey Points:
- ✅ Single-threaded event loop (like JavaScript)
- ✅ Cooperative multitasking at
awaitpoints - ✅ No parallelism, only concurrency
- ✅ Actors process messages sequentially
- ✅ Perfect parity with DomoActors-TS behavior
Note: Python does support multi-threading (threading module) and multi-processing (multiprocessing module), but DomoActors-Py uses pure asyncio to maintain identical semantics with the TypeScript version.
DomoActors-Py includes comprehensive type hints:
# Full type annotations
def actor_for(
self,
protocol: Protocol,
definition: Definition,
parent: Optional[Actor] = None,
supervisor_name: Optional[str] = None
) -> T:
...
# Generic type variables
T = TypeVar('T')
# Protocol classes for structural typing
class Counter(ActorProtocol):
async def increment(self) -> None: ...
async def get_value(self) -> int: ...Benefits:
- ✅ IDE autocomplete and IntelliSense
- ✅ Catch type errors before runtime with mypy/pyright
- ✅ Self-documenting code
- ✅ Refactoring safety
Check types statically:
mypy domo_actors # Type check the library- Python 3.10 or higher
- No external dependencies for core functionality
pytestandpytest-asynciofor running tests
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Format code
black domo_actors tests examples
# Type check
mypy domo_actors
# Lint
ruff check domo_actors tests examplesContributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
- Issues: https://github.com/VaughnVernon/DomoActors-Py/issues
- Discussions: https://github.com/VaughnVernon/DomoActors-Py/discussions
- Documentation: See
docs/directory
DomoActors-Py is inspired by and builds upon concepts from:
- XOOM/Actors (Java actor framework by Vaughn Vernon)
- DomoActors-TS (TypeScript version)
This Source Code Form is subject to the terms of the Reciprocal Public License, v. 1.5. If a copy of the RPL was not distributed with this file, You can obtain one at https://opensource.org/license/rpl-1-5.
Reciprocal Public License 1.5
See: ./LICENSE.md
Copyright © 2012-2025 Vaughn Vernon. All rights reserved. Copyright © 2012-2025 Kalele, Inc. All rights reserved.
Vaughn Vernon
- Creator of the XOOM Platform
- Books:
- Live and In-Person Training:
- LiveLessons video training:
- Domain-Driven Design Distilled
- Available on the O'Reilly Learning Platform
- Strategic Monoliths and Microservices
- Available on the O'Reilly Learning Platform
- Domain-Driven Design Distilled
- Curator and Editor: Pearson Addison-Wesley Signature Series
- Personal website: https://vaughnvernon.com