Lightweight dependency injection container for Python with Convention over Configuration
Project description
withdico
Python 向けの軽量な依存性注入(DI)コンテナです。 Convention over Configuration(CoC)により、設定ファイル不要でクラスを自動解決します。
インストール
pip install withdico
# または
uv add withdico
基本的な使い方
1. 抽象クラスと実装クラスを定義する
命名規則:抽象クラス Xxx に対して、実装クラスは DefaultXxx とします。
抽象クラスと実装クラスは同じモジュールに配置します。
from abc import ABC, abstractmethod
from withdico import resolve
class Greeter(ABC):
@abstractmethod
def greet(self, name: str) -> str: ...
class DefaultGreeter(Greeter):
def greet(self, name: str) -> str:
return f"Hello, {name}!"
class App:
def __init__(self, greeter: Greeter) -> None:
self.greeter = greeter
# Greeter → DefaultGreeter を自動解決し、App のコンストラクタに注入
app = resolve(App)
app.greeter.greet("World") # "Hello, World!"
コンストラクタの引数は型アノテーションを元に再帰的に解決されます(オートワイヤリング)。
型アノテーションが付いていないコンストラクタ引数がある場合は TypeError が発生します。
2. シングルトン
同じ型を複数回 resolve しても、同一インスタンスが返ります。
app1 = resolve(App)
app2 = resolve(App)
assert app1 is app2 # True
3. ファクトリー登録
構築ロジックが複雑な場合や、起動時の環境変数に依存する場合は register_factory を使います。
ファクトリーは最初の resolve 時に一度だけ呼ばれ、結果はシングルトンとしてキャッシュされます。
import os
from withdico import Withdico, resolve
Withdico.register_factory(Config, lambda: Config(path=os.environ["CONFIG_PATH"]))
config = resolve(Config) # このときはじめてファクトリーが呼ばれる
resolve(Config) is config # True(以降はキャッシュを返す)
4. テスト時のモック差し替え
from withdico import Withdico
class MockGreeter(Greeter):
def greet(self, name: str) -> str:
return "mocked!"
Withdico.register(Greeter, MockGreeter())
Withdico.unregister(App) # App のキャッシュをクリアして再生成させる
app = resolve(App)
app.greeter.greet("World") # "mocked!"
テスト間でシングルトンをすべてリセットしたい場合:
Withdico.reset()
5. 複数インスタンスの管理(name 引数)
同じ型に対して複数のインスタンスを名前で使い分けることができます。
Withdico.register(Greeter, JaGreeter(), name="ja")
Withdico.register(Greeter, EnGreeter(), name="en")
ja = Withdico.resolve(Greeter, name="ja")
en = Withdico.resolve(Greeter, name="en")
@package デコレーター
抽象クラスに @package デコレーターを付けることで、実装クラスの検索先モジュールを指定できます。
同一モジュールより優先して検索されます。
from withdico import package
@package('myproject.api')
class Greeter(ABC):
@abstractmethod
def greet(self, name: str) -> str: ...
myproject.api モジュール内の DefaultGreeter が優先して使用されます。
複数指定
複数の @package を重ねて指定でき、**記述順(上が優先)**に検索されます。
@package('myproject.api') # 1番目に検索
@package('myproject.impl') # 2番目に検索
class Greeter(ABC):
...
# 検索順: myproject.api → myproject.impl → 同一モジュール(フォールバック)
Environment 環境変数による環境別切り替え
環境変数 Environment を設定すると、各検索先パッケージのサブパッケージが優先して検索されます。
開発・ステージング・本番など環境ごとに実装を切り替えるために使用します。
Environment=staging python main.py
@package('tool.config') @package('config') が指定されていて Environment=staging の場合、
以下の順で DefaultXxx クラスを検索します:
1. tool.config.staging.impl ← 環境別 impl を最優先
2. tool.config.staging
3. config.staging.impl
4. config.staging
5. {parent}.staging.impl.{leaf} ← 同一モジュールの兄弟 env × impl
6. {parent}.staging.{leaf} ← 同一モジュールの兄弟 env
7. tool.config.impl ← 環境変数なし
8. tool.config
9. config.impl
10. config
11. {parent}.impl.{leaf} ← 同一モジュールの兄弟 impl
12. (同一モジュール) ← DefaultXxx のみ
使用例
# myproject/config/staging/greeter.py
class DefaultGreeter(Greeter):
def greet(self, name: str) -> str:
return "Staging environment!"
# myproject/service.py
@package('myproject.config')
class Greeter(ABC):
@abstractmethod
def greet(self, name: str) -> str: ...
# ステージング環境で起動
Environment=staging python main.py
# → myproject.config.staging.DefaultGreeter が使用される
テスト分離(TEST_TOKEN)
環境変数 TEST_TOKEN を設定すると、シングルトンのキーにトークンが付加されます。
並列テストなど、テスト間でシングルトンを分離したい場合に使用します。
# pytest の場合
def test_a(monkeypatch):
monkeypatch.setenv("TEST_TOKEN", "test_a")
svc = resolve(MyService) # test_a 専用のインスタンス
def test_b(monkeypatch):
monkeypatch.setenv("TEST_TOKEN", "test_b")
svc = resolve(MyService) # test_b 専用のインスタンス(test_a とは別)
API リファレンス
| API | 説明 |
|---|---|
resolve(Type) |
シングルトン取得(CoC で自動生成) |
Withdico.resolve(Type, name) |
resolve() と同じ(name 指定可) |
Withdico.register(Type, instance, name) |
インスタンスを事前登録 |
Withdico.register_factory(Type, factory, name) |
ファクトリー関数を登録(初回 resolve 時に呼び出し) |
Withdico.unregister(Type, name) |
シングルトン/ファクトリーの登録を解除 |
Withdico.is_registered(Type, name) |
シングルトンまたはファクトリーとして登録済みか確認 |
Withdico.try_resolve(Type, name, if_not_registered) |
未登録なら None またはフォールバック |
Withdico.reset() |
全シングルトン/ファクトリーをリセット |
@package(module_path) |
実装クラスの検索先モジュールを指定(複数可) |
resolve の優先順位
register() で登録済み → register_factory() で登録済み → CoC(DefaultXxx 自動解決)
Convention over Configuration
抽象クラスを resolve() すると、実装クラスを自動的に探して生成します。
検索ルール
| 検索先 | 探すクラス名 |
|---|---|
外部モジュール(@package 指定 / env / impl サブパッケージ) |
ClassName → DefaultClassName の順 |
| 同一モジュール(最終フォールバック) | DefaultClassName のみ |
同一モジュールで ClassName を探さないのは、そのクラス自身が今まさに解決しようとしている抽象クラスだからです。
インターフェース定義モジュールの兄弟 impl パッケージが自動的に検索対象に追加されます。
{parent}.impl.{leaf} の形式で、通常の Python パッケージ構造にそのまま対応します。
app/services/
├── user_service.py # app.services.user_service: UserService(ABC) を定義
├── staging/
│ ├── impl/
│ │ └── user_service.py # app.services.staging.impl.user_service (env × impl)
│ └── user_service.py # app.services.staging.user_service (env)
└── impl/
└── user_service.py # app.services.impl.user_service (impl)
完全な検索順の例
@package('tool.config') @package('config') が指定された Database(common.database モジュールで定義)を Environment=staging で resolve する場合:
1. [tool.config.staging.impl] Database ← 外部モジュール × env × impl
2. [tool.config.staging.impl] DefaultDatabase
3. [tool.config.staging ] Database ← 外部モジュール × env
4. [tool.config.staging ] DefaultDatabase
5. [config.staging.impl ] Database
6. [config.staging.impl ] DefaultDatabase
7. [config.staging ] Database
8. [config.staging ] DefaultDatabase
9. [common.staging.impl.database] Database ← 兄弟モジュール × env × impl
10. [common.staging.impl.database] DefaultDatabase
11. [common.staging.database ] Database ← 兄弟モジュール × env
12. [common.staging.database ] DefaultDatabase
13. [tool.config.impl ] Database ← 外部モジュール × impl
14. [tool.config.impl ] DefaultDatabase
15. [tool.config ] Database ← 外部モジュール
16. [tool.config ] DefaultDatabase
17. [config.impl ] Database
18. [config.impl ] DefaultDatabase
19. [config ] Database
20. [config ] DefaultDatabase
21. [common.impl.database ] Database ← 兄弟モジュール × impl
22. [common.impl.database ] DefaultDatabase
23. [common.database ] DefaultDatabase ← 同一モジュール(DefaultXxx のみ)
[モジュール] は検索対象のモジュール、その右がモジュール内で探すクラス名を表します。
見つかったクラスが抽象クラスの場合はスキップして次の候補へ進みます。
明示的に register() または register_factory() した場合はすべての CoC より優先されます。
エラー
| 例外 | 発生条件 |
|---|---|
WithdicoImplementationException |
抽象クラスに対応する DefaultXxx が見つからない |
WithdicoCircularDependencyException |
循環依存(A→B→A など)を検出した |
TypeError |
コンストラクタ引数に型アノテーションがない |
ライセンス
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file withdico-0.1.2.tar.gz.
File metadata
- Download URL: withdico-0.1.2.tar.gz
- Upload date:
- Size: 14.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aaed473795f56722253e622894865f3b243efc854027e3a513ffcbef1b81c68c
|
|
| MD5 |
7b0784d8f76258b6d506f340ffd41031
|
|
| BLAKE2b-256 |
9680d805ee9df6bcde8e9bea2368b0e6a3843e664e67e30f9d8b4bb3509f3847
|
Provenance
The following attestation bundles were made for withdico-0.1.2.tar.gz:
Publisher:
publish.yml on amnz/Withdico
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
withdico-0.1.2.tar.gz -
Subject digest:
aaed473795f56722253e622894865f3b243efc854027e3a513ffcbef1b81c68c - Sigstore transparency entry: 1706730957
- Sigstore integration time:
-
Permalink:
amnz/Withdico@d64cf35484f13b92b54cb94117c4eaedf812daed -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/amnz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d64cf35484f13b92b54cb94117c4eaedf812daed -
Trigger Event:
push
-
Statement type:
File details
Details for the file withdico-0.1.2-py3-none-any.whl.
File metadata
- Download URL: withdico-0.1.2-py3-none-any.whl
- Upload date:
- Size: 8.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e78f7dd2487ea1a6db0183bd937570a647c77eec1b694d2c381ec9c461f82321
|
|
| MD5 |
54f0a867c7cda200b458a0f8bc004772
|
|
| BLAKE2b-256 |
98904bae6aff40fa2b03433a8a7b8dc65ca6fe39970569b2938b636ae8c3a16d
|
Provenance
The following attestation bundles were made for withdico-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on amnz/Withdico
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
withdico-0.1.2-py3-none-any.whl -
Subject digest:
e78f7dd2487ea1a6db0183bd937570a647c77eec1b694d2c381ec9c461f82321 - Sigstore transparency entry: 1706730997
- Sigstore integration time:
-
Permalink:
amnz/Withdico@d64cf35484f13b92b54cb94117c4eaedf812daed -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/amnz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d64cf35484f13b92b54cb94117c4eaedf812daed -
Trigger Event:
push
-
Statement type: