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.impl   ← 環境別 impl サブパッケージを最優先
 2. tool.config.staging
 3. config.staging.impl
 4. config.staging
 5. (同一モジュール).staging.impl
 6. (同一モジュール).staging
 7. tool.config.impl           ← 環境変数なし
 8. tool.config
 9. config.impl
10. config
11. (同一モジュール).impl
12. (同一モジュール)

使用例

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

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

各パスに対して .impl サブパッケージが自動的に検索対象に追加されます。 インターフェースと同じパッケージ階層に impl サブパッケージを作って実装クラスを置くスタイルに対応します。

common/
├── database.py       # Database(ABC) を定義
└── impl/
    └── database.py   # Database または DefaultDatabase を実装

完全な検索順の例

@package('tool.config') @package('config') が指定された 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
10. common.staging.impl.DefaultDatabase
11. common.staging.Database
12. common.staging.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                   ← 同一モジュール × impl
22. common.impl.DefaultDatabase
23. 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.1.tar.gz (14.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.1-py3-none-any.whl (8.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: withdico-0.1.1.tar.gz
  • Upload date:
  • Size: 14.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.1.tar.gz
Algorithm Hash digest
SHA256 ae9117cb44cf14e418c76c57fac1975dff42ab4012e243a6c16e89586f9b44d5
MD5 2533ac3f42c54715597fad7d09a7ff1a
BLAKE2b-256 222c728ba25ea176dbb85537e87c9aa71f50d33a0765e306e9201c96db4bedc8

See more details on using hashes here.

Provenance

The following attestation bundles were made for withdico-0.1.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: withdico-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 8.3 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2db27abefb5db19bc1863b3a6ae72a8976f7942614162dc8f4e94dde221d647e
MD5 0b69584da4cf1d4be69f5bba1ab3a1b5
BLAKE2b-256 f5c4f7288f8ef3f527737b447ef10f2625b1814ff80ae1cb910cc0c76d141129

See more details on using hashes here.

Provenance

The following attestation bundles were made for withdico-0.1.1-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