Skip to main content

Python framework for backends that grow

Project description

waku logo

waku [ or わく] means framework in Japanese.


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

PyPI Python version Downloads

uv Ruff ty pyrefly mypy - checked

Telegram Ask DeepWiki


Python makes it easy to build a backend. waku makes it easy to keep growing one.

As your project scales, problems creep in: services import each other freely, swapping a database means editing dozens of files, and nobody can tell which module depends on what. waku gives you modules with explicit boundaries, type-safe DI powered by dishka, and integrated CQRS and event sourcing — so your codebase stays manageable as it scales.

[!TIP] Check out the full documentation and our examples to get started.

The Problem

Python has no built-in way to enforce component boundaries. Packages don't control visibility, imports aren't validated, and nothing stops module A from reaching into the internals of module B. As a project grows, what started as clean separation quietly becomes a web of implicit dependencies — where testing requires the whole system, onboarding means reading everything, and changing one module risks breaking three others.

What waku gives you

Structure

  • 🧩 Package by component: Each module is a self-contained unit with its own providers. Explicit imports and exports control what crosses boundaries — validated at startup, not discovered in production.
  • 💉 Dependency inversion: Define interfaces in your application core, bind adapters in infrastructure modules. Swap a database, a cache, or an API client by changing one provider — powered by dishka.
  • 🔌 One core, any entrypoint: Build your module tree once with WakuFactory. Plug it into FastAPI, Litestar, FastStream, Aiogram, CLI, or workers — same logic everywhere.

Capabilities

  • 📨 Messaging: DI alone doesn't decouple components — you need events. The message bus dispatches commands, queries, and events so components never reference each other directly. Pipeline behaviors handle cross-cutting concerns.
  • 📜 Event sourcing: Aggregates, projections, snapshots, upcasting, and the decider pattern with built-in SQLAlchemy adapters.
  • 🧪 Testing: Override any provider in tests with override(), or spin up a minimal app with create_test_app().
  • 🧰 Lifecycle & extensions: Hook into startup, shutdown, and module initialization. Add validation, logging, or custom behaviors — decoupled from your business logic.

Quick Start

Installation

uv add waku

Minimal Example

Define a service, register it in a module, and resolve it from the container:

import asyncio

from waku import WakuFactory, module
from waku.di import scoped


class GreetingService:
    async def greet(self, name: str) -> str:
        return f'Hello, {name}!'


@module(providers=[scoped(GreetingService)])
class GreetingModule:
    pass


@module(imports=[GreetingModule])
class AppModule:
    pass


async def main() -> None:
    app = WakuFactory(AppModule).create()

    async with app, app.container() as c:
        svc = await c.get(GreetingService)
        print(await svc.greet('waku'))


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

Module Boundaries in Action

Modules control visibility. InfrastructureModule exports ILoggerUserModule imports it. Dependencies are explicit, not implicit:

import asyncio
from typing import Protocol

from waku import WakuFactory, module
from waku.di import scoped, singleton


class ILogger(Protocol):
    async def log(self, message: str) -> None: ...


class ConsoleLogger(ILogger):
    async def log(self, message: str) -> None:
        print(f'[LOG] {message}')


class UserService:
    def __init__(self, logger: ILogger) -> 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


@module(
    providers=[singleton(ILogger, ConsoleLogger)],
    exports=[ILogger],
)
class InfrastructureModule:
    pass


@module(
    imports=[InfrastructureModule],
    providers=[scoped(UserService)],
)
class UserModule:
    pass


@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

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 – Inspiration for modular architecture and design patterns.
  • Wolverine (C#) – Inspiration for messaging, transport model, and pipeline architecture.
  • Marten (C#) – Inspiration for event sourcing, projections, and document store design.
  • Emmett – Functional-first event sourcing patterns.
  • Eventuous – Event store interface design.
  • Jérémie Chassaing – Decider pattern formalization.

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

waku-0.43.0.tar.gz (65.6 kB view details)

Uploaded Source

Built Distribution

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

waku-0.43.0-py3-none-any.whl (116.0 kB view details)

Uploaded Python 3

File details

Details for the file waku-0.43.0.tar.gz.

File metadata

  • Download URL: waku-0.43.0.tar.gz
  • Upload date:
  • Size: 65.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for waku-0.43.0.tar.gz
Algorithm Hash digest
SHA256 eefc015daa6dbf36d160b97c54b29ccc9b56fbfc0bc00efc5d315d3e0cd6269a
MD5 d2feda0bb28537c8ed8e4d0666e1ffea
BLAKE2b-256 c658055cd6d3089c27985d09b461f0b095e51b476261a98b11279ff9d045f1e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for waku-0.43.0.tar.gz:

Publisher: release.yml on waku-py/waku

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file waku-0.43.0-py3-none-any.whl.

File metadata

  • Download URL: waku-0.43.0-py3-none-any.whl
  • Upload date:
  • Size: 116.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for waku-0.43.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8890fcb6f93dfc3c09a6f06388a66d72d5e22adc72d9b098c819c6a3b41096d5
MD5 e006c3030c58844dd42dfdac2921cf56
BLAKE2b-256 47e8869837b65ef2ac73bccbd2676f40fd3be417bde5bb84e7603db0b1303590

See more details on using hashes here.

Provenance

The following attestation bundles were made for waku-0.43.0-py3-none-any.whl:

Publisher: release.yml on waku-py/waku

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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