Skip to main content

Lightweight decorator-driven Python async service framework with dependency injection

Project description

Canary Framework

Lightweight Python Async Service Framework — Decorator-Driven, Annotation-Based DI

License Python CI GitHub Stars


Canary Framework is a decorator-driven async service framework for Python. Core philosophy: Services are the smallest unit, modules compose services, and modules themselves are services.

Core Features

  • Decorator-Driven — Use @service, @module, @router decorators, zero inheritance required
  • Annotation-Based DI — Declare dependencies with type annotations: db: DatabaseService, no boilerplate
  • Topological Startup — Kahn's algorithm ensures dependencies start first
  • Lifecycle Management@after_config/@after_init/@before_startup/@before_shutdown hooks
  • ASGI Compatible — Built on Starlette, works with uvicorn and other ASGI servers
  • Modular Architecture — Hierarchical composition with nested modules
  • OpenAPI Support — Auto-generated Swagger UI and ReDoc documentation

Installation

pip install canary-framework

Quick Start

from canary_framework import module, service, router, get, post, after_config

@service()
class DatabaseService:
    @after_config
    async def connect(self):
        self.conn = "connected"

@service()
class UserService:
    db: DatabaseService

    async def get_user(self, user_id: int):
        return {"id": user_id, "name": "Alice"}

@router(prefix="/api", tags=["users"])
class ApiRouter:
    user_service: UserService

    @get("/users/{user_id}")
    async def get_user(self, user_id: int) -> dict:
        return self.user_service.get_user(user_id)

    @post("/users")
    async def create_user(self, body: dict) -> dict:
        return {"id": 1, **body}

@module(services=[DatabaseService, UserService, ApiRouter])
class App:
    pass

# Run with uvicorn
# uvicorn main:App --host 0.0.0.0 --port 8000 --reload

Web Example with OpenAPI

from canary_framework import module, router, get, post
from pydantic import BaseModel, Field

class UserRequest(BaseModel):
    name: str = Field(description="User name")
    email: str = Field(description="User email")

class UserResponse(BaseModel):
    id: int
    name: str
    email: str

@router(prefix="/users", tags=["Users"])
class UsersRouter:
    @get("/", summary="List users", description="Get all users")
    async def list_users(self) -> list[UserResponse]:
        return []

    @post("/",
          summary="Create user",
          description="Create a new user",
          request_model=UserRequest,
          response_model=UserResponse)
    async def create_user(self, user: UserRequest) -> UserResponse:
        return UserResponse(id=1, name=user.name, email=user.email)

@module(services=[UsersRouter])
class App:
    pass

OpenAPI Documentation

Access automatically generated documentation:

  • Swagger UI: http://localhost:8000/docs
  • ReDoc: http://localhost:8000/redoc
  • OpenAPI JSON: http://localhost:8000/openapi.json

Architecture

src/canary_framework/
├── common/              # Shared infrastructure
│   ├── errors.py        # Framework exceptions
│   ├── markers.py       # Metadata markers, resolve_deps()
│   ├── routing.py       # Route path parsing
│   └── types.py         # Data classes and type aliases
├── core/                # Base classes
│   ├── module.py        # ModuleBase — orchestration and DI
│   ├── service.py       # ServiceBase — lifecycle management
│   └── router.py        # RouterBase — ASGI routing
├── decorators/          # Decorator implementations
│   ├── module.py        # @module
│   ├── service.py       # @service
│   ├── router.py        # @router, @get/@post/...
│   └── lifecycle.py     # @after_config, @after_init, etc.
└── engine/              # Runtime engine
    ├── registry.py      # Service registry
    ├── injector.py      # Topological sort
    ├── hooks.py         # Lifecycle hook discovery
    ├── openapi.py       # OpenAPI schema generation
    ├── utils.py         # make_subclass()
    └── logging.py       # Framework logging

Dependency Injection Flow

@service() class MyService:
    db: DatabaseService      ←  1. User declares dependency via annotation

    ↓ configure phase

resolve_deps(MyService)
    → get_type_hints() reads {db: DatabaseService}
    → filters by CF_SERVICE_MARKER
    → returns {"db": DatabaseService}

    ↓ registration: recursively registers DatabaseService
    ↓ topological_sort: build dependency graph
    ↓ instantiation: creates instances in order
    ↓ wiring:

setattr(instance, "db", db_instance)   ←  3. Injected with annotation key name

Lifecycle Flow

app.configure(config_instance)
  ├── Register all services + transitive deps
  ├── Topological sort (Kahn's algorithm)
  ├── Instantiate services
  ├── Inject dependencies (annotation-driven)
  ├── Call configure() on each service (topological order)
  └── Invoke @after_config hooks

app.init()
  ├── Invoke @after_init hook
  └── Call init() on each service (topological order)

app.startup()
  ├── Invoke @before_startup hook
  └── Call startup() on each service (topological order)

app.shutdown()
  ├── Invoke @before_shutdown hook
  └── Call shutdown() on each service (reverse topological order)

Testing

# Run all tests
pytest

# Run unit tests
pytest tests/unit/

# Run integration tests
pytest tests/integration/

Community

Contributing

See CONTRIBUTING.md.

License

Apache 2.0 · Copyright 2026 Zhang Wenbo (Canary)

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

canary_framework-0.4.10.tar.gz (83.8 kB view details)

Uploaded Source

Built Distribution

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

canary_framework-0.4.10-py3-none-any.whl (38.9 kB view details)

Uploaded Python 3

File details

Details for the file canary_framework-0.4.10.tar.gz.

File metadata

  • Download URL: canary_framework-0.4.10.tar.gz
  • Upload date:
  • Size: 83.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for canary_framework-0.4.10.tar.gz
Algorithm Hash digest
SHA256 c7ab97dc01dba7641ed103d6cc4eccaa62857e9a9d15e91b7a71e3108d5cb8a5
MD5 9b9843c9e3aa98e17c2bdba7092b57e0
BLAKE2b-256 862022e019a41420f9df10b9614fa634cd6d691e3b8667acc626fac975cf1b1a

See more details on using hashes here.

Provenance

The following attestation bundles were made for canary_framework-0.4.10.tar.gz:

Publisher: publish.yml on HotcocoaCanary/Canary-Framework

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

File details

Details for the file canary_framework-0.4.10-py3-none-any.whl.

File metadata

File hashes

Hashes for canary_framework-0.4.10-py3-none-any.whl
Algorithm Hash digest
SHA256 e068d347112e2361d138cf1c12f4201f5b4ce841cc31df625f778a3a59f32ab0
MD5 f1eda14376cccb8defe5270cc425cf9c
BLAKE2b-256 795cf1afc6135031a35653cc4256a578ab1620a3899e2791f1824e64229a3aec

See more details on using hashes here.

Provenance

The following attestation bundles were made for canary_framework-0.4.10-py3-none-any.whl:

Publisher: publish.yml on HotcocoaCanary/Canary-Framework

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