Canary Framework
Dependency injection and lifecycle for plain Python classes — standard library only, zero dependencies.
中文 · Documentation · Changelog
Install
pip install canary-framework
Requires Python 3.12 or newer. Installing pulls in no third-party packages.
The model
Subclass Canary and you have a unit: it declares what it depends on with dep(), and
what it does in each phase with @init / @start / @stop. Start one unit and its
dependencies come up in dependency order; leaving reclaims them in reverse.
import asyncio
from canary_framework import Canary, dep, init, start, stop
class Config(Canary):
@init
def load(self) -> None:
self.dsn = "postgresql://localhost/dev"
class Database(Canary):
config = dep(Config)
@start
async def connect(self) -> None:
print(f"connecting to {self.config.dsn}")
@stop
async def close(self) -> None:
print("disconnected")
class UserService(Canary):
database = dep(Database)
async def main() -> None:
async with UserService() as service:
print(service.database.config.dsn)
asyncio.run(main())
connecting to postgresql://localhost/dev
postgresql://localhost/dev
disconnected
Two rules
Advancing recurses along dependencies: a unit enters a phase only after its dependencies have completed it. One unit runs one phase exactly once no matter how many units depend on it, and independent dependencies advance concurrently.
Unwinding is linear: a dependency graph is not a tree, so reclamation runs over a ledger in reverse entry order.
init / start / stop are three names for these two rules. Adding a fourth phase is one
line: Phase("migrate", after=init).
Highlights
- Typed end to end.
self.configis aConfig,async with serviceyields your type, anddep(SomethingElse)is a type error. No plugin required. - Plain classes. Decorators only mark methods; units stay subclassable, mixable, nestable,
and lifecycle methods can be overridden with
super(). - Failure paths are part of the design. A failing
start()reclaims what started;stop()is the single reclamation path, shared by success and failure, and is idempotent. A stopped graph starts again; a failed advance can be retried. - Test doubles without mocks.
scope_of(service).provide(Database, FakeDatabase())swaps a dependency across the whole graph, and the double runs its own lifecycle. - Concurrent by default. Independent units advance together, scheduled by dependency.
- Zero dependencies. A test asserts that a full lifecycle imports nothing from site-packages.
Hosting
The framework knows nothing about shells — HTTP, CLI, schedulers and consumers are all yours:
@asynccontextmanager
async def lifespan(_app: FastAPI):
async with service:
yield
app = FastAPI(lifespan=lifespan)
Stability
1.0 is the first stable release. The public API follows Semantic Versioning: no breaking changes before 2.0, and removals are deprecated for at least one minor release first.
Documentation
- Documentation — What's new in 1.0, patterns, upgrading from 0.9.x
- A complete five-layer example:
examples/library/
Community
Questions and ideas go to Discussions, bugs to Issues. See CONTRIBUTING, GOVERNANCE and SECURITY.
License
Apache-2.0. See LICENSE.
Release files for canary-framework 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| canary_framework-1.0.0.tar.gz | 80.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| canary_framework-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 106.5 kB
Release files / canary_framework-1.0.0.tar.gz
| Download URL | canary_framework-1.0.0.tar.gz |
|---|---|
| Size | 80.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
769bf3cf3cbeb075a94859694b867f760966a8a0981273af1688c7a78e38b709
|
|
BLAKE2b-256 checksum How to use checksums |
b0f00a8e443d5c9e863e78ce409c3404dd68632ff1e20fa48d5f1c8da2de17bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency logRelease files / canary_framework-1.0.0-py3-none-any.whl
| Download URL | canary_framework-1.0.0-py3-none-any.whl |
|---|---|
| Size | 26.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a489a8f67d4b8b93e13446d1447f94af8dcb77b8ff60073d921d5caa94eb6f2a
|
|
BLAKE2b-256 checksum How to use checksums |
7cf11c5751e4a0828fd3ee48bef6902784f581508c4597ca5ebc321c43850eb7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency log