waku [ζ or γγ] means framework in Japanese.
waku is a modular, type-safe Python framework for scalable, maintainable applications. Inspired by NestJS, powered by Dishka IoC.
Warning
waku
is going through a major rewrite, so docs aren't fully up-to-date yet.
Stick to this README and our examples for now.
For more details, check out our waku
deepwiki page.
- π§© Modular architecture: Group related code with explicit imports/exports for clear boundaries and responsibilities.
- π First-class Dependency Injection: Built on Dishka with flexible provider patterns (singleton, scoped, transient); swap implementations easily.
- π¨ Event-driven & CQRS: Handle commands, queries, and events with a comprehensive CQRS implementation, pipeline chains, and centralized processing inspired by MediatR (C#).
- π Framework-agnostic & Integrations: Works with FastAPI, Litestar, FastStream, Aiogram, and more - no vendor lock-in.
- π§° Extensions & Lifecycle Hooks: Hook into the app lifecycle for logging, validation, and custom logic; precise startup/shutdown management.
- π‘οΈ Production-ready: Type-safe APIs, robust validation, and scalable testing support.
- π₯ Enterprise development teams building modular, maintainable backend services or microservices
- ποΈ Architects and tech leads seeking a structured framework with clear dependency boundaries and testability
- π Python developers frustrated with monolithic codebases and looking for better separation of concerns
- π Engineers from other ecosystems (Java Spring, C# ASP.NET, TypeScript NestJS) wanting familiar patterns in Python
- π Projects requiring scalability both in codebase organization and team collaboration
uv add waku
# or
pip install waku
Waku is built around a few core concepts:
- π§© Modules: Classes decorated with
@module()
that define boundaries for application components and establish clear dependency relationships. - π§βπ§ Providers: Injectable services and logic registered within modules.
- π Dependency Injection: Type-safe, flexible wiring powered by Dishka IoC container.
- π WakuFactory: The entry point that creates a
WakuApplication
instance from your root module. - π Application Lifecycle: Initialization and shutdown phases, enhanced with extension hooks.
This structure keeps your code clean and your dependencies explicit.
waku
is framework-agnostic - entrypoints (such as HTTP handlers) are provided by integrations, not the core.
Here's a minimal example showing the core concepts:
import asyncio
from waku import WakuFactory, module
from waku.di import scoped
# 1. Define a provider (service)
class GreetingService:
async def greet(self, name: str) -> str:
return f'Hello, {name}!'
# 2. Create a module and register the provider
@module(providers=[scoped(GreetingService)])
class GreetingModule:
pass
# 3. Create a root module that imports other modules
@module(imports=[GreetingModule])
class AppModule:
pass
async def main() -> None:
# 4. Bootstrap the application with WakuFactory
app = WakuFactory(AppModule).create()
# 5. Use the application with a properly scoped container
async with app, app.container() as c:
# 6. Resolve and use dependencies
svc = await c.get(GreetingService)
print(await svc.greet('waku'))
if __name__ == '__main__':
asyncio.run(main())
Let's add protocols and module exports:
import asyncio
from typing import Protocol
from waku import WakuFactory, module
from waku.di import scoped, singleton
# Define a protocol for loose coupling
class Logger(Protocol):
async def log(self, message: str) -> None: ...
# Implementation of the logger
class ConsoleLogger:
async def log(self, message: str) -> None:
print(f'[LOG] {message}')
# Service that depends on the logger
class UserService:
def __init__(self, logger: Logger) -> None:
self.logger = logger
async def create_user(self, username: str) -> str:
user_id = f'user_{username}'
await self.logger.log(f'Created user: {username}')
return user_id
# Infrastructure module provides core services
@module(
providers=[singleton(ConsoleLogger, provided_type=Logger)],
exports=[Logger], # Export to make available to other modules
)
class InfrastructureModule:
pass
# Feature module for user management
@module(
imports=[InfrastructureModule], # Import dependencies from other modules
providers=[scoped(UserService)],
)
class UserModule:
pass
# Application root module
@module(imports=[UserModule])
class AppModule:
pass
async def main() -> None:
app = WakuFactory(AppModule).create()
async with app, app.container() as c:
user_service = await c.get(UserService)
user_id = await user_service.create_user('alice')
print(f'Created user with ID: {user_id}')
if __name__ == '__main__':
asyncio.run(main())
Want to learn more? Here's where to go next:
- Get familiar with module exports and imports
- Try different provider scopes
- Add CQRS for clean command handling
- Use extension hooks to customize your app
- Connect with your favorite framework
Check our Getting Started guide and browse the examples directory for inspiration.
- Create logo
- Improve inner architecture
- Improve documentation
- Add new and improve existing validation rules
- Provide example projects for common architectures
This project is licensed under the terms of the MIT License.
- Dishka β Dependency Injection framework powering
waku
IoC container. - NestJS β Primary inspiration for modular architecture, design patterns and some implementation details.
- MediatR (C#) β Inspiration and implementation details for the CQRS subsystem.