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. {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 サブパッケージ) ClassNameDefaultClassName の順
同一モジュール(最終フォールバック) 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') が指定された Databasecommon.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


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.2.tar.gz (14.7 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.2-py3-none-any.whl (8.6 kB view details)

Uploaded Python 3

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

Hashes for withdico-0.1.2.tar.gz
Algorithm Hash digest
SHA256 aaed473795f56722253e622894865f3b243efc854027e3a513ffcbef1b81c68c
MD5 7b0784d8f76258b6d506f340ffd41031
BLAKE2b-256 9680d805ee9df6bcde8e9bea2368b0e6a3843e664e67e30f9d8b4bb3509f3847

See more details on using hashes here.

Provenance

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

Hashes for withdico-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 e78f7dd2487ea1a6db0183bd937570a647c77eec1b694d2c381ec9c461f82321
MD5 54f0a867c7cda200b458a0f8bc004772
BLAKE2b-256 98904bae6aff40fa2b03433a8a7b8dc65ca6fe39970569b2938b636ae8c3a16d

See more details on using hashes here.

Provenance

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