😕

Python Dependency Injector: Configuration初期化の罠

2022/10/13に公開

Configurationプロバイダについて

過去の記事[1] [2] でも紹介しているPythonのDIライブラリ、 Dependency Injector です。

Configurationプロバイダにより、JSON・YAML・INI・環境変数等の設定値を読み込むことができます。
https://python-dependency-injector.ets-labs.org/providers/configuration.html#loading-from-an-environment-variable

以下ではJSONを例にとって進めます。読み込み方には2通りあります。

conf.json
{
    "message": "Hello world!"
}
main.py
from dependency_injector import containers, providers


class Container(containers.DeclarativeContainer):
    config1 = providers.Configuration(json_files=["conf.json"])
    config2 = providers.Configuration()
    config2.from_json("conf.json")


if __name__ == '__main__':
    container = Container()
    print(container.config1.message())  # Hello world!
    print(container.config2.message())  # Hello world!

疑問

ところが、プロバイダをContainerに入れずに書いてみると、片方だけ動かなくなります

main.py
from dependency_injector import containers, providers


config1 = providers.Configuration(json_files=["conf.json"])
config2 = providers.Configuration()
config2.from_json("conf.json")
print(config1.message())  # None
print(config2.message())  # Hello world!

克服

load を呼びましょう。
https://python-dependency-injector.ets-labs.org/api/providers.html#dependency_injector.providers.Configuration.load

Load configuration.
This method loads configuration from configuration files or pydantic settings that were set earlier with set_*() methods or provided to the init(), e.g.:

json_files= yaml_files= ini_files= pydantic_settings= のいずれも同様で、これらを使う場合はload()をして初めて読み込まれます。
from_json from_yaml from_ini from_pydantic についてはload不要で即時に読み込まれる仕様のようです。

main.py
from dependency_injector import containers, providers


config1 = providers.Configuration(json_files=["conf.json"])
config1.load()  # !!!
config2 = providers.Configuration()
config2.from_json("conf.json")
print(config1.message())  # Hello world!
print(config2.message())  # Hello world!

Containerにプロバイダを入れた場合、コンテナの初期化時に自動的にloadしてくれるそうで、それで一見奇怪なこの挙動になります。

以上は私が本家GitHubのissueに問い合わせて理解したものです。
https://github.com/ets-labs/python-dependency-injector/issues/564

余談: やりたかったこと

設定値によって注入するオブジェクトを変更するようなことをしたい場合、コンテナ内でConfigurationから値を読みたいことがあります。その際loadにつまづくかもしれません。

from dependency_injector import containers, providers


class Container(containers.DeclarativeContainer):
    config = providers.Configuration(json_files=["conf.json"])
    config.load()
    
    if config.target() == "Production":
        s3_factory = providers.Factory(S3Client)
    elif config.target() == "Development":
        s3_factory = providers.Factory(MockS3Client)
脚注
  1. FastAPIでDIをする (Dependency Injectorを使う) https://zenn.dev/shimat/articles/4be773f427c502 ↩︎

  2. FastAPI + Dependency Injector において Configuration をモックする https://zenn.dev/shimat/articles/d566561e37ceda ↩︎

Discussion