Skip to main content

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   ← 環境別サブパッケージを優先
2. config.staging
3. (同一モジュール).staging
4. tool.config           ← 環境変数なしと同じ検索
5. config
6. (同一モジュール)

使用例

# 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 サブパッケージ) ClassNameDefaultClassName の順
同一モジュール(フォールバック) DefaultClassName のみ

同一モジュールで ClassName を探さないのは、そのクラス自身が今まさに解決しようとしている抽象クラスだからです。

完全な検索順の例

@package('tool.config') @package('config') が指定された common.Database(抽象)を Environment=staging で resolve する場合:

 1. tool.config.staging.Database      ← 外部モジュール × env × 同名
 2. tool.config.staging.DefaultDatabase
 3. config.staging.Database
 4. config.staging.DefaultDatabase
 5. common.staging.Database
 6. common.staging.DefaultDatabase
 7. tool.config.Database              ← 外部モジュール × 同名
 8. tool.config.DefaultDatabase
 9. config.Database
10. config.DefaultDatabase
11. common.DefaultDatabase            ← 同一モジュールは DefaultXxx のみ

見つかったクラスが抽象クラスの場合はスキップして次の候補へ進みます。

明示的に register() または register_factory() した場合はすべての CoC より優先されます。

エラー

例外 発生条件
WithdicoImplementationException 抽象クラスに対応する DefaultXxx が見つからない
WithdicoCircularDependencyException 循環依存(A→B→A など)を検出した
TypeError コンストラクタ引数に型アノテーションがない

ライセンス

MIT License

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

withdico-0.1.0.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

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

withdico-0.1.0-py3-none-any.whl (7.9 kB view details)

Uploaded Python 3

File details

Details for the file withdico-0.1.0.tar.gz.

File metadata

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

File hashes

Hashes for withdico-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6097df761a2a41dea1ec9b96e22e9ecfe3ebb0eed23e0cd850b05c7c6fcbddbb
MD5 121ee878629cb563fa96cecc24429388
BLAKE2b-256 d4a46061c06bf4d4ba07e48fa8330d739e75340e29f8d7a5d3a948b68d2d526e

See more details on using hashes here.

Provenance

The following attestation bundles were made for withdico-0.1.0.tar.gz:

Publisher: publish.yml on amnz/Withdico

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

File details

Details for the file withdico-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: withdico-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for withdico-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 da711124d938be756358276c6028e99cb9c143f71c5c47ad8556b71c9575eb0e
MD5 6daf105b5c7d86ee7887dbec3deedbfb
BLAKE2b-256 4855a9078cc7172a35636f66fa7502cf9415f181088bc0e1d4528c944b5fa1ad

See more details on using hashes here.

Provenance

The following attestation bundles were made for withdico-0.1.0-py3-none-any.whl:

Publisher: publish.yml on amnz/Withdico

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