diff --git a/docs/examples-other/chained-factories.rst b/docs/examples-other/chained-factories.rst new file mode 100644 index 00000000..0bec024e --- /dev/null +++ b/docs/examples-other/chained-factories.rst @@ -0,0 +1,68 @@ +Chained Factories pattern +========================= + +This example demonstrates "Chained Factories" pattern. + +The idea of the pattern is in wrapping ``Factory`` into another ``Factory`` that adds +additional arguments. + +.. code-block:: python + + base_factory = providers.Factory( + SomeClass, + base_argument=1, + ) + + concrete_factory = providers.Factory( + base_factory, + extra_argument=2, + ) + + + if __name__ == '__main__': + instance = concrete_factory() + # Same as: # instance = SomeClass(base_argument=1, extra_argument=2) + +Sample code +----------- + +Listing of the pattern example: + +.. literalinclude:: ../../examples/miniapps/factory-patterns/chained_factories.py + :language: python + +Arguments priority +------------------ + +Passing of the arguments works the same way like for any other :ref:`factory-provider`. + +.. code-block:: python + + # 1. Keyword arguments of upper level factory are added to lower level factory + chained_dict_factory = providers.Factory( + providers.Factory(dict, arg1=1), + arg2=2, + ) + print(chained_dict_factory()) # prints: {'arg1': 1, 'arg2': 2} + + # 2. Keyword arguments of upper level factory have priority + chained_dict_factory = providers.Factory( + providers.Factory(dict, arg1=1), + arg1=2, + ) + print(chained_dict_factory()) # prints: {'arg1': 2} + + # 3. Keyword arguments provided from context have the most priority + chained_dict_factory = providers.Factory( + providers.Factory(dict, arg1=1), + arg1=2, + ) + print(chained_dict_factory(arg1=3)) # prints: {'arg1': 3} + + +Credits +------- + +The "Chained Factories" pattern was suggested by the ``Dependency Injector`` users. + +.. disqus:: diff --git a/docs/examples-other/chained_factories.rst b/docs/examples-other/chained_factories.rst deleted file mode 100644 index 6d20bc7a..00000000 --- a/docs/examples-other/chained_factories.rst +++ /dev/null @@ -1,21 +0,0 @@ -Chained Factories pattern -========================= - -This example demonstrate implementation of "Chained Factories" pattern. -Main idea of this pattern is about wrapping :py:class:`Factory` into -another :py:class:`Factory` that mix additional arguments or keyword -arguments to a wrapped one. - -Listing of ``data.py``, demonstrates sample classes structure: - -.. literalinclude:: ../../examples/miniapps/factory_patterns/data.py - :language: python - -Listing of ``chained_factories.py``, demonstrates "Chained Factories" -pattern and provide some explanation: - -.. literalinclude:: ../../examples/miniapps/factory_patterns/chained_factories.py - :language: python - - -.. disqus:: diff --git a/docs/examples-other/factory-of-factories.rst b/docs/examples-other/factory-of-factories.rst new file mode 100644 index 00000000..6cc71aa3 --- /dev/null +++ b/docs/examples-other/factory-of-factories.rst @@ -0,0 +1,74 @@ +Factory of Factories pattern +============================ + +This example demonstrates "Factory of Factories" pattern. + +The idea of the pattern is in creating a ``Factory`` that creates another ``Factory`` and adds +additional arguments. + +.. code-block:: python + + base_factory = providers.Factory( + providers.Factory + SomeClass, + base_argument=1, + ) + + concrete_factory = providers.Factory( + OtherClass, + instance=base_factory(extra_argument=1), + ) + + + if __name__ == '__main__': + instance = concrete_factory() + # Same as: # instance = SomeClass(base_argument=1, extra_argument=2) + +Sample code +----------- + +Listing of the pattern example: + +.. literalinclude:: ../../examples/miniapps/factory-patterns/factory_of_factories.py + :language: python + +Arguments priority +------------------ + +Passing of the arguments works the same way like for any other :ref:`factory-provider`. + +.. code-block:: python + + # 1. Keyword arguments of upper level factory are added to lower level factory + factory_of_dict_factories = providers.Factory( + providers.Factory, + dict, + arg1=1, + ) + dict_factory = factory_of_dict_factories(arg2=2) + print(dict_factory()) # prints: {'arg1': 1, 'arg2': 2} + + # 2. Keyword arguments of upper level factory have priority + factory_of_dict_factories = providers.Factory( + providers.Factory, + dict, + arg1=1, + ) + dict_factory = factory_of_dict_factories(arg1=2) + print(dict_factory()) # prints: {'arg1': 2} + + # 3. Keyword arguments provided from context have the most priority + factory_of_dict_factories = providers.Factory( + providers.Factory, + dict, + arg1=1, + ) + dict_factory = factory_of_dict_factories(arg1=2) + print(dict_factory(arg1=3)) # prints: {'arg1': 3} + +Credits +------- + +The "Factory of Factories" pattern was suggested by the ``Dependency Injector`` users. + +.. disqus:: diff --git a/docs/examples-other/factory_of_factories.rst b/docs/examples-other/factory_of_factories.rst deleted file mode 100644 index d3ef157b..00000000 --- a/docs/examples-other/factory_of_factories.rst +++ /dev/null @@ -1,20 +0,0 @@ -Factory of Factories pattern -============================ - -This example demonstrate implementation of "Factory of Factories" pattern. -Main idea of this pattern is about creation of a :py:class:`Factory` that -creates another :py:class:`Factory` and mix additional arguments to it. - -Listing of ``data.py``, demonstrates sample classes structure: - -.. literalinclude:: ../../examples/miniapps/factory_patterns/data.py - :language: python - -Listing of ``factory_of_factories.py``, demonstrates "Chained Factories" -pattern and provide some explanation: - -.. literalinclude:: ../../examples/miniapps/factory_patterns/factory_of_factories.py - :language: python - - -.. disqus:: diff --git a/docs/examples-other/index.rst b/docs/examples-other/index.rst index 8b082fa8..023e60ec 100644 --- a/docs/examples-other/index.rst +++ b/docs/examples-other/index.rst @@ -2,18 +2,16 @@ Other examples ============== .. meta:: - :keywords: Python,DI,Dependency injection,IoC,Inversion of Control - :description: Current section of documentation is designed to provide - several example mini applications that are built on the top - of inversion of control principle and powered by - "Dependency Injector" framework. + :keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application, + Framework + :description: This sections contains assorted Dependency Injector examples. This sections contains assorted ``Dependency Injector`` examples. .. toctree:: :maxdepth: 2 - use_cases_miniapp - password_hashing_miniapp - chained_factories - factory_of_factories + use-cases + password-hashing + chained-factories + factory-of-factories diff --git a/docs/examples-other/password-hashing.rst b/docs/examples-other/password-hashing.rst new file mode 100644 index 00000000..d4d0af39 --- /dev/null +++ b/docs/examples-other/password-hashing.rst @@ -0,0 +1,30 @@ +Password hashing example +======================== + +.. meta:: + :keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application, + Framework,Callable + :description: This example demonstrates a usage of the Callable provider. + +This example demonstrates an injection of the ``Callable`` provider. + +The source code is available on the `Github `_. + +Sample code +----------- + +Listing of the pattern example: + +.. literalinclude:: ../../examples/miniapps/password-hashing/example.py + :language: python + +Run the example +--------------- + +Instructions for running: + +.. code-block:: bash + + python example.py + +.. disqus:: diff --git a/docs/examples-other/password_hashing_miniapp.rst b/docs/examples-other/password_hashing_miniapp.rst deleted file mode 100644 index 21177545..00000000 --- a/docs/examples-other/password_hashing_miniapp.rst +++ /dev/null @@ -1,18 +0,0 @@ -Dependency injection and password hashing in Python -=================================================== - -Small example that demonstrates using of dependency injection for user -password hashing. - -Instructions for running: - -.. code-block:: bash - - python example.py - -Listing of ``example.py``: - -.. literalinclude:: ../../examples/miniapps/password_hashing/example.py - :language: python - -.. disqus:: diff --git a/docs/examples-other/use-cases.rst b/docs/examples-other/use-cases.rst new file mode 100644 index 00000000..9f191e39 --- /dev/null +++ b/docs/examples-other/use-cases.rst @@ -0,0 +1,74 @@ +Use cases example +================= + +.. meta:: + :keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application, + Framework,DependenciesContainer + :description: This example demonstrates a usage of the DependenciesContainer provider. + +This example demonstrates a usage of the ``DependenciesContainer`` provider. + +The source code is available on the `Github `_. + +Application structure +--------------------- + +Example application has next structure: + +.. code-block:: bash + + + ./ + └── example/ + ├── __init__.py + ├── __main__.py + ├── adapters.py + ├── containers.py + └── usecases.py + +Containers +---------- + +Listing of the ``example/containers.py``: + +.. literalinclude:: ../../examples/miniapps/use-cases/example/containers.py + :language: python + +Main module +----------- + +Listing of the ``example/__main__.py``: + +.. literalinclude:: ../../examples/miniapps/use-cases/example/__main__.py + :language: python + + +Run the application +------------------- + +Instructions for running in the "test" mode: + +.. code-block:: bash + + python run.py test example@example.com + +Instructions for running in the "prod" mode: + +.. code-block:: bash + + python run.py prod example@example.com + +Adapters and use cases +---------------------- + +Listing of the ``example/adapters.py``: + +.. literalinclude:: ../../examples/miniapps/use-cases/example/adapters.py + :language: python + +Listing of the ``example/usecases.py``: + +.. literalinclude:: ../../examples/miniapps/use-cases/example/usecases.py + :language: python + +.. disqus:: diff --git a/docs/examples-other/use_cases_miniapp.rst b/docs/examples-other/use_cases_miniapp.rst deleted file mode 100644 index 03aa7150..00000000 --- a/docs/examples-other/use_cases_miniapp.rst +++ /dev/null @@ -1,55 +0,0 @@ -Use cases mini application example ----------------------------------- - -.. currentmodule:: dependency_injector.providers - -"Use cases" miniapp demonstrate usage of :py:class:`DependenciesContainer` -provider. - -Example application -~~~~~~~~~~~~~~~~~~~ - -"Use cases" mini application has next structure: - -.. code-block:: bash - - use_cases/ - example/ <-- Example package - __init__.py - adapters.py - use_cases.py - containers.py <-- Dependency injection containers - run.py <-- Entrypoint - - -IoC containers -~~~~~~~~~~~~~~ - -Listing of ``use_cases/containers.py``: - -.. literalinclude:: ../../examples/miniapps/use_cases/containers.py - :language: python - -Run application -~~~~~~~~~~~~~~~ - -Listing of ``run.py``: - -.. literalinclude:: ../../examples/miniapps/use_cases/run.py - :language: python - -Instructions for running: - -.. code-block:: bash - - python run.py prod example@example.com # Running in "production" environment - python run.py test example@example.com # Running in "testing" environment - -Links -~~~~~ - -+ `Dependency Injector `_ -+ `Full example sources `_ - - -.. disqus:: diff --git a/docs/examples/decoupled-packages.rst b/docs/examples/decoupled-packages.rst index a030938e..abb4c6ab 100644 --- a/docs/examples/decoupled-packages.rst +++ b/docs/examples/decoupled-packages.rst @@ -1,3 +1,5 @@ +.. _decoupled-packages: + Decoupled packages example (multiple containers) ================================================ diff --git a/docs/introduction/di_in_python.rst b/docs/introduction/di_in_python.rst index fe56d0ca..7802d7e3 100644 --- a/docs/introduction/di_in_python.rst +++ b/docs/introduction/di_in_python.rst @@ -134,6 +134,7 @@ Choose one of the following as a next step: - Look at application examples: - :ref:`application-single-container` - :ref:`application-multiple-containers` + - :ref:`decoupled-packages` - Pass the tutorials: - :ref:`flask-tutorial` - :ref:`aiohttp-tutorial` diff --git a/docs/introduction/what_is_di.rst b/docs/introduction/what_is_di.rst index 9b9d1248..aa64c07c 100644 --- a/docs/introduction/what_is_di.rst +++ b/docs/introduction/what_is_di.rst @@ -178,6 +178,7 @@ Choose one of the following as a next step: - Look at application examples: - :ref:`application-single-container` - :ref:`application-multiple-containers` + - :ref:`decoupled-packages` - Pass the tutorials: - :ref:`flask-tutorial` - :ref:`aiohttp-tutorial` diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 152f925f..ac1f6c85 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -7,6 +7,17 @@ that were made in every particular version. From version 0.7.6 *Dependency Injector* framework strictly follows `Semantic versioning`_ +3.41.0 +------ +- Refactor "use cases" example. +- Refactor "password hashing" example. +- Refactor "chained factories" pattern example. +- Refactor "factory of factories" pattern example. +- Fix declarative container mypy stub to ``__init__`` to accept not only providers. +- Refactor main module of the "decoupled packages" example. +- Delete "api client" example mini app. +- Delete "mail service" example mini app. + 3.40.0 ------ - Add "Decoupled packages" example. diff --git a/examples/miniapps/api_client/api.py b/examples/miniapps/api_client/api.py deleted file mode 100644 index b876a470..00000000 --- a/examples/miniapps/api_client/api.py +++ /dev/null @@ -1,15 +0,0 @@ -"""API client module.""" - - -class ApiClient: - """Some API client.""" - - def __init__(self, host, api_key): - """Initialize instance.""" - self.host = host - self.api_key = api_key - - def call(self, operation, data): - """Make some network operations.""" - print('API call [{0}:{1}], method - {2}, data - {3}'.format( - self.host, self.api_key, operation, repr(data))) diff --git a/examples/miniapps/api_client/main.py b/examples/miniapps/api_client/main.py deleted file mode 100644 index d99979d8..00000000 --- a/examples/miniapps/api_client/main.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Main module.""" - -from dependency_injector import providers - -import api -import models - - -# Creating ApiClient and User providers: -api_client = providers.Singleton(api.ApiClient, - host='production.com', - api_key='PROD_API_KEY') -user_factory = providers.Factory(models.User, - api_client=api_client) - - -if __name__ == '__main__': - # Creating several users and register them: - user1 = user_factory(1) - user1.register() - # API call [production.com:PROD_API_KEY], method - register, data - - # {'id': 1} - - user2 = user_factory(2) - user2.register() - # API call [production.com:PROD_API_KEY], method - register, data - - # {'id': 2} - - # Overriding of ApiClient on dev environment: - api_client.override(providers.Singleton(api.ApiClient, - host='localhost', - api_key='DEV_API_KEY')) - - user3 = user_factory(3) - user3.register() - # API call [localhost:DEV_API_KEY], method - register, data - {'id': 3} diff --git a/examples/miniapps/api_client/models.py b/examples/miniapps/api_client/models.py deleted file mode 100644 index f8145c02..00000000 --- a/examples/miniapps/api_client/models.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Models module.""" - - -class User: - """User model.""" - - def __init__(self, id, api_client): - """Initialize instance.""" - self.id = id - self.api_client = api_client - - def register(self): - """Register user.""" - self.api_client.call('register', {'id': self.id}) diff --git a/examples/miniapps/api_client/tests.py b/examples/miniapps/api_client/tests.py deleted file mode 100644 index c304e6b0..00000000 --- a/examples/miniapps/api_client/tests.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Tests module.""" - -from unittest.mock import Mock - -import main -import api - -# Mock ApiClient for testing: -with main.api_client.override(Mock(api.ApiClient)) as api_client_mock: - user = main.user_factory('test') - user.register() - api_client_mock().call.assert_called_with('register', {'id': 'test'}) diff --git a/examples/miniapps/decoupled-packages/example/__main__.py b/examples/miniapps/decoupled-packages/example/__main__.py index 2e4e74da..d57cc194 100644 --- a/examples/miniapps/decoupled-packages/example/__main__.py +++ b/examples/miniapps/decoupled-packages/example/__main__.py @@ -3,7 +3,7 @@ from .containers import ApplicationContainer -if __name__ == '__main__': +def main() -> None: application = ApplicationContainer() application.config.from_ini('config.ini') @@ -22,3 +22,7 @@ if __name__ == '__main__': assert aggregation_service.user_repository is user_repository assert aggregation_service.photo_repository is photo_repository print('Aggregate analytics from user and photo packages') + + +if __name__ == '__main__': + main() diff --git a/examples/miniapps/factory-patterns/chained_factories.py b/examples/miniapps/factory-patterns/chained_factories.py new file mode 100644 index 00000000..b54da028 --- /dev/null +++ b/examples/miniapps/factory-patterns/chained_factories.py @@ -0,0 +1,73 @@ +"""`Chained Factories` pattern.""" + +from dependency_injector import containers, providers + + +class SqlAlchemyDatabaseService: + + def __init__(self, session, base_class): + self.session = session + self.base_class = base_class + + +class TokensService: + + def __init__(self, id_generator, database): + self.id_generator = id_generator + self.database = database + + +class Token: + ... + + +class UsersService: + + def __init__(self, id_generator, database): + self.id_generator = id_generator + self.database = database + + +class User: + ... + + +# Sample objects +session = object() +id_generator = object() + + +class Container(containers.DeclarativeContainer): + + database = providers.Factory( + SqlAlchemyDatabaseService, + session=session, + ) + + token_service = providers.Factory( + TokensService, + id_generator=id_generator, + database=providers.Factory( + database, + base_class=Token, + ), + ) + + user_service = providers.Factory( + UsersService, + id_generator=id_generator, + database=providers.Factory( + database, + base_class=User, + ), + ) + + +if __name__ == '__main__': + container = Container() + + token_service = container.token_service() + assert token_service.database.base_class is Token + + user_service = container.user_service() + assert user_service.database.base_class is User diff --git a/examples/miniapps/factory-patterns/factory_of_factories.py b/examples/miniapps/factory-patterns/factory_of_factories.py new file mode 100644 index 00000000..664c2c58 --- /dev/null +++ b/examples/miniapps/factory-patterns/factory_of_factories.py @@ -0,0 +1,68 @@ +"""`Factory of Factories` pattern.""" + +from dependency_injector import containers, providers + + +class SqlAlchemyDatabaseService: + + def __init__(self, session, base_class): + self.session = session + self.base_class = base_class + + +class TokensService: + + def __init__(self, id_generator, database): + self.id_generator = id_generator + self.database = database + + +class Token: + ... + + +class UsersService: + + def __init__(self, id_generator, database): + self.id_generator = id_generator + self.database = database + + +class User: + ... + + +# Sample objects +session = object() +id_generator = object() + + +class Container(containers.DeclarativeContainer): + + database_factory = providers.Factory( + providers.Factory, + SqlAlchemyDatabaseService, + session=session, + ) + + token_service = providers.Factory( + TokensService, + id_generator=id_generator, + database=database_factory(base_class=Token), + ) + + user_service = providers.Factory( + UsersService, + id_generator=id_generator, + database=database_factory(base_class=User), + ) + + +if __name__ == '__main__': + container = Container() + + token_service = container.token_service() + assert token_service.database.base_class is Token + + user_service = container.user_service() + assert user_service.database.base_class is User diff --git a/examples/miniapps/factory_patterns/chained_factories.py b/examples/miniapps/factory_patterns/chained_factories.py deleted file mode 100644 index 993d1fa2..00000000 --- a/examples/miniapps/factory_patterns/chained_factories.py +++ /dev/null @@ -1,59 +0,0 @@ -"""`Chained Factories` pattern.""" - -from dependency_injector import providers - -from data import ( - id_generator, - session, - SqlAlchemyDatabaseService, - TokensService, - Token, - UsersService, - User, -) - - -# "Chained Factories" pattern - -database = providers.Factory(SqlAlchemyDatabaseService, session=session) - -tokens = providers.Factory( - TokensService, - id_generator=id_generator, - database=providers.Factory(database, base_class=Token), -) - -users = providers.Factory( - UsersService, - id_generator=id_generator, - database=providers.Factory(database, base_class=User), -) - -tokens_service = tokens() -assert tokens_service.database.base_class is Token - -users_service = users() -assert users_service.database.base_class is User - -# Explanation & some more examples - -# 1. Keyword arguments of upper level factory are added to lower level factory -chained_dict_factory = providers.Factory( - providers.Factory(dict, arg1=1), - arg2=2, -) -print(chained_dict_factory()) # prints: {'arg1': 1, 'arg2': 2} - -# 2. Keyword arguments of upper level factory have priority -chained_dict_factory = providers.Factory( - providers.Factory(dict, arg1=1), - arg1=2, -) -print(chained_dict_factory()) # prints: {'arg1': 2} - -# 3. Keyword arguments provided from context have most priority -chained_dict_factory = providers.Factory( - providers.Factory(dict, arg1=1), - arg1=2, -) -print(chained_dict_factory(arg1=3)) # prints: {'arg1': 3} diff --git a/examples/miniapps/factory_patterns/data.py b/examples/miniapps/factory_patterns/data.py deleted file mode 100644 index 80fdd20f..00000000 --- a/examples/miniapps/factory_patterns/data.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Sample data classes.""" - - -class SqlAlchemyDatabaseService: - """Database service of an entity.""" - - def __init__(self, session, base_class): - """Initialize object.""" - self.session = session - self.base_class = base_class - - -class TokensService: - """Tokens service.""" - - def __init__(self, id_generator, database): - """Initialize object.""" - self.id_generator = id_generator - self.database = database - - -class Token: - """Token entity.""" - - -class UsersService: - """Users service.""" - - def __init__(self, id_generator, database): - """Initialize object.""" - self.id_generator = id_generator - self.database = database - - -class User: - """User entity.""" - - -# Sample objects -session = object() -id_generator = object() diff --git a/examples/miniapps/factory_patterns/factory_of_factories.py b/examples/miniapps/factory_patterns/factory_of_factories.py deleted file mode 100644 index 7a6a5fc1..00000000 --- a/examples/miniapps/factory_patterns/factory_of_factories.py +++ /dev/null @@ -1,69 +0,0 @@ -"""`Factory of Factories` pattern.""" - -from dependency_injector import providers - -from data import ( - id_generator, - session, - SqlAlchemyDatabaseService, - TokensService, - Token, - UsersService, - User, -) - - -# "Factory of Factories" pattern - -database_factory = providers.Factory( - providers.Factory, - SqlAlchemyDatabaseService, - session=session, -) - -tokens = providers.Factory( - TokensService, - id_generator=id_generator, - database=database_factory(base_class=Token), -) - -users = providers.Factory( - UsersService, - id_generator=id_generator, - database=database_factory(base_class=User), -) - -tokens_service = tokens() -assert tokens_service.database.base_class is Token - -users_service = users() -assert users_service.database.base_class is User - -# Explanation & some more examples - -# 1. Keyword arguments of upper level factory are added to lower level factory -factory_of_dict_factories = providers.Factory( - providers.Factory, - dict, - arg1=1, -) -dict_factory = factory_of_dict_factories(arg2=2) -print(dict_factory()) # prints: {'arg1': 1, 'arg2': 2} - -# 2. Keyword arguments of upper level factory have priority -factory_of_dict_factories = providers.Factory( - providers.Factory, - dict, - arg1=1, -) -dict_factory = factory_of_dict_factories(arg1=2) -print(dict_factory()) # prints: {'arg1': 2} - -# 3. Keyword arguments provided from context have most priority -factory_of_dict_factories = providers.Factory( - providers.Factory, - dict, - arg1=1, -) -dict_factory = factory_of_dict_factories(arg1=2) -print(dict_factory(arg1=3)) # prints: {'arg1': 3} diff --git a/examples/miniapps/mail_service/container.py b/examples/miniapps/mail_service/container.py deleted file mode 100644 index 5603d320..00000000 --- a/examples/miniapps/mail_service/container.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Mail service and user registration DI container example.""" - -from dependency_injector.containers import DeclarativeContainer -from dependency_injector.providers import Callable, Singleton - -import example - - -class Container(DeclarativeContainer): - """DI container.""" - - mail_service = Singleton(example.MailService, - host='localhost', - port=587, - login='my_login', - password='super_secret_password') - - add_user = Callable(example.add_user, - mailer=mail_service) - - -if __name__ == '__main__': - print('Using real mail service:') - Container.add_user('sample@mail.com', 'password') - # Using real mail service: - # Connecting server localhost:587 with my_login:super_secret_password - # Sending "Your password is password" to "sample@mail.com" - - print('Using mail service stub:') - Container.add_user('sample@mail.com', 'password', - mailer=example.MailServiceStub()) - # Using mail service stub: - # Emulating sending "Your password is password" to "sample@mail.com" - - # Also you can override provider by another provider: - Container.mail_service.override(Singleton(example.MailServiceStub)) - print('Using mail service stub by overriding mail service provider:') - Container.add_user('sample@mail.com', 'password') - # Using mail service stub by overriding mail service provider: - # Emulating sending "Your password is password" to "sample@mail.com" - Container.mail_service.reset_override() # Resetting provider overriding diff --git a/examples/miniapps/mail_service/example.py b/examples/miniapps/mail_service/example.py deleted file mode 100644 index 0703e476..00000000 --- a/examples/miniapps/mail_service/example.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Mail service and user registration example.""" - - -class AbstractMailService: - """Abstract mail service.""" - - def send(self, email, body): - """Send email.""" - raise NotImplementedError() - - -class MailService(AbstractMailService): - """Mail service.""" - - def __init__(self, host, port, login, password): - """Initialize instance.""" - self._host = host - self._port = port - self._login = login - self._password = password - - def send(self, email, body): - """Send email.""" - print('Connecting server {0}:{1} with {2}:{3}'.format( - self._host, self._port, self._login, self._password)) - print('Sending "{0}" to "{1}"'.format(body, email)) - - -class MailServiceStub(AbstractMailService): - """Mail service stub.""" - - def send(self, email, body): - """Send email.""" - print('Emulating sending "{0}" to "{1}"'.format(body, email)) - - -def add_user(email, password, mailer): - """Register user.""" - mailer.send(email, 'Your password is {0}'.format(password)) diff --git a/examples/miniapps/password-hashing/README.rst b/examples/miniapps/password-hashing/README.rst new file mode 100644 index 00000000..a979b1e6 --- /dev/null +++ b/examples/miniapps/password-hashing/README.rst @@ -0,0 +1,10 @@ +Password hashing +================ + +This example demonstrates an injection of the ``Callable`` provider. + +Instructions for running: + +.. code-block:: bash + + python example.py diff --git a/examples/miniapps/password-hashing/example.py b/examples/miniapps/password-hashing/example.py new file mode 100644 index 00000000..e9d042b9 --- /dev/null +++ b/examples/miniapps/password-hashing/example.py @@ -0,0 +1,50 @@ +"""Password hashing example.""" + +from typing import Callable, Dict + +import passlib.hash + +from dependency_injector import containers, providers + + +class UserService: + + def __init__(self, password_hasher: Callable[[str], str]) -> None: + self._password_hasher = password_hasher + + def create_user(self, name: str, password: str) -> Dict[str, str]: + hashed_password = self._password_hasher(password) + return { + 'name': name, + 'password': hashed_password, + } + + +class Container(containers.DeclarativeContainer): + + config = providers.Configuration() + + password_hasher = providers.Callable( + passlib.hash.sha256_crypt.hash, + salt_size=config.salt_size, + rounds=config.rounds, + ) + + user_service = providers.Factory( + UserService, + password_hasher=password_hasher.provider, + ) + + +if __name__ == '__main__': + container = Container( + config={ + 'salt_size': 16, + 'rounds': 10000, + }, + ) + + user_service = container.user_service() + + user = user_service.create_user(name='Roman', password='secret1') + print(user) diff --git a/examples/miniapps/password_hashing/README.rst b/examples/miniapps/password_hashing/README.rst deleted file mode 100644 index 9c306ab5..00000000 --- a/examples/miniapps/password_hashing/README.rst +++ /dev/null @@ -1,12 +0,0 @@ -Dependency injection and password hashing in Python -=================================================== - -Small example that demonstrates using of dependency injection for user -password hashing. - - -instructions for running: - -.. code-block:: bash - - python example.py diff --git a/examples/miniapps/password_hashing/example.py b/examples/miniapps/password_hashing/example.py deleted file mode 100644 index 1273330e..00000000 --- a/examples/miniapps/password_hashing/example.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Example of dependency injection and password hashing in Python.""" - -import passlib.hash - -import dependency_injector.containers as containers -import dependency_injector.providers as providers - - -class UsersService: - """Users service.""" - - def __init__(self, password_hasher): - """Initialize instance.""" - self._password_hasher = password_hasher - - def create_user(self, name, password): - """Create user with hashed password.""" - hashed_password = self._password_hasher(password) - return dict(name=name, password=hashed_password) - - -class Container(containers.DeclarativeContainer): - """Inversion of control container.""" - - password_hasher = providers.Callable( - passlib.hash.sha256_crypt.hash, - salt_size=16, - rounds=10000) - - users_service = providers.Factory( - UsersService, - password_hasher=password_hasher.provider) - - -if __name__ == '__main__': - container = Container() - users_service = container.users_service() - - user1 = users_service.create_user(name='Roman', password='secret1') - user2 = users_service.create_user(name='Vitaly', password='secret2') - - print(user1, user2) diff --git a/examples/miniapps/use-cases/README.rst b/examples/miniapps/use-cases/README.rst new file mode 100644 index 00000000..c2de398a --- /dev/null +++ b/examples/miniapps/use-cases/README.rst @@ -0,0 +1,11 @@ +Use cases +========= + +This example demonstrates a usage of the ``DependenciesContainer`` provider. + +Instructions for running: + +.. code-block:: bash + + python -m example prod example@example.com + python -m example test example@example.com diff --git a/examples/miniapps/use-cases/example/__init__.py b/examples/miniapps/use-cases/example/__init__.py new file mode 100644 index 00000000..1c744ca5 --- /dev/null +++ b/examples/miniapps/use-cases/example/__init__.py @@ -0,0 +1 @@ +"""Top-level package.""" diff --git a/examples/miniapps/use_cases/run.py b/examples/miniapps/use-cases/example/__main__.py similarity index 56% rename from examples/miniapps/use_cases/run.py rename to examples/miniapps/use-cases/example/__main__.py index 873f908a..01d6741c 100644 --- a/examples/miniapps/use_cases/run.py +++ b/examples/miniapps/use-cases/example/__main__.py @@ -1,19 +1,23 @@ -"""Run 'Use Cases' example application.""" +"""Main module.""" import sys -from containers import Adapters, TestAdapters, UseCases +from .containers import UseCases, Adapters, TestAdapters -if __name__ == '__main__': - environment, email = sys.argv[1:] - +def main(environment: str, email: str) -> None: if environment == 'prod': adapters = Adapters() elif environment == 'test': adapters = TestAdapters() + else: + raise RuntimeError('Unknown environment') use_cases = UseCases(adapters=adapters) use_case = use_cases.signup() use_case.execute(email) + + +if __name__ == '__main__': + main(*sys.argv[1:]) diff --git a/examples/miniapps/use-cases/example/adapters.py b/examples/miniapps/use-cases/example/adapters.py new file mode 100644 index 00000000..c84496a4 --- /dev/null +++ b/examples/miniapps/use-cases/example/adapters.py @@ -0,0 +1,22 @@ +"""Adapters module.""" + +import abc + + +class EmailSender(metaclass=abc.ABCMeta): + + @abc.abstractmethod + def send(self, to: str, body: str) -> None: + ... + + +class SmtpEmailSender: + + def send(self, to: str, body: str) -> None: + print(f'Sending an email to {to} over SMTP, body="{body}"') + + +class EchoEmailSender: + + def send(self, to: str, body: str) -> None: + print(f'Fake sending an email to {to}, body="{body}"') diff --git a/examples/miniapps/use-cases/example/containers.py b/examples/miniapps/use-cases/example/containers.py new file mode 100644 index 00000000..f164c68f --- /dev/null +++ b/examples/miniapps/use-cases/example/containers.py @@ -0,0 +1,25 @@ +"""Containers module.""" + +from dependency_injector import containers, providers + +from . import adapters, usecases + + +class Adapters(containers.DeclarativeContainer): + + email_sender = providers.Singleton(adapters.SmtpEmailSender) + + +class TestAdapters(containers.DeclarativeContainer): + + email_sender = providers.Singleton(adapters.EchoEmailSender) + + +class UseCases(containers.DeclarativeContainer): + + adapters = providers.DependenciesContainer() + + signup = providers.Factory( + usecases.SignupUseCase, + email_sender=adapters.email_sender, + ) diff --git a/examples/miniapps/use-cases/example/usecases.py b/examples/miniapps/use-cases/example/usecases.py new file mode 100644 index 00000000..dd03f5c7 --- /dev/null +++ b/examples/miniapps/use-cases/example/usecases.py @@ -0,0 +1,22 @@ +"""Use cases module.""" + +import abc + +from .adapters import EmailSender + + +class UseCase(metaclass=abc.ABCMeta): + + @abc.abstractmethod + def execute(self) -> None: + ... + + +class SignupUseCase: + + def __init__(self, email_sender: EmailSender) -> None: + self.email_sender = email_sender + + def execute(self, email: str) -> None: + print(f'Sign up user {email}') + self.email_sender.send(email, f'Welcome, {email}') diff --git a/examples/miniapps/use_cases/README.rst b/examples/miniapps/use_cases/README.rst deleted file mode 100644 index 44db341b..00000000 --- a/examples/miniapps/use_cases/README.rst +++ /dev/null @@ -1,9 +0,0 @@ -Dependency Injector Use Cases example -===================================== - -Instructions for running - -.. code-block:: bash - - python run.py prod example@example.com # Running in "production" environment - python run.py test example@example.com # Running in "testing" environment diff --git a/examples/miniapps/use_cases/containers.py b/examples/miniapps/use_cases/containers.py deleted file mode 100644 index 847fada3..00000000 --- a/examples/miniapps/use_cases/containers.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Dependency injection containers for 'Use Cases' example application.""" - -from dependency_injector import containers, providers - -from example.adapters import SmtpEmailSender, EchoEmailSender -from example.use_cases import SignupUseCase - - -class Adapters(containers.DeclarativeContainer): - """Adapters container.""" - - email_sender = providers.Singleton(SmtpEmailSender) - - -class TestAdapters(containers.DeclarativeContainer): - """Adapters container. - - This container is used for testing purposes. - """ - - email_sender = providers.Singleton(EchoEmailSender) - - -class UseCases(containers.DeclarativeContainer): - """Use cases container.""" - - adapters = providers.DependenciesContainer() - - signup = providers.Factory(SignupUseCase, - email_sender=adapters.email_sender) diff --git a/examples/miniapps/use_cases/example/__init__.py b/examples/miniapps/use_cases/example/__init__.py deleted file mode 100644 index bfa99aa2..00000000 --- a/examples/miniapps/use_cases/example/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Example top-level package.""" diff --git a/examples/miniapps/use_cases/example/adapters.py b/examples/miniapps/use_cases/example/adapters.py deleted file mode 100644 index d940cf0a..00000000 --- a/examples/miniapps/use_cases/example/adapters.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Example adapters package.""" - - -class EmailSender: - """Abstract email sender.""" - - def send(self, to, body): - """Send email to specified email.""" - raise NotImplementedError() - - -class SmtpEmailSender: - """SMTP email sender uses SMTP protocol for sending emails.""" - - def send(self, to, body): - """Send email to specified email.""" - # Send email via SMTP - - -class EchoEmailSender: - """Echo email sender prints emails to stdout.""" - - def send(self, to, body): - """Send email to specified email.""" - print('Sending email to "{0}", body = "{1}"'.format(to, body)) diff --git a/examples/miniapps/use_cases/example/use_cases.py b/examples/miniapps/use_cases/example/use_cases.py deleted file mode 100644 index 4946ae46..00000000 --- a/examples/miniapps/use_cases/example/use_cases.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Example use cases package.""" - - -class UseCase: - """Abstract use case.""" - - def execute(self): - """Execute use case handling.""" - raise NotImplementedError() - - -class SignupUseCase: - """Sign up use cases registers users.""" - - def __init__(self, email_sender): - """Initialize instance.""" - self.email_sender = email_sender - - def execute(self, email): - """Execute use case handling.""" - print('Sign up user {0}'.format(email)) - self.email_sender.send(email, 'Welcome, "{}"'.format(email)) diff --git a/src/dependency_injector/__init__.py b/src/dependency_injector/__init__.py index 4e3e13c4..1b7b4e85 100644 --- a/src/dependency_injector/__init__.py +++ b/src/dependency_injector/__init__.py @@ -1,6 +1,6 @@ """Dependency injector top-level package.""" -__version__ = '3.40.0' +__version__ = '3.41.0' """Version number that follows semantic versioning. :type: str diff --git a/src/dependency_injector/containers.pyi b/src/dependency_injector/containers.pyi index 62117b43..1c5346bf 100644 --- a/src/dependency_injector/containers.pyi +++ b/src/dependency_injector/containers.pyi @@ -24,7 +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 __init__(self, **overriding_providers: Union[Provider, Any]) -> None: ... def override(container: Container) -> _Callable[[Container], Container]: ...