diff --git a/README.rst b/README.rst index 61cff790..a7347028 100644 --- a/README.rst +++ b/README.rst @@ -52,7 +52,7 @@ What is ``Dependency Injector``? ``Dependency Injector`` is a dependency injection framework for Python. -It helps you implement the dependency injection principle. +It helps you in implementing the dependency injection principle. What is dependency injection? ----------------------------- @@ -70,6 +70,9 @@ Before: .. code-block:: python + import os + + class ApiClient: def __init__(self): @@ -82,10 +85,18 @@ Before: def __init__(self): self.api_client = ApiClient() + + if __name__ == '__main__': + service = Service() + + After: .. code-block:: python + import os + + class ApiClient: def __init__(self, api_key: str, timeout: int): @@ -98,55 +109,82 @@ After: def __init__(self, api_client: ApiClient): self.api_client = api_client -Who creates the objects now? Look at the next section. + + if __name__ == '__main__': + service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT'))) + + +Flexibility comes with a price: now you need to assemble your objects like this +``Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT')))``. The assembly code might get +duplicated and it'll become harder to change the application structure. What does Dependency Injector do? --------------------------------- -``Dependency Injector`` provides you with the container and the providers that help you build -your application objects when you apply dependency injection principle: +``Dependency Injector`` helps you assemble the objects. + +It provides you the container and the providers that help you describe objects assembly. When you +need an object you get it from the container. The rest of the assembly work is done by the +framework: .. code-block:: python - from dependency_injector import containers, providers + from dependency_injector import containers, providers - class ApiClient: + class ApiClient: - def __init__(self, api_key: str, timeout: int): - self.api_key = api_key - self.timeout = timeout + def __init__(self, api_key: str, timeout: int): + self.api_key = api_key + self.timeout = timeout - class Service: + class Service: - def __init__(self, api_client: ApiClient): - self.api_client = api_client + def __init__(self, api_client: ApiClient): + self.api_client = api_client - class Container(containers.DeclarativeContainer): + class Container(containers.DeclarativeContainer): - config = providers.Configuration() + config = providers.Configuration() - api_client = providers.Singleton( - ApiClient, - api_key=config.api_key, - timeout=config.timeout, - ) + api_client = providers.Singleton( + ApiClient, + api_key=config.api_key, + timeout=config.timeout, + ) - service = providers.Factory( - Service, - api_client=api_client, - ) + service = providers.Factory( + Service, + api_client=api_client, + ) - if __name__ == '__main__': - container = Container() - container.config.from_yaml('config.yml') + if __name__ == '__main__': + container = Container() + container.config.api_key.from_env('API_KEY') + container.config.timeout.from_env('TIMEOUT') - service = container.service() + service = container.service() + assert isinstance(service.api_client, ApiClient) - assert isinstance(service.api_client, ApiClient) +Retrieving of the ``Service`` instance now is done like this ``container.service()``. + +Also ``Dependency Injector`` provides a bonus in overriding any of the providers with the +``.override()`` method: + +.. code-block:: python + + from unittest import mock + + + with container.api_client.override(mock.Mock()): + service = container.service() + assert isinstance(service.api_client, mock.Mock) + +It helps in a testing. Also you can use it for configuring project for the different environments: +replace an API client with a stub on the dev or stage. `More examples `_ diff --git a/docs/containers/declarative.rst b/docs/containers/declarative.rst index 045e9c6e..5f362b31 100644 --- a/docs/containers/declarative.rst +++ b/docs/containers/declarative.rst @@ -1,55 +1,43 @@ -Declarative containers ----------------------- +Declarative container +--------------------- .. currentmodule:: dependency_injector.containers -:py:class:`DeclarativeContainer` is inversion of control container that -could be defined in declarative manner. It should cover most of the cases -when list of providers that would be included in container is deterministic -(container will not change its structure in runtime). +:py:class:`DeclarativeContainer` is a class-based style of the providers definition. -Declarative containers have to extend base declarative container class - -:py:class:`dependency_injector.containers.DeclarativeContainer`. - -Declarative container's providers have to be defined like container's class -attributes. Every provider in container has name. This name should follow -``some_provider`` convention, that is standard naming convention for -attribute names in Python. - -.. note:: - - Declarative containers have several features that could be useful - for some kind of operations on container's providers, please visit API - documentation for getting full list of features - - :py:class:`dependency_injector.containers.DeclarativeContainer`. - -Here is an simple example of defining declarative container with several -factories: - -.. image:: /images/containers/declarative.png - :width: 85% - :align: center +You create the declarative container subclass, put the providers as attributes and create the +container instance. .. literalinclude:: ../../examples/containers/declarative.py :language: python + :lines: 3- -Example of declarative containers inheritance: +The declarative container providers should only be used when you have the container instance. +Working with the providers of the container on the class level will influence all further +instances. -.. image:: /images/containers/declarative_inheritance.png - :width: 100% - :align: center +The declarative container can not have any methods or any other attributes then providers. + +The container class provides next attributes: + +- ``providers`` - the dictionary of all the container providers +- ``cls_providers`` - the dictionary of the container providers of the current container +- ``inherited_providers`` - the dictionary of all the inherited container providers .. literalinclude:: ../../examples/containers/declarative_inheritance.py :language: python + :lines: 3- -Example of declarative containers's provider injections: - -.. image:: /images/containers/declarative_injections.png - :width: 100% - :align: center +Injections in the declarative container are done the usual way: .. literalinclude:: ../../examples/containers/declarative_injections.py :language: python + :lines: 3- +You can override the container providers when you create the container instance: + +.. literalinclude:: ../../examples/containers/declarative_override_providers.py + :language: python + :lines: 3- .. disqus:: diff --git a/docs/containers/dynamic.rst b/docs/containers/dynamic.rst index bd879f23..856ad7c4 100644 --- a/docs/containers/dynamic.rst +++ b/docs/containers/dynamic.rst @@ -1,28 +1,25 @@ -Dynamic containers ------------------- +Dynamic container +----------------- .. currentmodule:: dependency_injector.containers -:py:class:`DynamicContainer` is an inversion of control container with dynamic -structure. It should cover most of the cases when list of providers that -would be included in container is non-deterministic and depends on -application's flow or its configuration (container's structure could be -determined just after application will be started and will do some initial -work, like parsing list of container's providers from the configuration). +:py:class:`DynamicContainer` is a collection of the providers defined in the runtime. -While :py:class:`DeclarativeContainer` acts on class-level, -:py:class:`DynamicContainer` does the same on instance-level. - -Here is an simple example of defining dynamic container with several factories: +You create the dynamic container instance and put the providers as attributes. .. literalinclude:: ../../examples/containers/dynamic.py :language: python + :lines: 3- -Next example demonstrates creation of dynamic container based on some -configuration: +The dynamic container is good for the case when your application structure depends on the +configuration file or some other source that you can reach only after application is already +running (database, api, etc). + +In this example we use the configuration to fill in the dynamic container with the providers: .. literalinclude:: ../../examples/containers/dynamic_runtime_creation.py :language: python - + :lines: 3- .. disqus:: + diff --git a/docs/containers/index.rst b/docs/containers/index.rst index 93bee78a..510d4d02 100644 --- a/docs/containers/index.rst +++ b/docs/containers/index.rst @@ -1,21 +1,16 @@ -IoC Containers -============== +Containers +========== -Containers are collections of providers. Main purpose of containers is to group -providers. +Containers are collections of the providers. -There are, actually, several popular cases of containers usage: +There are several use cases how you can use containers: -+ Keeping all providers in a single container. -+ Grouping of providers from the same architectural layer (for example, ++ Keeping all the providers in a single container (most common). ++ Grouping of the providers from the same architectural layer (for example, ``Services``, ``Models`` and ``Forms`` containers). + Grouping of providers from the same functional groups (for example, - container ``Users``, that contains all functional parts of ``Users`` - component). - -Also, for both of these and some other cases, it might be useful to attach -some init / shutdown functionality or something else, that deals with group -of providers. + container ``Users``, that contains all functional parts of the ``users`` + package). Containers package API docs - :py:mod:`dependency_injector.containers`. diff --git a/docs/containers/overriding.rst b/docs/containers/overriding.rst index 729a8b1c..cf5627af 100644 --- a/docs/containers/overriding.rst +++ b/docs/containers/overriding.rst @@ -1,41 +1,24 @@ -Overriding of containers ------------------------- +Overriding of the container +--------------------------- .. currentmodule:: dependency_injector.containers -Containers can be overridden by other containers. This, actually, means that -all of the providers from overriding container will override providers with -the same names in overridden container. +The container can be overridden by the other container. All of the providers from the overriding +container will override the providers with the same names in the overridden container. -There are two ways to override :py:class:`DeclarativeContainer` with another -container: - -- Use :py:meth:`DeclarativeContainer.override` method. -- Use :py:func:`override` class decorator. - -Example of overriding container using :py:meth:`DeclarativeContainer.override` -method: - -.. literalinclude:: ../../examples/containers/override_declarative.py +.. literalinclude:: ../../examples/containers/override.py :language: python + :lines: 3- -Example of overriding container using :py:func:`override` decorator: +It helps in a testing. Also you can use it for configuring project for the different +environments: replace an API client with a stub on the dev or stage. -.. literalinclude:: ../../examples/containers/override_declarative_decorator.py - :language: python +The container also has: -Also there are several useful :py:class:`DeclarativeContainer` methods and -properties that help to work with container overridings: - -- :py:attr:`DeclarativeContainer.overridden` - tuple of all overriding - containers. -- :py:meth:`DeclarativeContainer.reset_last_overriding()` - reset last - overriding provider for each container providers. -- :py:meth:`DeclarativeContainer.reset_override()` - reset all overridings - for each container providers. - -:py:class:`DynamicContainer` has exactly the same functionality, except of -:py:func:`override` decorator. +- ``container.overridden`` - tuple of all overriding containers. +- ``container.reset_last_overriding()`` - reset last overriding for each provider in the container. +- ``container.reset_override()`` - reset all overriding in the container. +:py:class:`DynamicContainer` has the same functionality. .. disqus:: diff --git a/docs/containers/specialization.rst b/docs/containers/specialization.rst index 1c2619f1..724d0bd1 100644 --- a/docs/containers/specialization.rst +++ b/docs/containers/specialization.rst @@ -1,25 +1,25 @@ -Specialization of containers ----------------------------- +Specialization of the container provider type +--------------------------------------------- .. currentmodule:: dependency_injector.containers -:py:class:`DeclarativeContainer` could be specialized for any kind of needs -via declaring its subclasses. - -One of such `builtin` features is a limitation for providers type. - -Next example shows usage of this feature with :py:class:`DeclarativeContainer` -in couple with feature of :py:class:`dependency_injector.providers.Factory` -for limitation of its provided type: +You can make a restriction of the :py:class:`DeclarativeContainer` provider type: .. literalinclude:: ../../examples/containers/declarative_provider_type.py :language: python + :lines: 3- + :emphasize-lines: 29-31 -Limitation for providers type could be used with :py:class:`DynamicContainer` -as well: +The emphasized lines will cause an error because ``other_provider`` is not a subtype of the +``ServiceProvider``. This helps to control the content of the container. + +The same works for the :py:class:`DynamicContainer`: .. literalinclude:: ../../examples/containers/dynamic_provider_type.py :language: python + :lines: 3- + :emphasize-lines: 23 +The emphasized line will also cause an error. .. disqus:: diff --git a/docs/images/containers/declarative.png b/docs/images/containers/declarative.png deleted file mode 100644 index 11b0297d..00000000 Binary files a/docs/images/containers/declarative.png and /dev/null differ diff --git a/docs/images/containers/declarative_inheritance.png b/docs/images/containers/declarative_inheritance.png deleted file mode 100644 index 1e0eb1f4..00000000 Binary files a/docs/images/containers/declarative_inheritance.png and /dev/null differ diff --git a/docs/images/containers/declarative_injections.png b/docs/images/containers/declarative_injections.png deleted file mode 100644 index 36454e5b..00000000 Binary files a/docs/images/containers/declarative_injections.png and /dev/null differ diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 403e11a0..1061b171 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -7,6 +7,11 @@ that were made in every particular version. From version 0.7.6 *Dependency Injector* framework strictly follows `Semantic versioning`_ +3.30.3 +------ +- Update README. +- Update containers documentation and examples. + 3.30.2 ------ - Update README. diff --git a/examples/containers/declarative.py b/examples/containers/declarative.py index f095d5e9..4fb7d795 100644 --- a/examples/containers/declarative.py +++ b/examples/containers/declarative.py @@ -1,23 +1,23 @@ -"""Declarative IoC container simple example.""" +"""Declarative container example.""" -import dependency_injector.containers as containers -import dependency_injector.providers as providers +from dependency_injector import containers, providers -# Defining declarative IoC container: class Container(containers.DeclarativeContainer): - """Example IoC container.""" factory1 = providers.Factory(object) factory2 = providers.Factory(object) -# Creating some objects: -object1 = Container.factory1() -object2 = Container.factory2() +if __name__ == '__main__': + container = Container() -# Making some asserts: -assert object1 is not object2 -assert isinstance(object1, object) -assert isinstance(object2, object) + object1 = container.factory1() + object2 = container.factory2() + + print(container.providers) + # { + # 'factory1': can contain only - # instances +class ServiceProvider(providers.Factory): - try: - class _SequenceContainer2(SequencesContainer): - object_provider = SequenceProvider(object) - except errors.Error as exception: - print(exception) - # can provide only - # instances + provided_type = Service - class _SequenceContaier3(SequencesContainer): - list_provider = SequenceProvider(list) - assert _SequenceContaier3.list_provider() == list() +class ServiceContainer(containers.DeclarativeContainer): + + provider_type = ServiceProvider + + +class MyServices(ServiceContainer): + + user_service = ServiceProvider(UserService) + + +class ImproperServices(ServiceContainer): + + other_provider = providers.Factory(object) diff --git a/examples/containers/dynamic.py b/examples/containers/dynamic.py index cf33dc58..678351d6 100644 --- a/examples/containers/dynamic.py +++ b/examples/containers/dynamic.py @@ -1,18 +1,18 @@ -"""Dynamic container simple example.""" +"""Dynamic container example.""" -import dependency_injector.containers as containers -import dependency_injector.providers as providers +from dependency_injector import containers, providers -# Defining dynamic container: -container = containers.DynamicContainer() -container.factory1 = providers.Factory(object) -container.factory2 = providers.Factory(object) +if __name__ == '__main__': + container = containers.DynamicContainer() + container.factory1 = providers.Factory(object) + container.factory2 = providers.Factory(object) -# Creating some objects: -object1 = container.factory1() -object2 = container.factory2() + object1 = container.factory1() + object2 = container.factory2() -# Making some asserts: -assert object1 is not object2 -assert isinstance(object1, object) and isinstance(object2, object) + print(container.providers) + # { + # 'factory1': can contain only - # instances +class ServiceProvider(providers.Factory): - try: - sequences_container.object_provider = SequenceProvider(object) - except errors.Error as exception: - print(exception) - # can provide only - # instances + provided_type = Service - sequences_container.list_provider = SequenceProvider(list) - assert sequences_container.list_provider() == list() +services = containers.DynamicContainer() +services.provider_type = ServiceProvider + +services.user_service = ServiceProvider(UserService) +services.other_provider = providers.Factory(object) diff --git a/examples/containers/dynamic_runtime_creation.py b/examples/containers/dynamic_runtime_creation.py index 2a82cb03..f95114fa 100644 --- a/examples/containers/dynamic_runtime_creation.py +++ b/examples/containers/dynamic_runtime_creation.py @@ -1,59 +1,40 @@ -"""Creation of dynamic container based on some configuration example.""" +"""Creation of dynamic container based on the configuration example.""" -import collections - -import dependency_injector.containers as containers +from dependency_injector import containers, providers -# Defining several example services: -UsersService = collections.namedtuple('UsersService', []) -AuthService = collections.namedtuple('AuthService', []) +class UserService: + ... -def import_cls(cls_name): - """Import class by its fully qualified name. - - In terms of current example it is just a small helper function. Please, - don't use it in production approaches. - """ - path_components = cls_name.split('.') - module = __import__('.'.join(path_components[:-1]), - locals(), - globals(), - fromlist=path_components[-1:]) - return getattr(module, path_components[-1]) +class AuthService: + ... -# "Parsing" some configuration: -config = { - 'services': { - 'users': { - 'class': '__main__.UsersService', - 'provider_class': 'dependency_injector.providers.Factory', +def populate_container(container, providers_config): + for provider_name, provider_info in providers_config.items(): + provided_cls = globals().get(provider_info['class']) + provider_cls = getattr(providers, provider_info['provider_class']) + setattr(container, provider_name, provider_cls(provided_cls)) + + +if __name__ == '__main__': + services_config = { + 'user': { + 'class': 'UserService', + 'provider_class': 'Factory', }, 'auth': { - 'class': '__main__.AuthService', - 'provider_class': 'dependency_injector.providers.Factory', - } + 'class': 'AuthService', + 'provider_class': 'Factory', + }, } -} + services = containers.DynamicContainer() -# Creating empty container of service providers: -services = containers.DynamicContainer() + populate_container(services, services_config) -# Filling dynamic container with service providers using configuration: -for service_name, service_info in config['services'].iteritems(): - # Runtime importing of service and service provider classes: - service_cls = import_cls(service_info['class']) - service_provider_cls = import_cls(service_info['provider_class']) + user_service = services.user() + auth_service = services.auth() - # Binding service provider to the dynamic service providers catalog: - setattr(services, service_name, service_provider_cls(service_cls)) - -# Creating some objects: -users_service = services.users() -auth_service = services.auth() - -# Making some asserts: -assert isinstance(users_service, UsersService) -assert isinstance(auth_service, AuthService) + assert isinstance(user_service, UserService) + assert isinstance(auth_service, AuthService) diff --git a/examples/containers/override.py b/examples/containers/override.py new file mode 100644 index 00000000..268cc4d0 --- /dev/null +++ b/examples/containers/override.py @@ -0,0 +1,31 @@ +"""Container overriding example.""" + +from dependency_injector import containers, providers + + +class Service: + ... + + +class ServiceStub: + ... + + +class Container(containers.DeclarativeContainer): + + service = providers.Factory(Service) + + +class OverridingContainer(containers.DeclarativeContainer): + + service = providers.Factory(ServiceStub) + + +if __name__ == '__main__': + container = Container() + overriding_container = OverridingContainer() + + container.override(overriding_container) + + service = container.service() + assert isinstance(service, ServiceStub) diff --git a/examples/containers/override_declarative.py b/examples/containers/override_declarative.py deleted file mode 100644 index b11d1b2e..00000000 --- a/examples/containers/override_declarative.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Declarative IoC container overriding example.""" - -import dependency_injector.containers as containers -import dependency_injector.providers as providers - - -class Container(containers.DeclarativeContainer): - """IoC container.""" - - sequence_factory = providers.Factory(list) - - -class OverridingContainer(containers.DeclarativeContainer): - """Overriding IoC container.""" - - sequence_factory = providers.Factory(tuple) - - -# Overriding `Container` with `OverridingContainer`: -Container.override(OverridingContainer) - -# Creating some objects using overridden container: -sequence_1 = Container.sequence_factory([1, 2, 3]) -sequence_2 = Container.sequence_factory([3, 2, 1]) - -# Making some asserts: -assert Container.overridden == (OverridingContainer,) -assert sequence_1 == (1, 2, 3) and sequence_2 == (3, 2, 1) diff --git a/examples/containers/override_declarative_decorator.py b/examples/containers/override_declarative_decorator.py deleted file mode 100644 index 74165383..00000000 --- a/examples/containers/override_declarative_decorator.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Declarative IoC container overriding using `@override()` decorator.""" - -import dependency_injector.containers as containers -import dependency_injector.providers as providers - - -class Container(containers.DeclarativeContainer): - """IoC container.""" - - sequence_factory = providers.Factory(list) - - -# Overriding `Container` with `OverridingContainer`: -@containers.override(Container) -class OverridingContainer(containers.DeclarativeContainer): - """Overriding IoC container.""" - - sequence_factory = providers.Factory(tuple) - - -# Creating some objects using overridden container: -sequence_1 = Container.sequence_factory([1, 2, 3]) -sequence_2 = Container.sequence_factory([3, 2, 1]) - -# Making some asserts: -assert Container.overridden == (OverridingContainer,) -assert sequence_1 == (1, 2, 3) and sequence_2 == (3, 2, 1) diff --git a/examples/di_demo2/config.yml b/examples/di_demo2/config.yml deleted file mode 100644 index b6f36405..00000000 --- a/examples/di_demo2/config.yml +++ /dev/null @@ -1,2 +0,0 @@ -api_key: test-key -timeout: 5 \ No newline at end of file diff --git a/examples/di_demo2/demo.py b/examples/di_demo2/demo.py index ccfccd97..02d49f09 100644 --- a/examples/di_demo2/demo.py +++ b/examples/di_demo2/demo.py @@ -32,8 +32,8 @@ class Container(containers.DeclarativeContainer): if __name__ == '__main__': container = Container() - container.config.from_yaml('config.yml') + container.config.api_key.from_env('API_KEY') + container.config.timeout.from_env('TIMEOUT') service = container.service() - assert isinstance(service.api_client, ApiClient) diff --git a/examples/di_demo2/example_di.py b/examples/di_demo2/example_di.py new file mode 100644 index 00000000..db85c237 --- /dev/null +++ b/examples/di_demo2/example_di.py @@ -0,0 +1,18 @@ +import os + + +class ApiClient: + + def __init__(self, api_key: str, timeout: int): + self.api_key = api_key + self.timeout = timeout + + +class Service: + + def __init__(self, api_client: ApiClient): + self.api_client = api_client + + +if __name__ == '__main__': + service = Service(ApiClient(os.getenv('API_KEY'), os.getenv('TIMEOUT'))) diff --git a/examples/di_demo2/example_no_di.py b/examples/di_demo2/example_no_di.py new file mode 100644 index 00000000..c567ebd6 --- /dev/null +++ b/examples/di_demo2/example_no_di.py @@ -0,0 +1,18 @@ +import os + + +class ApiClient: + + def __init__(self): + self.api_key = os.getenv('API_KEY') + self.timeout = os.getenv('TIMEOUT') + + +class Service: + + def __init__(self): + self.api_client = ApiClient() + + +if __name__ == '__main__': + service = Service() diff --git a/examples/di_demo2/test.py b/examples/di_demo2/test.py new file mode 100644 index 00000000..56310989 --- /dev/null +++ b/examples/di_demo2/test.py @@ -0,0 +1,11 @@ +from unittest import mock + +from demo import Container + + +if __name__ == '__main__': + container = Container() + + with container.api_client.override(mock.Mock()): + service = container.service() + assert isinstance(service.api_client, mock.Mock) diff --git a/src/dependency_injector/__init__.py b/src/dependency_injector/__init__.py index 849437b1..fa0c9dcd 100644 --- a/src/dependency_injector/__init__.py +++ b/src/dependency_injector/__init__.py @@ -1,6 +1,6 @@ """Dependency injector top-level package.""" -__version__ = '3.30.2' +__version__ = '3.30.3' """Version number that follows semantic versioning. :type: str