Skip to content

waku

waku [ or わく] means framework in Japanese.


CI/CD codecov GitHub issues GitHub contributors GitHub commit activity GitHub license

PyPI Python version Downloads

uv Ruff mypy - checked basedpyright - checked

Telegram


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.

Why waku?

Who is it for?

  • 👥 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

Quick Start

Installation

uv add waku
# or
pip install waku

Understanding the Basics

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.

Basic Example

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())

More Realistic Example

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())

Next Steps

Want to learn more? Here's where to go next:

Check our Getting Started guide and browse the examples directory for inspiration.

Documentation

Contributing

Top contributors

contrib.rocks image

Roadmap

  • Create logo
  • Improve inner architecture
  • Improve documentation
  • Add new and improve existing validation rules
  • Provide example projects for common architectures

Support

License

This project is licensed under the terms of the MIT License.

Acknowledgements

  • 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.