diff --git a/docs/images/providers/callable.png b/docs/images/providers/callable.png deleted file mode 100644 index f34b2a6a..00000000 Binary files a/docs/images/providers/callable.png and /dev/null differ diff --git a/docs/images/providers/dependency.png b/docs/images/providers/dependency.png deleted file mode 100644 index dfd2f149..00000000 Binary files a/docs/images/providers/dependency.png and /dev/null differ diff --git a/docs/images/providers/singleton.png b/docs/images/providers/singleton.png deleted file mode 100644 index 06e8996f..00000000 Binary files a/docs/images/providers/singleton.png and /dev/null differ diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 6c48df01..942cf509 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -7,6 +7,12 @@ that were made in every particular version. From version 0.7.6 *Dependency Injector* framework strictly follows `Semantic versioning`_ +3.35.0 +------ +- Update documentation and rework examples for: ``Singleton``, ``Callable``, ``Coroutine``, + ``Object``, ``List``, ``Configuration``, ``Selector``, and ``Dependency`` providers. +- Fix mypy stub of the ``DeclarativeContainer`` to specify the ``__init__`` interface. + 3.34.0 ------ - Update ``Factory`` provider documentation. diff --git a/docs/providers/callable.rst b/docs/providers/callable.rst index 9c82c87f..e0cb5089 100644 --- a/docs/providers/callable.rst +++ b/docs/providers/callable.rst @@ -1,69 +1,20 @@ -Callable providers ------------------- +Callable provider +----------------- + +.. meta:: + :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Function,Method,Example + :description: Callable provider helps to make dependencies injection into functions. This page + demonstrates how to use a Callable provider. .. currentmodule:: dependency_injector.providers -:py:class:`Callable` provider calls wrapped callable on every call. +:py:class:`Callable` provider calls a function, a method or another callable. -Callable providers and injections -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:py:class:`Callable` provider takes a various number of positional and keyword -arguments that are used as wrapped callable injections. Every time, when -:py:class:`Callable` provider is called, positional and keyword argument -injections would be passed as callable arguments. - -Injections are done according to the next rules: - -+ All providers (instances of :py:class:`Provider`) are called every time - when injection needs to be done. -+ Providers could be injected "as is" (delegated), if it is defined obviously. - Check out :ref:`callable_providers_delegation`. -+ All other injectable values are provided *"as is"*. -+ Positional context arguments will be appended after :py:class:`Callable` - positional injections. -+ Keyword context arguments have priority on :py:class:`Callable` keyword - injections and will be merged over them. - -Example that shows usage of :py:class:`Callable` with positional argument -injections: - -.. literalinclude:: ../../examples/providers/callable_args.py +.. literalinclude:: ../../examples/providers/callable.py :language: python + :lines: 3- -Next one example shows usage of :py:class:`Callable` with keyword argument -injections: - -.. image:: /images/providers/callable.png - :width: 100% - :align: center - -.. literalinclude:: ../../examples/providers/callable_kwargs.py - :language: python - -.. _callable_providers_delegation: - -Callable providers delegation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:py:class:`Callable` provider could be delegated to any other provider via -any kind of injection. - -Delegation of :py:class:`Callable` providers is the same as -:py:class:`Factory` providers delegation, please follow -:ref:`factory_providers_delegation` section for examples (with exception -of using :py:class:`DelegatedCallable` instead of -:py:class:`DelegatedFactory`). - -Abstract callable providers -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:py:class:`AbstractCallable` provider is a :py:class:`Callable` provider that -must be explicitly overridden before calling. - -Behaviour of :py:class:`AbstractCallable` providers is the same as of -:py:class:`AbstractFactory`, please follow :ref:`abstract_factory_providers` -section for examples (with exception of using :py:class:`AbstractCallable` -provider instead of :py:class:`AbstractFactory`). +``Callable`` provider handles an injection of the dependencies the same way like a +:ref:`factory-provider`. .. disqus:: diff --git a/docs/providers/configuration.rst b/docs/providers/configuration.rst index dfa20539..43fd3ee3 100644 --- a/docs/providers/configuration.rst +++ b/docs/providers/configuration.rst @@ -1,5 +1,12 @@ -Configuration providers ------------------------ +Configuration provider +---------------------- + +.. meta:: + :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection, + Option,Ini,Json,Yaml,Dict,Environment Variable,Load,Read,Get + :description: Configuration provides configuration options to the other providers. This page + demonstrates how to use Configuration provider to inject the dependencies, load + a configuration from an ini or yaml file, dictionary or an environment variable. .. currentmodule:: dependency_injector.providers @@ -10,13 +17,13 @@ Configuration providers :emphasize-lines: 4,9-10 :lines: 4-14 -It implements "use first, define later" principle. +It implements the principle "use first, define later". -Loading from ``ini`` file -~~~~~~~~~~~~~~~~~~~~~~~~~ +Loading from an INI file +~~~~~~~~~~~~~~~~~~~~~~~~ -:py:class:`Configuration` provider can load configuration from ``ini`` file using -:py:meth:`Configuration.from_ini`: +``Configuration`` provider can load configuration from an ``ini`` file using the +:py:meth:`Configuration.from_ini` method: .. literalinclude:: ../../examples/providers/configuration/configuration_ini.py :language: python @@ -28,15 +35,15 @@ where ``examples/providers/configuration/config.ini`` is: .. literalinclude:: ../../examples/providers/configuration/config.ini :language: ini -:py:meth:`Configuration.from_ini` supports environment variables interpolation. Use -``${ENV_NAME}`` format in the configuration file to substitute value of environment +:py:meth:`Configuration.from_ini` method supports environment variables interpolation. Use +``${ENV_NAME}`` format in the configuration file to substitute value of the environment variable ``ENV_NAME``. -Loading from ``yaml`` file -~~~~~~~~~~~~~~~~~~~~~~~~~~ +Loading from a YAML file +~~~~~~~~~~~~~~~~~~~~~~~~ -:py:class:`Configuration` provider can load configuration from ``yaml`` file using -:py:meth:`Configuration.from_yaml`: +``Configuration`` provider can load configuration from a ``yaml`` file using the +:py:meth:`Configuration.from_yaml` method: .. literalinclude:: ../../examples/providers/configuration/configuration_yaml.py :language: python @@ -48,43 +55,51 @@ where ``examples/providers/configuration/config.yml`` is: .. literalinclude:: ../../examples/providers/configuration/config.yml :language: ini -:py:meth:`Configuration.from_yaml` supports environment variables interpolation. Use -``${ENV_NAME}`` format in the configuration file to substitute value of environment +:py:meth:`Configuration.from_yaml` method supports environment variables interpolation. Use +``${ENV_NAME}`` format in the configuration file to substitute value of the environment variable ``ENV_NAME``. .. note:: - Loading configuration from yaml requires ``PyYAML`` package. You can install - `Dependency Injector` with extras ``pip install dependency-injector[yaml]`` or install - ``PyYAML`` separately ``pip install pyyaml``. + Loading of a yaml configuration requires ``PyYAML`` package. -Loading from ``dict`` -~~~~~~~~~~~~~~~~~~~~~ + You can install the ``Dependency Injector`` with an extra dependency:: -:py:class:`Configuration` provider can load configuration from Python ``dict`` using -:py:meth:`Configuration.from_dict`: + pip install dependency-injector[yaml] + + or install ``PyYAML`` directly:: + + pip install pyyaml + + *Don't forget to mirror the changes in the requirements file.* + +Loading from a dictionary +~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Configuration`` provider can load configuration from a Python ``dict`` using the +:py:meth:`Configuration.from_dict` method: .. literalinclude:: ../../examples/providers/configuration/configuration_dict.py :language: python :lines: 3-5,6- :emphasize-lines: 6-13 -Loading from environment variable -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Loading from an environment variable +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:py:class:`Configuration` provider can load configuration from environment variable using -:py:meth:`Configuration.from_env`: +``Configuration`` provider can load configuration from an environment variable using the +:py:meth:`Configuration.from_env` method: .. literalinclude:: ../../examples/providers/configuration/configuration_env.py :language: python :lines: 5-7,13-21 :emphasize-lines: 6-8 -Loading from multiple sources -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Loading from the multiple sources +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:py:class:`Configuration` provider can load configuration from multiple sources. Loaded -configuration is merged recursively over existing configuration. +``Configuration`` provider can load configuration from the multiple sources. Loaded +configuration is merged recursively over the existing configuration. .. literalinclude:: ../../examples/providers/configuration/configuration_multiple.py :language: python @@ -96,12 +111,12 @@ where ``examples/providers/configuration/config.local.yml`` is: .. literalinclude:: ../../examples/providers/configuration/config.local.yml :language: ini -Specifying value type -~~~~~~~~~~~~~~~~~~~~~ +Specifying the value type +~~~~~~~~~~~~~~~~~~~~~~~~~ You can specify the type of the injected configuration value explicitly. -This helps when you read the value from the ini file or the environment variable and need to +This helps when you read the value from an ini file or an environment variable and need to convert it into an ``int`` or a ``float``. .. literalinclude:: ../../examples/providers/configuration/configuration_type.py @@ -109,20 +124,20 @@ convert it into an ``int`` or a ``float``. :lines: 3- :emphasize-lines: 17 -:py:class:`Configuration` provider has next helper methods: +``Configuration`` provider has next helper methods: - ``.as_int()`` - ``.as_float()`` - ``.as_(callback, *args, **kwargs)`` -The last method ``.as_(callback, *args, **kwargs)`` helps to implement a other conversions. +The last method ``.as_(callback, *args, **kwargs)`` helps to implement other conversions. .. literalinclude:: ../../examples/providers/configuration/configuration_type_custom.py :language: python :lines: 3- :emphasize-lines: 16 -With the ``.as_(callback, *args, **kwargs)`` you can specify the function that will be called +With the ``.as_(callback, *args, **kwargs)`` you can specify a function that will be called before the injection. The value from the config will be passed as a first argument. The returned value will be injected. Parameters ``*args`` and ``**kwargs`` are handled as any other injections. diff --git a/docs/providers/coroutine.rst b/docs/providers/coroutine.rst index 303e420e..3db7846a 100644 --- a/docs/providers/coroutine.rst +++ b/docs/providers/coroutine.rst @@ -1,72 +1,27 @@ -Coroutine providers -------------------- +Coroutine provider +------------------ + +.. meta:: + :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Coroutine,Asynchronous, + Asyncio,Example + :description: Coroutine provider creates a coroutine. This page demonstrates how to use a + Coroutine provider. .. currentmodule:: dependency_injector.providers -:py:class:`Coroutine` provider create wrapped coroutine on every call. +:py:class:`Coroutine` provider creates a coroutine. -:py:class:`Coroutine` provider is designed for making better integration with -``asyncio`` coroutines. In particular, :py:class:`Coroutine` provider returns -``True`` for ``asyncio.iscoroutinefunction()`` checks. - -.. note:: - - :py:class:`Coroutine` provider works only for Python 3.4+. - -Example of usage :py:class:`Coroutine` provider with ``async / await``-based -coroutine: - -.. literalinclude:: ../../examples/providers/coroutine_async_await.py +.. literalinclude:: ../../examples/providers/coroutine.py :language: python - -Coroutine providers and injections -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:py:class:`Coroutine` provider takes a various number of positional and keyword -arguments that are used as wrapped coroutine injections. Every time, when -:py:class:`Coroutine` provider is called, positional and keyword argument -injections would be passed as coroutine arguments. - -Injections are done according to the next rules: - -+ All providers (instances of :py:class:`Provider`) are called every time - when injection needs to be done. -+ Providers could be injected "as is" (delegated), if it is defined obviously. - Check out :ref:`coroutine_providers_delegation`. -+ All other injectable values are provided *"as is"*. -+ Positional context arguments will be appended after :py:class:`Coroutine` - positional injections. -+ Keyword context arguments have priority on :py:class:`Coroutine` keyword - injections and will be merged over them. + :lines: 3- .. note:: + The example works on Python 3.7+. For earlier versions use ``loop.run_until_complete()``. - Examples of making injections could be found in API docs - - :py:class:`Coroutine`. +``Coroutine`` provider handles an injection of the dependencies the same way like a +:ref:`factory-provider`. -.. _coroutine_providers_delegation: - -Coroutine providers delegation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:py:class:`Coroutine` provider could be delegated to any other provider via -any kind of injection. - -Delegation of :py:class:`Coroutine` providers is the same as -:py:class:`Factory` providers delegation, please follow -:ref:`factory_providers_delegation` section for examples (with exception -of using :py:class:`DelegatedCoroutine` instead of -:py:class:`DelegatedFactory`). - -Abstract coroutine providers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:py:class:`AbstractCoroutine` provider is a :py:class:`Coroutine` provider that -must be explicitly overridden before calling. - -Behaviour of :py:class:`AbstractCoroutine` providers is the same as of -:py:class:`AbstractFactory`, please follow :ref:`abstract_factory_providers` -section for examples (with exception of using :py:class:`AbstractCoroutine` -provider instead of :py:class:`AbstractFactory`). +.. note:: + ``Coroutine`` provider returns ``True`` for ``asyncio.iscoroutinefunction()`` check. .. disqus:: diff --git a/docs/providers/dependency.rst b/docs/providers/dependency.rst index 4bd9bf2f..9ce3977b 100644 --- a/docs/providers/dependency.rst +++ b/docs/providers/dependency.rst @@ -1,43 +1,21 @@ -Dependency providers --------------------- +Dependency provider +------------------- .. currentmodule:: dependency_injector.providers -:py:class:`Dependency` provider can be useful for development of -self-sufficient libraries / modules / applications that have required external -dependencies. +:py:class:`Dependency` provider is a placeholder for the dependency of the specified type. -For example, you have created self-sufficient library / module / application, -that has dependency on *database connection*. +The first argument of the ``Dependency`` provider specifies a type of the dependency. It is +called ``instance_of``. ``Dependency`` provider controls the type of the returned object to be an +instance of the ``instance_of`` type. -Second step you want to do is to make this software component to be easy -reusable by wide amount of developers and to be easily integrated into many -applications. - -It may be good idea, to move all external dependencies (like -*database connection*) to the top level and make them to be injected on your -software component's initialization. It will make third party developers feel -themselves free about integration of your component in their applications, -because they would be able to find right place / right way for doing this -in their application's architectures. - -At the same time, you can be sure, that your external dependency will be -satisfied with appropriate instance. - -Example: - -.. note:: - - Class ``UsersService`` is a part of some library. ``UsersService`` has - dependency on database connection, which can be satisfied with any - DBAPI 2.0 database connection. Being a self-sufficient library, - ``UsersService`` doesn't hardcode any kind of database management logic. - Instead of this, ``UsersService`` has external dependency, that has to - be satisfied by client's code, out of library's scope. - -.. image:: /images/providers/dependency.png +The ``Dependency`` provider must be overridden before usage. It can be overridden by any type of +the provider. The only rule is that overriding provider must return an instance of ``instance_of`` +dependency type. .. literalinclude:: ../../examples/providers/dependency.py :language: python + :lines: 3- + :emphasize-lines: 26 .. disqus:: diff --git a/docs/providers/factory.rst b/docs/providers/factory.rst index 7708f25e..6050b530 100644 --- a/docs/providers/factory.rst +++ b/docs/providers/factory.rst @@ -1,5 +1,7 @@ +.. _factory-provider: + Factory provider ----------------- +================ .. meta:: :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Factory,Abstract Factory, @@ -100,6 +102,8 @@ attribute of the provider that you're going to inject. .. note:: Any provider has a ``.provider`` attribute. +.. _factory-specialize-provided-type: + Specializing the provided type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -112,6 +116,8 @@ class attribute. :lines: 3- :emphasize-lines: 12-14 +.. _abstract-factory: + Abstract factory ~~~~~~~~~~~~~~~~ diff --git a/docs/providers/list.rst b/docs/providers/list.rst index 4b92eeca..9ad4ece4 100644 --- a/docs/providers/list.rst +++ b/docs/providers/list.rst @@ -1,5 +1,10 @@ -List providers --------------- +List provider +------------- + +.. meta:: + :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,List,Injection + :description: List provider helps to inject a list of the dependencies. This page demonstrates + how to use a List provider. .. currentmodule:: dependency_injector.providers @@ -7,28 +12,12 @@ List providers .. literalinclude:: ../../examples/providers/list.py :language: python - :emphasize-lines: 6-9 - :lines: 6-8, 23-29 - -:py:class:`List` provider is needed for injecting a list of dependencies. It handles -positional argument injections the same way as :py:class:`Factory` provider: - -+ All providers (instances of :py:class:`Provider`) are called every time - when injection needs to be done. -+ Providers could be injected "as is" (delegated), if it is defined explicitly. Check out - :ref:`factory_providers_delegation`. -+ All other values are injected *"as is"*. -+ Positional context arguments will be appended after :py:class:`List` positional injections. - -Full example: - -.. literalinclude:: ../../examples/providers/list.py - :language: python - :emphasize-lines: 23-26 :lines: 3- + :emphasize-lines: 19-22 + +``List`` provider handles positional arguments the same way as a :ref:`factory-provider`. .. note:: - - Keyword argument injections are not supported. + Keyword argument are not supported. .. disqus:: diff --git a/docs/providers/object.rst b/docs/providers/object.rst index 6599dd92..800b8116 100644 --- a/docs/providers/object.rst +++ b/docs/providers/object.rst @@ -1,14 +1,17 @@ -Object providers ----------------- +Object provider +--------------- + +.. meta:: + :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Object + :description: Object provider provides an object "as is". This page demonstrates how to use an + Object provider. .. currentmodule:: dependency_injector.providers -:py:class:`Object` provider returns provided instance "as is". - -Example: +:py:class:`Object` provider returns an object "as is". .. literalinclude:: ../../examples/providers/object.py :language: python - + :lines: 3- .. disqus:: diff --git a/docs/providers/selector.rst b/docs/providers/selector.rst index 29867217..f7b0a9e7 100644 --- a/docs/providers/selector.rst +++ b/docs/providers/selector.rst @@ -3,29 +3,31 @@ Selector providers ------------------ +.. meta:: + :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Configuration,Injection, + Selector,Polymorphism,Environment Variable,Flexibility + :description: Selector selects provider based on a configuration value or another callable. + This page demonstrates how to implement the polymorphism and increase the + flexibility of your application using the Selector provider. + .. currentmodule:: dependency_injector.providers -:py:class:`Selector` provider selects provider based on the configuration value or other callable. +:py:class:`Selector` provider selects provider based on a configuration value or another callable. .. literalinclude:: ../../examples/providers/selector.py :language: python - :emphasize-lines: 6-10 - :lines: 3-5,14-20 - -:py:class:`Selector` provider has a callable called ``selector`` and a dictionary of providers. - -The ``selector`` callable is provided as a first positional argument. It can be -:py:class:`Configuration` provider or any other callable. It has to return a string value. -This value is used as a key for selecting the provider from the dictionary of providers. - -The providers are provided as keyword arguments. Argument name is used as a key for -selecting the provider. - -Full example: - -.. literalinclude:: ../../examples/providers/selector.py - :language: python - :emphasize-lines: 14-18 :lines: 3- + :emphasize-lines: 14-18 + +The first argument of the ``Selector`` provider is called ``selector``. It can be an option of +a ``Configuration`` provider or any other callable. The ``selector`` callable has to return a +string value. This value is used as a key for selecting the provider. + +The providers are provided as keyword arguments. Argument name is used as a key for selecting the +provider. + +When a ``Selector`` provider is called, it gets a ``selector`` value and delegates the work to +the provider with a matching name. The ``selector`` callable works as a switch: when the returned +value is changed the ``Selector`` provider will delegate the work to another provider. .. disqus:: diff --git a/docs/providers/singleton.rst b/docs/providers/singleton.rst index f479770f..e8fe4452 100644 --- a/docs/providers/singleton.rst +++ b/docs/providers/singleton.rst @@ -1,111 +1,100 @@ -Singleton providers -------------------- +Singleton provider +------------------ + +.. meta:: + :keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Singleton,Pattern,Example, + Threads,Multithreading,Scoped + :description: Singleton provider helps to provide a single object. This page + demonstrates how to use a Singleton provider. It also provides the example + of using a singleton and thread locals singleton in the multi-threaded + environment. .. currentmodule:: dependency_injector.providers -:py:class:`Singleton` provider creates new instance of specified class on -first call and returns same instance on every next call. - -Example: - -.. image:: /images/providers/singleton.png - :width: 80% - :align: center +:py:class:`Singleton` provider provides single object. It memorizes the first created object and +returns it on the rest of the calls. .. literalinclude:: ../../examples/providers/singleton.py :language: python + :lines: 3- -Singleton providers resetting -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Created and memorized by :py:class:`Singleton` instance can be reset. Reset of -:py:class:`Singleton`'s memorized instance is done by clearing reference to -it. Further lifecycle of memorized instance is out of :py:class:`Singleton` -provider's control and depends on garbage collection strategy. - -Example: - -.. literalinclude:: ../../examples/providers/singleton_resetting.py - :language: python - -Singleton providers and injections -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:py:class:`Singleton` provider has same interface as :py:class:`Factory` -provider, so, all of the rules about injections are the same, as for -:py:class:`Factory` provider. +``Singleton`` provider handles an injection of the dependencies the same way like a +:ref:`factory-provider`. .. note:: - Due that :py:class:`Singleton` provider creates specified class instance - only on the first call, all injections are done once, during the first - call. Every next call, while instance has been already created - and memorized, no injections are done, :py:class:`Singleton` provider just - returns memorized earlier instance. + ``Singleton`` provider does dependencies injection only when creates the object. When the object + is created and memorized ``Singleton`` provider just returns it without applying the injections. - This may cause some problems, for example, in case of trying to bind - :py:class:`Factory` provider with :py:class:`Singleton` provider (provided - by dependent :py:class:`Factory` instance will be injected only once, - during the first call). Be aware that such behaviour was made with opened - eyes and is not a bug. +Specialization of the provided type and abstract singletons work the same like like for the +factories: - By the way, in such case, :py:class:`Delegate` or - :py:class:`DelegatedSingleton` provider can be useful - . It makes possible to inject providers *as is*. Please check out - `Singleton providers delegation`_ section. +- :ref:`factory-specialize-provided-type` +- :ref:`abstract-factory` -Singleton providers delegation -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Resetting memorized object +~~~~~~~~~~~~~~~~~~~~~~~~~~ -:py:class:`Singleton` provider could be delegated to any other provider via -any kind of injection. +To reset a memorized object you need to call the ``.reset()`` method of the ``Singleton`` +provider. -Delegation of :py:class:`Singleton` providers is the same as -:py:class:`Factory` providers delegation, please follow -:ref:`factory_providers_delegation` section for examples (with exception -of using :py:class:`DelegatedSingleton` instead of -:py:class:`DelegatedFactory`). +.. literalinclude:: ../../examples/providers/singleton_resetting.py + :language: python + :lines: 3- + :emphasize-lines: 14 -Singleton providers specialization -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. note:: + Resetting of the memorized object clears the reference to it. Further object's lifecycle is + managed by the garbage collector. -:py:class:`Singleton` provider could be specialized for any kind of needs via -declaring its subclasses. +Using singleton with multiple threads +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Specialization of :py:class:`Singleton` providers is the same as -:py:class:`Factory` providers specialization, please follow -:ref:`factory_providers_specialization` section for examples. +``Singleton`` provider is NOT thread-safe. You need to explicitly establish a synchronization for +using the ``Singleton`` provider in the multi-threading application. Otherwise you could trap +into the race condition problem: ``Singleton`` will create multiple objects. -Abstract singleton providers -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +There are two thread-safe singleton implementations out of the box: -:py:class:`AbstractSingleton` provider is a :py:class:`Singleton` provider that -must be explicitly overridden before calling. - -Behaviour of :py:class:`AbstractSingleton` providers is the same as of -:py:class:`AbstractFactory`, please follow :ref:`abstract_factory_providers` -section for examples (with exception of using :py:class:`AbstractSingleton` -provider instead of :py:class:`AbstractFactory`). - -Singleton providers and multi-threading -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:py:class:`Singleton` provider is NOT thread-safe and should be used in -multi-threading applications with manually controlled locking. - -:py:class:`ThreadSafeSingleton` is a thread-safe version of -:py:class:`Singleton` and could be used in multi-threading applications -without any additional locking. - -Also there could be a need to use thread-scoped singletons and there is a -special provider for such case - :py:class:`ThreadLocalSingleton`. -:py:class:`ThreadLocalSingleton` provider creates instance once for each -thread and returns it on every call. - -Example: ++ :py:class:`ThreadSafeSingleton` - is a thread-safe version of a ``Singleton`` provider. You can use + in multi-threading applications without additional synchronization. ++ :py:class:`ThreadLocalSingleton` - is a singleton provider that uses thread-locals as a storage. + This type of singleton will manage multiple objects - the one object for the one thread. .. literalinclude:: ../../examples/providers/singleton_thread_locals.py :language: python + :lines: 3- + :emphasize-lines: 11,12 +Implementing scopes +~~~~~~~~~~~~~~~~~~~ + +To implement a scoped singleton provider use a ``Singleton`` provider and reset its scope when +needed. + +.. literalinclude:: ../../examples/providers/singleton_scoped.py + :language: python + :lines: 3- + +The output should look like this (each request a ``Service`` object has a different address): + +.. code-block:: + + * Serving Flask app "singleton_scoped" (lazy loading) + * Environment: production + WARNING: This is a development server. Do not use it in a production deployment. + Use a production WSGI server instead. + * Debug mode: off + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + <__main__.Service object at 0x1099a9d90> + 127.0.0.1 - - [25/Aug/2020 17:33:11] "GET / HTTP/1.1" 200 - + <__main__.Service object at 0x1099a9cd0> + 127.0.0.1 - - [25/Aug/2020 17:33:17] "GET / HTTP/1.1" 200 - + <__main__.Service object at 0x1099a9d00> + 127.0.0.1 - - [25/Aug/2020 17:33:18] "GET / HTTP/1.1" 200 - + <__main__.Service object at 0x1099a9e50> + 127.0.0.1 - - [25/Aug/2020 17:33:18] "GET / HTTP/1.1" 200 - + <__main__.Service object at 0x1099a9d90> + 127.0.0.1 - - [25/Aug/2020 17:33:18] "GET / HTTP/1.1" 200 - .. disqus:: diff --git a/examples/providers/callable.py b/examples/providers/callable.py new file mode 100644 index 00000000..40e0d363 --- /dev/null +++ b/examples/providers/callable.py @@ -0,0 +1,18 @@ +"""`Callable` provider example.""" + +import passlib.hash + +from dependency_injector import providers + + +password_hasher = providers.Callable( + passlib.hash.sha256_crypt.hash, + salt_size=16, + rounds=10000, +) +password_verifier = providers.Callable(passlib.hash.sha256_crypt.verify) + + +if __name__ == '__main__': + hashed_password = password_hasher('super secret') + assert password_verifier('super secret', hashed_password) diff --git a/examples/providers/callable_args.py b/examples/providers/callable_args.py deleted file mode 100644 index a4292de4..00000000 --- a/examples/providers/callable_args.py +++ /dev/null @@ -1,16 +0,0 @@ -"""`Callable` providers with positional arguments example.""" - -import dependency_injector.providers as providers - - -# Creating even and odd filter providers: -even_filter = providers.Callable(filter, lambda x: x % 2 == 0) -odd_filter = providers.Callable(filter, lambda x: x % 2 != 0) - -# Creating even and odd ranges using range() and filter providers: -even_range = even_filter(range(1, 10)) -odd_range = odd_filter(range(1, 10)) - -# Making some asserts: -assert even_range == [2, 4, 6, 8] -assert odd_range == [1, 3, 5, 7, 9] diff --git a/examples/providers/callable_kwargs.py b/examples/providers/callable_kwargs.py deleted file mode 100644 index a6b89990..00000000 --- a/examples/providers/callable_kwargs.py +++ /dev/null @@ -1,16 +0,0 @@ -"""`Callable` providers with keyword arguments example.""" - -import passlib.hash - -import dependency_injector.providers as providers - - -# Password hasher and verifier providers: -password_hasher = providers.Callable(passlib.hash.sha256_crypt.encrypt, - salt_size=16, - rounds=10000) -password_verifier = providers.Callable(passlib.hash.sha256_crypt.verify) - -# Making some asserts: -hashed_password = password_hasher('super secret') -assert password_verifier('super secret', hashed_password) diff --git a/examples/providers/coroutine.py b/examples/providers/coroutine.py index 054173ca..98767295 100644 --- a/examples/providers/coroutine.py +++ b/examples/providers/coroutine.py @@ -1,26 +1,19 @@ -"""`Coroutine` providers example with @asyncio.coroutine decorator. - -Current example works only fot Python 3.4+. -""" +"""`Coroutine` providers example with async / await syntax.""" import asyncio -import dependency_injector.providers as providers +from dependency_injector import providers -@asyncio.coroutine -def coroutine_function(arg1, arg2): - """Sample coroutine function.""" - yield from asyncio.sleep(0.1) +async def coroutine(arg1, arg2): + await asyncio.sleep(0.1) return arg1, arg2 -coroutine_provider = providers.Coroutine(coroutine_function, arg1=1, arg2=2) +coroutine_provider = providers.Coroutine(coroutine, arg1=1, arg2=2) if __name__ == '__main__': - loop = asyncio.get_event_loop() - arg1, arg2 = loop.run_until_complete(coroutine_provider()) - + arg1, arg2 = asyncio.run(coroutine_provider()) assert (arg1, arg2) == (1, 2) assert asyncio.iscoroutinefunction(coroutine_provider) diff --git a/examples/providers/coroutine_async_await.py b/examples/providers/coroutine_async_await.py deleted file mode 100644 index cca13c26..00000000 --- a/examples/providers/coroutine_async_await.py +++ /dev/null @@ -1,25 +0,0 @@ -"""`Coroutine` providers example with async / await syntax. - -Current example works only fot Python 3.5+. -""" - -import asyncio - -import dependency_injector.providers as providers - - -async def coroutine_function(arg1, arg2): - """Sample coroutine function.""" - await asyncio.sleep(0.1) - return arg1, arg2 - - -coroutine_provider = providers.Coroutine(coroutine_function, arg1=1, arg2=2) - - -if __name__ == '__main__': - loop = asyncio.get_event_loop() - arg1, arg2 = loop.run_until_complete(coroutine_provider()) - - assert (arg1, arg2) == (1, 2) - assert asyncio.iscoroutinefunction(coroutine_provider) diff --git a/examples/providers/dependency.py b/examples/providers/dependency.py index a96aa58d..a1114cba 100644 --- a/examples/providers/dependency.py +++ b/examples/providers/dependency.py @@ -1,73 +1,50 @@ -"""`Dependency` providers example.""" +"""`Dependency` provider example.""" -import sqlite3 -import contextlib +import abc +import dataclasses -import dependency_injector.providers as providers +from dependency_injector import containers, providers, errors -class UsersService: - """Example class UsersService. - - UsersService has dependency on DBAPI 2.0 database connection. - """ - - def __init__(self, database): - """Initialize instance. - - :param database: Database connection. - :type database: sqlite3.dbapi2.Connection - """ - self.database = database - self.database.row_factory = sqlite3.dbapi2.Row - - def init_database(self): - """Initialize database, if it has not been initialized yet.""" - with contextlib.closing(self.database.cursor()) as cursor: - cursor.execute(""" - CREATE TABLE IF NOT EXISTS users( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(32) - ) - """) - - def create(self, name): - """Create user with provided name and return his id.""" - with contextlib.closing(self.database.cursor()) as cursor: - cursor.execute('INSERT INTO users(name) VALUES (?)', (name,)) - return cursor.lastrowid - - def get_by_id(self, id): - """Return user info by user id.""" - with contextlib.closing(self.database.cursor()) as cursor: - cursor.execute('SELECT id, name FROM users WHERE id=?', (id,)) - return cursor.fetchone() +class DbAdapter(metaclass=abc.ABCMeta): + ... -# Database and UsersService providers: -database = providers.Dependency(instance_of=sqlite3.dbapi2.Connection) -users_service_factory = providers.Factory(UsersService, - database=database) +class SqliteDbAdapter(DbAdapter): + ... -# Out of library's scope. -# -# Setting database provider: -database.provided_by(providers.Singleton(sqlite3.dbapi2.Connection, - database=':memory:', - timeout=30, - detect_types=True, - isolation_level='EXCLUSIVE')) -# Creating UsersService instance: -users_service = users_service_factory() +class PostgresDbAdapter(DbAdapter): + ... -# Initializing UsersService database: -users_service.init_database() -# Creating test user and retrieving full information about him: -test_user_id = users_service.create(name='test_user') -test_user = users_service.get_by_id(test_user_id) +@dataclasses.dataclass +class UserService: + database: DbAdapter -# Making some asserts: -assert test_user['id'] == 1 -assert test_user['name'] == 'test_user' + +class Container(containers.DeclarativeContainer): + + database = providers.Dependency(instance_of=DbAdapter) + + user_service = providers.Factory( + UserService, + database=database, + ) + + +if __name__ == '__main__': + container1 = Container(database=providers.Singleton(SqliteDbAdapter)) + container2 = Container(database=providers.Singleton(PostgresDbAdapter)) + + assert isinstance(container1.user_service().database, SqliteDbAdapter) + assert isinstance(container2.user_service().database, PostgresDbAdapter) + + container3 = Container(database=providers.Singleton(object)) + try: + container3.user_service() + except errors.Error as exception: + print(exception) + # The output is: + # is not an + # instance of diff --git a/examples/providers/factory.py b/examples/providers/factory.py index 33453dd3..c83ded02 100644 --- a/examples/providers/factory.py +++ b/examples/providers/factory.py @@ -1,4 +1,4 @@ -"""`Factory` providers example.""" +"""`Factory` provider example.""" from dependency_injector import providers diff --git a/examples/providers/factory_delegation.py b/examples/providers/factory_delegation.py index f6bc02d1..00f3fec5 100644 --- a/examples/providers/factory_delegation.py +++ b/examples/providers/factory_delegation.py @@ -1,4 +1,4 @@ -"""`Factory` providers delegation example.""" +"""`Factory` provider delegation example.""" from typing import Callable, List diff --git a/examples/providers/factory_init_injections.py b/examples/providers/factory_init_injections.py index 682ace80..d720edb6 100644 --- a/examples/providers/factory_init_injections.py +++ b/examples/providers/factory_init_injections.py @@ -1,4 +1,4 @@ -"""`Factory` providers init injections example.""" +"""`Factory` provider init injections example.""" from dependency_injector import providers diff --git a/examples/providers/factory_init_injections_underlying.py b/examples/providers/factory_init_injections_underlying.py index cf49388e..5c9f196f 100644 --- a/examples/providers/factory_init_injections_underlying.py +++ b/examples/providers/factory_init_injections_underlying.py @@ -1,4 +1,4 @@ -"""`Factory` providers - building a complex object graph with deep init injections example.""" +"""`Factory` provider - passing injections to the underlying providers example.""" from dependency_injector import providers diff --git a/examples/providers/list.py b/examples/providers/list.py index cbf33bd6..1d2eb474 100644 --- a/examples/providers/list.py +++ b/examples/providers/list.py @@ -8,15 +8,11 @@ from dependency_injector import providers @dataclasses.dataclass class Module: - """Example module.""" - name: str @dataclasses.dataclass class Dispatcher: - """Example dispatcher.""" - modules: List[Module] @@ -28,6 +24,7 @@ dispatcher_factory = providers.Factory( ), ) + if __name__ == '__main__': dispatcher = dispatcher_factory() @@ -35,11 +32,10 @@ if __name__ == '__main__': assert dispatcher.modules[0].name == 'm1' assert dispatcher.modules[1].name == 'm2' - # Call of dispatcher_factory() is equivalent to: - - dispatcher = Dispatcher( - modules=[ - Module(name='m1'), - Module(name='m2'), - ], - ) + # Call "dispatcher = dispatcher_factory()" is an equivalent for: + # dispatcher = Dispatcher( + # modules=[ + # Module(name='m1'), + # Module(name='m2'), + # ], + # ) diff --git a/examples/providers/object.py b/examples/providers/object.py index f3fa6693..7da9cb26 100644 --- a/examples/providers/object.py +++ b/examples/providers/object.py @@ -1,10 +1,10 @@ -"""Object providers example.""" +"""`Object` provider example.""" -import dependency_injector.providers as providers +from dependency_injector import providers -# Creating object provider: object_provider = providers.Object(1) -# Making some asserts: -assert object_provider() == 1 + +if __name__ == '__main__': + assert object_provider() == 1 diff --git a/examples/providers/selector.py b/examples/providers/selector.py index 9c3036bc..7c7c17cd 100644 --- a/examples/providers/selector.py +++ b/examples/providers/selector.py @@ -19,10 +19,11 @@ selector = providers.Selector( another=providers.Factory(SomeOtherClass), ) -config.override({'one_or_another': 'one'}) -instance_1 = selector() -assert isinstance(instance_1, SomeClass) +if __name__ == '__main__': + config.override({'one_or_another': 'one'}) + instance_1 = selector() + assert isinstance(instance_1, SomeClass) -config.override({'one_or_another': 'another'}) -instance_2 = selector() -assert isinstance(instance_2, SomeOtherClass) + config.override({'one_or_another': 'another'}) + instance_2 = selector() + assert isinstance(instance_2, SomeOtherClass) diff --git a/examples/providers/singleton.py b/examples/providers/singleton.py index 97fe5c02..9e94b76c 100644 --- a/examples/providers/singleton.py +++ b/examples/providers/singleton.py @@ -1,19 +1,16 @@ -"""`Singleton` providers example.""" +"""`Singleton` provider example.""" -import collections - -import dependency_injector.providers as providers +from dependency_injector import providers -UsersService = collections.namedtuple('UsersService', []) +class UserService: + ... -# Singleton provider creates new instance of specified class on first call -# and returns same instance on every next call. -users_service_provider = providers.Singleton(UsersService) -# Retrieving several UserService objects: -users_service1 = users_service_provider() -users_service2 = users_service_provider() +user_service_provider = providers.Singleton(UserService) -# Making some asserts: -assert users_service1 is users_service2 + +if __name__ == '__main__': + user_service1 = user_service_provider() + user_service2 = user_service_provider() + assert user_service1 is user_service2 diff --git a/examples/providers/singleton_provided_type.py b/examples/providers/singleton_provided_type.py deleted file mode 100644 index 1402fd5c..00000000 --- a/examples/providers/singleton_provided_type.py +++ /dev/null @@ -1,35 +0,0 @@ -"""`Singleton` specialization for limitation to provided type example.""" - -import dependency_injector.providers as providers -import dependency_injector.errors as errors - - -class BaseService: - """Base service class.""" - - -class UsersService(BaseService): - """Users service.""" - - -class PhotosService(BaseService): - """Photos service.""" - - -class ServiceProvider(providers.Singleton): - """Service provider.""" - - provided_type = BaseService - - -# Creating several service providers with BaseService instances: -users_service_provider = ServiceProvider(UsersService) -photos_service_provider = ServiceProvider(PhotosService) - -# Trying to create service provider with not a BaseService instance: -try: - some_service_provider = ServiceProvider(object) -except errors.Error as exception: - print(exception) - # can provide only - # instances diff --git a/examples/providers/singleton_resetting.py b/examples/providers/singleton_resetting.py index 8fe2f5a7..edec0594 100644 --- a/examples/providers/singleton_resetting.py +++ b/examples/providers/singleton_resetting.py @@ -1,27 +1,19 @@ -"""`Singleton` providers resetting example.""" +"""`Singleton` provider resetting example.""" -import collections - -import dependency_injector.providers as providers +from dependency_injector import providers -UsersService = collections.namedtuple('UsersService', []) +class UserService: + ... -# Users service singleton provider: -users_service_provider = providers.Singleton(UsersService) -# Retrieving several UsersService objects: -users_service1 = users_service_provider() -users_service2 = users_service_provider() +user_service_provider = providers.Singleton(UserService) -# Making some asserts: -assert users_service1 is users_service2 -# Resetting of memorized instance: -users_service_provider.reset() +if __name__ == '__main__': + user_service1 = user_service_provider() -# Retrieving one more UserService object: -users_service3 = users_service_provider() + user_service_provider.reset() -# Making some asserts: -assert users_service3 is not users_service1 + users_service2 = user_service_provider() + assert users_service2 is not user_service1 diff --git a/examples/providers/singleton_scoped.py b/examples/providers/singleton_scoped.py new file mode 100644 index 00000000..3ab27beb --- /dev/null +++ b/examples/providers/singleton_scoped.py @@ -0,0 +1,32 @@ +"""`Singleton` - flask request scope example.""" + +from dependency_injector import providers +from flask import Flask + + +class Service: + ... + + +service_provider = providers.Singleton(Service) + + +def index_view(): + service_1 = service_provider() + service_2 = service_provider() + assert service_1 is service_2 + print(service_1) + return 'Hello World!' + + +def teardown_context(request): + service_provider.reset() + return request + + +app = Flask(__name__) +app.add_url_rule('/', 'index', view_func=index_view) +app.after_request(teardown_context) + +if __name__ == '__main__': + app.run() diff --git a/examples/providers/singleton_thread_locals.py b/examples/providers/singleton_thread_locals.py index 7c1a0aa3..83325559 100644 --- a/examples/providers/singleton_thread_locals.py +++ b/examples/providers/singleton_thread_locals.py @@ -1,53 +1,42 @@ -"""`ThreadLocalSingleton` providers example.""" +"""`ThreadLocalSingleton` provider example.""" import threading import queue -import dependency_injector.providers as providers +from dependency_injector import providers -def example(example_object, queue_object): - """Put provided object in the provided queue.""" +def put_in_queue(example_object, queue_object): queue_object.put(example_object) -# Create thread-local singleton provider for some object (main thread): thread_local_object = providers.ThreadLocalSingleton(object) - -# Create singleton provider for thread-safe queue: -queue_factory = providers.ThreadSafeSingleton(queue.Queue) - -# Create callable provider for example(), inject dependencies: -example = providers.DelegatedCallable( - example, +queue_provider = providers.ThreadSafeSingleton(queue.Queue) +put_in_queue = providers.Callable( + put_in_queue, example_object=thread_local_object, - queue_object=queue_factory, + queue_object=queue_provider, +) +thread_factory = providers.Factory( + threading.Thread, + target=put_in_queue.provider, ) -# Create factory for threads that are targeted to execute example(): -thread_factory = providers.Factory(threading.Thread, target=example) if __name__ == '__main__': - # Create 10 threads for concurrent execution of example(): threads = [] for thread_number in range(10): threads.append( thread_factory(name='Thread{0}'.format(thread_number)), ) - - # Start execution of all created threads: for thread in threads: thread.start() - - # Wait while threads would complete their work: for thread in threads: thread.join() - # Making some asserts (main thread): all_objects = set() - - while not queue_factory().empty(): - all_objects.add(queue_factory().get()) + while not queue_provider().empty(): + all_objects.add(queue_provider().get()) assert len(all_objects) == len(threads) # Queue contains same number of objects as number of threads where diff --git a/src/dependency_injector/__init__.py b/src/dependency_injector/__init__.py index ecd68dbc..1462af0f 100644 --- a/src/dependency_injector/__init__.py +++ b/src/dependency_injector/__init__.py @@ -1,6 +1,6 @@ """Dependency injector top-level package.""" -__version__ = '3.34.0' +__version__ = '3.35.0' """Version number that follows semantic versioning. :type: str diff --git a/src/dependency_injector/containers.pyi b/src/dependency_injector/containers.pyi index 9c2798fb..62117b43 100644 --- a/src/dependency_injector/containers.pyi +++ b/src/dependency_injector/containers.pyi @@ -24,6 +24,7 @@ class DynamicContainer(Container): ... class DeclarativeContainer(Container): cls_providers: ClassVar[Dict[str, Provider]] inherited_providers: ClassVar[Dict[str, Provider]] + def __init__(self, **overriding_providers: Provider) -> None: ... def override(container: Container) -> _Callable[[Container], Container]: ...