diff --git a/docs/examples/bundles_miniapp.rst b/docs/examples-other/bundles_miniapp.rst similarity index 100% rename from docs/examples/bundles_miniapp.rst rename to docs/examples-other/bundles_miniapp.rst diff --git a/docs/examples/chained_factories.rst b/docs/examples-other/chained_factories.rst similarity index 100% rename from docs/examples/chained_factories.rst rename to docs/examples-other/chained_factories.rst diff --git a/docs/examples/factory_of_factories.rst b/docs/examples-other/factory_of_factories.rst similarity index 100% rename from docs/examples/factory_of_factories.rst rename to docs/examples-other/factory_of_factories.rst diff --git a/docs/examples-other/index.rst b/docs/examples-other/index.rst new file mode 100644 index 00000000..e99ec099 --- /dev/null +++ b/docs/examples-other/index.rst @@ -0,0 +1,22 @@ +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. + +Current section of documentation is designed to provide several example mini +applications that are built according to the inversion of control principle +and powered by *Dependency Injector* framework. + +.. toctree:: + :maxdepth: 2 + + bundles_miniapp + use_cases_miniapp + password_hashing_miniapp + chained_factories + factory_of_factories diff --git a/docs/examples/password_hashing_miniapp.rst b/docs/examples-other/password_hashing_miniapp.rst similarity index 100% rename from docs/examples/password_hashing_miniapp.rst rename to docs/examples-other/password_hashing_miniapp.rst diff --git a/docs/examples/use_cases_miniapp.rst b/docs/examples-other/use_cases_miniapp.rst similarity index 100% rename from docs/examples/use_cases_miniapp.rst rename to docs/examples-other/use_cases_miniapp.rst diff --git a/docs/examples/application-multiple-containers.rst b/docs/examples/application-multiple-containers.rst new file mode 100644 index 00000000..d49f999a --- /dev/null +++ b/docs/examples/application-multiple-containers.rst @@ -0,0 +1,87 @@ +.. _application-multiple-containers: + +Application example (multiple containers) +========================================= + +.. meta:: + :keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application, + Framework,AWS,boto3,client + :description: This example shows how you can create an application using multiple declarative + containers. We build an example Python micro application following the dependency + injection principle. It consists from several services with a domain logic that + have dependencies on database & AWS S3. + +This example shows how you can create an application using multiple declarative containers. Using +multiple declarative containers is a good choice for a large application. For +building a moderate or a small size application refer to :ref:`application-single-container`. + +We build an example micro application following the dependency injection principle. It consists +of several services with a domain logic. The services have dependencies on database & AWS S3. + +.. image:: images/application.png + :width: 100% + :align: center + +Start from the scratch or jump to the section: + +.. contents:: + :local: + :backlinks: none + +You can find the source code and instructions for running on the `Github `_. + +Application structure +--------------------- + +Application consists of an ``example`` package, a configuration file and a ``requirements.txt`` +file. + +.. code-block:: bash + + ./ + ├── example/ + │ ├── __init__.py + │ ├── __main__.py + │ ├── containers.py + │ └── services.py + ├── config.yml + └── requirements.txt + +Containers +---------- + +Listing of the ``example/containers.py``: + +.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/containers.py + :language: python + +Main module +----------- + +Listing of the ``example/__main__.py``: + +.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/__main__.py + :language: python + +Services +-------- + +Listing of the ``example/services.py``: + +.. literalinclude:: ../../examples/miniapps/application-multiple-containers/example/services.py + :language: python + +Configuration +------------- + +Listing of the ``config.yml``: + +.. literalinclude:: ../../examples/miniapps/application-multiple-containers/config.yml + :language: yaml + +Run the application +------------------- + +You can find the source code and instructions for running on the `Github `_. + +.. disqus:: \ No newline at end of file diff --git a/docs/examples/application-single-container.rst b/docs/examples/application-single-container.rst new file mode 100644 index 00000000..2f6c1c42 --- /dev/null +++ b/docs/examples/application-single-container.rst @@ -0,0 +1,93 @@ +.. _application-single-container: + +Application example (single container) +====================================== + +.. meta:: + :keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application, + Framework,AWS,boto3,client + :description: This example shows how you can create an application using a single declarative + container. We build an example Python micro application following the dependency + injection principle. It consists from several services with a domain logic that + have dependencies on database & AWS S3. + +This example shows how you can create an application using a single declarative container. Using +a single declarative container is a good choice for small or moderate size application. For +building a large application refer to :ref:`application-multiple-containers`. + +We build an example micro application following the dependency injection principle. It consists +of several services with a domain logic. The services have dependencies on database & AWS S3. + +.. image:: images/application.png + :width: 100% + :align: center + +Start from the scratch or jump to the section: + +.. contents:: + :local: + :backlinks: none + +You can find the source code and instructions for running on the `Github `_. + +Application structure +--------------------- + +Application consists of an ``example`` package, several configuration files and a +``requirements.txt`` file. + +.. code-block:: bash + + ./ + ├── example/ + │ ├── __init__.py + │ ├── __main__.py + │ ├── containers.py + │ └── services.py + ├── config.ini + ├── logging.ini + └── requirements.txt + +Container +--------- + +Listing of the ``example/containers.py``: + +.. literalinclude:: ../../examples/miniapps/application-single-container/example/containers.py + :language: python + +Main module +----------- + +Listing of the ``example/__main__.py``: + +.. literalinclude:: ../../examples/miniapps/application-single-container/example/__main__.py + :language: python + +Services +-------- + +Listing of the ``example/services.py``: + +.. literalinclude:: ../../examples/miniapps/application-single-container/example/services.py + :language: python + +Configuration +------------- + +Listing of the ``config.ini``: + +.. literalinclude:: ../../examples/miniapps/application-single-container/config.ini + :language: ini + +Listing of the ``logging.ini``: + +.. literalinclude:: ../../examples/miniapps/application-single-container/logging.ini + :language: ini + +Run the application +------------------- + +You can find the source code and instructions for running on the `Github `_. + +.. disqus:: diff --git a/docs/examples/images/application.png b/docs/examples/images/application.png new file mode 100644 index 00000000..1ef8f7be Binary files /dev/null and b/docs/examples/images/application.png differ diff --git a/docs/examples/index.rst b/docs/examples/index.rst index 244569b2..2d3545f2 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -2,23 +2,15 @@ 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,DI,Dependency injection,IoC,Inversion of Control,Example + :description: Python dependency injection examples. -Current section of documentation is designed to provide several example mini -applications that are built according to the inversion of control principle -and powered by *Dependency Injector* framework. +Explore the examples to see the ``Dependency Injector`` in action. .. toctree:: :maxdepth: 2 - services_miniapp_v1 - services_miniapp_v2 - bundles_miniapp - use_cases_miniapp - password_hashing_miniapp - chained_factories - factory_of_factories + application-single-container + application-multiple-containers + +.. disqus:: diff --git a/docs/examples/services_miniapp_v1.rst b/docs/examples/services_miniapp_v1.rst deleted file mode 100644 index 5277c56c..00000000 --- a/docs/examples/services_miniapp_v1.rst +++ /dev/null @@ -1,73 +0,0 @@ -Services mini application example (v1 - multiple containers) ------------------------------------------------------------- - -.. meta:: - :description: "Services miniapp" is an example mini application that - consists from several services that have dependencies on - some standard and 3rd-party libraries for logging, - interaction with database and remote service via API. - "Services miniapp" example demonstrates usage of - Dependency Injector for creating several inversion of control / - dependency injection containers. - -"Services miniapp" is an example mini application that consists from several -services that have dependencies on some standard and 3rd-party libraries for -logging, interaction with database and remote service calls via API. - -"Services miniapp" example demonstrates usage of -:doc:`Dependency Injector <../index>` for creating several IoC containers. - -Instructions for running: - -.. code-block:: bash - - python run.py 1 secret photo.jpg - -Example application -~~~~~~~~~~~~~~~~~~~ - -Classes diagram: - -.. image:: /images/miniapps/services/classes.png - :width: 100% - :align: center - - -Example application structure: - -.. code-block:: bash - - /example - /__init__.py - /main.py - /services.py - - -Listing of ``example/services.py``: - -.. literalinclude:: ../../examples/miniapps/services_v1/example/services.py - :language: python - -Listing of ``example/main.py``: - -.. literalinclude:: ../../examples/miniapps/services_v1/example/main.py - :language: python - -IoC containers -~~~~~~~~~~~~~~ - -Listing of ``containers.py``: - -.. literalinclude:: ../../examples/miniapps/services_v1/containers.py - :language: python - -Run application -~~~~~~~~~~~~~~~ - -Listing of ``run.py``: - -.. literalinclude:: ../../examples/miniapps/services_v1/run.py - :language: python - - -.. disqus:: diff --git a/docs/examples/services_miniapp_v2.rst b/docs/examples/services_miniapp_v2.rst deleted file mode 100644 index 4c29fca7..00000000 --- a/docs/examples/services_miniapp_v2.rst +++ /dev/null @@ -1,73 +0,0 @@ -Services mini application example (v2 - single container) ---------------------------------------------------------- - -.. meta:: - :description: "Services miniapp" is an example mini application that - consists from several services that have dependencies on - some standard and 3rd-party libraries for logging, - interaction with database and remote service via API. - "Services miniapp" example demonstrates usage of - Dependency Injector for creating inversion of control / - dependency injection container. - -"Services miniapp" is an example mini application that consists from several -services that have dependencies on some standard and 3rd-party libraries for -logging, interaction with database and remote service calls via API. - -"Services miniapp" example demonstrates usage of -:doc:`Dependency Injector <../index>` for creating IoC container. - -Instructions for running: - -.. code-block:: bash - - python run.py 1 secret photo.jpg - -Example application -~~~~~~~~~~~~~~~~~~~ - -Classes diagram: - -.. image:: /images/miniapps/services/classes.png - :width: 100% - :align: center - - -Example application structure: - -.. code-block:: bash - - /example - /__init__.py - /main.py - /services.py - - -Listing of ``example/services.py``: - -.. literalinclude:: ../../examples/miniapps/services_v2/example/services.py - :language: python - -Listing of ``example/main.py``: - -.. literalinclude:: ../../examples/miniapps/services_v2/example/main.py - :language: python - -IoC container -~~~~~~~~~~~~~ - -Listing of ``container.py``: - -.. literalinclude:: ../../examples/miniapps/services_v2/container.py - :language: python - -Run application -~~~~~~~~~~~~~~~ - -Listing of ``run.py``: - -.. literalinclude:: ../../examples/miniapps/services_v2/run.py - :language: python - - -.. disqus:: diff --git a/docs/images/miniapps/services/classes.png b/docs/images/miniapps/services/classes.png deleted file mode 100644 index 8e9d29f7..00000000 Binary files a/docs/images/miniapps/services/classes.png and /dev/null differ diff --git a/docs/index.rst b/docs/index.rst index e1eb64c7..e12ca2fb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -129,11 +129,11 @@ Contents :maxdepth: 2 introduction/index - main/installation + examples/index tutorials/index providers/index containers/index - examples/index + examples-other/index api/index main/feedback main/changelog diff --git a/docs/introduction/di_in_python.rst b/docs/introduction/di_in_python.rst index 644fef9f..fe56d0ca 100644 --- a/docs/introduction/di_in_python.rst +++ b/docs/introduction/di_in_python.rst @@ -3,90 +3,90 @@ Dependency injection and inversion of control in Python .. meta:: :keywords: Python,DI,Dependency injection,IoC,Inversion of Control - :description: This article describes benefits of dependency injection and - inversion of control for Python applications. Also it - contains some Python examples that show how dependency - injection and inversion could be implemented. In addition, it - demonstrates usage of dependency injection framework, + :description: This article describes benefits of dependency injection and + inversion of control for Python applications. Also it + contains some Python examples that show how dependency + injection and inversion could be implemented. In addition, it + demonstrates usage of dependency injection framework, IoC container and such popular design pattern as Factory. History ~~~~~~~ -Originally, dependency injection pattern got popular in languages with static -typing, like Java. Dependency injection framework can +Originally, dependency injection pattern got popular in languages with static +typing, like Java. Dependency injection framework can significantly improve flexibility of the language with static typing. Also, -implementation of dependency injection framework for language with static -typing is not something that one can do shortly, it could be quite complex +implementation of dependency injection framework for language with static +typing is not something that one can do shortly, it could be quite complex thing to be done well. -While Python is very flexible interpreted language with dynamic typing, there -is a meaning that dependency injection doesn't work for it as well, as it does -for Java. Also there is a meaning that dependency injection framework is +While Python is very flexible interpreted language with dynamic typing, there +is a meaning that dependency injection doesn't work for it as well, as it does +for Java. Also there is a meaning that dependency injection framework is something that Python developer would not ever need, cause dependency injection could be implemented easily using language fundamentals. Discussion ~~~~~~~~~~ -It is true. +It is true. Partly. -Dependency injection, as a software design pattern, has number of +Dependency injection, as a software design pattern, has number of advantages that are common for each language (including Python): + Dependency Injection decreases coupling between a class and its dependency. -+ Because dependency injection doesn't require any change in code behavior it - can be applied to legacy code as a refactoring. The result is clients that - are more independent and that are easier to unit test in isolation using - stubs or mock objects that simulate other objects not under test. This ease - of testing is often the first benefit noticed when using dependency ++ Because dependency injection doesn't require any change in code behavior it + can be applied to legacy code as a refactoring. The result is clients that + are more independent and that are easier to unit test in isolation using + stubs or mock objects that simulate other objects not under test. This ease + of testing is often the first benefit noticed when using dependency injection. -+ Dependency injection can be used to externalize a system's configuration - details into configuration files allowing the system to be reconfigured - without recompilation (rebuilding). Separate configurations can be written - for different situations that require different implementations of ++ Dependency injection can be used to externalize a system's configuration + details into configuration files allowing the system to be reconfigured + without recompilation (rebuilding). Separate configurations can be written + for different situations that require different implementations of components. This includes, but is not limited to, testing. -+ Reduction of boilerplate code in the application objects since all work to ++ Reduction of boilerplate code in the application objects since all work to initialize or set up dependencies is handled by a provider component. -+ Dependency injection allows a client to remove all knowledge of a concrete - implementation that it needs to use. This helps isolate the client from the - impact of design changes and defects. It promotes reusability, testability ++ Dependency injection allows a client to remove all knowledge of a concrete + implementation that it needs to use. This helps isolate the client from the + impact of design changes and defects. It promotes reusability, testability and maintainability. -+ Dependency injection allows a client the flexibility of being configurable. - Only the client's behavior is fixed. The client may act on anything that ++ Dependency injection allows a client the flexibility of being configurable. + Only the client's behavior is fixed. The client may act on anything that supports the intrinsic interface the client expects. .. note:: - While improved testability is one the first benefits of using dependency - injection, it could be easily overwhelmed by monkey-patching technique, - that works absolutely great in Python (you can monkey-patch anything, - anytime). At the same time, monkey-patching has nothing similar with - other advantages defined above. Also monkey-patching technique is + While improved testability is one the first benefits of using dependency + injection, it could be easily overwhelmed by monkey-patching technique, + that works absolutely great in Python (you can monkey-patch anything, + anytime). At the same time, monkey-patching has nothing similar with + other advantages defined above. Also monkey-patching technique is something that could be considered like too dirty to be used in production. -The complexity of dependency injection pattern implementation in Python is -definitely quite lower than in other languages (even with dynamic typing). +The complexity of dependency injection pattern implementation in Python is +definitely quite lower than in other languages (even with dynamic typing). -.. note:: +.. note:: - Low complexity of dependency injection pattern implementation in Python - still means that some code should be written, reviewed, tested and + Low complexity of dependency injection pattern implementation in Python + still means that some code should be written, reviewed, tested and supported. -Talking about inversion of control, it is a software design principle that +Talking about inversion of control, it is a software design principle that also works for each programming language, not depending on its typing type. -Inversion of control is used to increase modularity of the program and make +Inversion of control is used to increase modularity of the program and make it extensible. Main design purposes of using inversion of control are: + To decouple the execution of a task from implementation. + To focus a module on the task it is designed for. -+ To free modules from assumptions about how other systems do what they do and ++ To free modules from assumptions about how other systems do what they do and instead rely on contracts. + To prevent side effects when replacing a module. @@ -115,9 +115,9 @@ Next example demonstrates creation of several cars with different engines: :language: python While previous example demonstrates advantages of dependency injection, there -is a disadvantage demonstration as well - creation of car requires additional -code for specification of dependencies. Nevertheless, this disadvantage could -be easily avoided by using a dependency injection framework for creation of +is a disadvantage demonstration as well - creation of car requires additional +code for specification of dependencies. Nevertheless, this disadvantage could +be easily avoided by using a dependency injection framework for creation of inversion of control container (IoC container). Example of creation of several inversion of control containers (IoC containers) @@ -131,18 +131,21 @@ What's next? Choose one of the following as a next step: -+ Pass one of the dependency injection tutorials: - + :ref:`flask-tutorial` - + :ref:`aiohttp-tutorial` - + :ref:`asyncio-daemon-tutorial` - + :ref:`cli-tutorial` -+ Know more about the :ref:`providers` -+ Go to the :ref:`contents` +- Look at application examples: + - :ref:`application-single-container` + - :ref:`application-multiple-containers` +- Pass the tutorials: + - :ref:`flask-tutorial` + - :ref:`aiohttp-tutorial` + - :ref:`asyncio-daemon-tutorial` + - :ref:`cli-tutorial` +- Know more about the :ref:`providers` +- Go to the :ref:`contents` Useful links ~~~~~~~~~~~~ -There are some useful links related to dependency injection design pattern +There are some useful links related to dependency injection design pattern that could be used for further reading: + https://en.wikipedia.org/wiki/Dependency_injection diff --git a/docs/introduction/index.rst b/docs/introduction/index.rst index 0c3588de..2a4c77ff 100644 --- a/docs/introduction/index.rst +++ b/docs/introduction/index.rst @@ -17,3 +17,4 @@ dependency injection pattern, inversion of control principle and what_is_di di_in_python key_features + installation diff --git a/docs/main/installation.rst b/docs/introduction/installation.rst similarity index 98% rename from docs/main/installation.rst rename to docs/introduction/installation.rst index dcd8e3d3..bfd72043 100644 --- a/docs/main/installation.rst +++ b/docs/introduction/installation.rst @@ -31,7 +31,7 @@ Verification of currently installed version could be done using >>> import dependency_injector >>> dependency_injector.__version__ - '3.15.2' + '3.38.0' .. _PyPi: https://pypi.org/project/dependency-injector/ .. _GitHub: https://github.com/ets-labs/python-dependency-injector diff --git a/docs/introduction/what_is_di.rst b/docs/introduction/what_is_di.rst index 1288bfcb..9b9d1248 100644 --- a/docs/introduction/what_is_di.rst +++ b/docs/introduction/what_is_di.rst @@ -175,13 +175,16 @@ What's next? Choose one of the following as a next step: -+ Pass one of the tutorials: - + :ref:`cli-tutorial` - + :ref:`flask-tutorial` - + :ref:`aiohttp-tutorial` - + :ref:`asyncio-daemon-tutorial` -+ Know more about the :ref:`providers` -+ Go to the :ref:`contents` +- Look at application examples: + - :ref:`application-single-container` + - :ref:`application-multiple-containers` +- Pass the tutorials: + - :ref:`flask-tutorial` + - :ref:`aiohttp-tutorial` + - :ref:`asyncio-daemon-tutorial` + - :ref:`cli-tutorial` +- Know more about the :ref:`providers` +- Go to the :ref:`contents` .. disqus:: diff --git a/examples/miniapps/application-multiple-containers/README.rst b/examples/miniapps/application-multiple-containers/README.rst new file mode 100644 index 00000000..fe9840de --- /dev/null +++ b/examples/miniapps/application-multiple-containers/README.rst @@ -0,0 +1,29 @@ +Application example (multiple containers) +========================================= + +Create virtual env: + +.. code-block:: bash + + python3 -m venv venv + . venv/bin/activate + +Install requirements: + +.. code-block:: bash + + pip install -r requirements.txt + +Run: + +.. code-block:: bash + + python -m example user@example.com secret photo.jpg + +You should see: + +.. code-block:: bash + + [2020-09-04 16:06:00,750] [DEBUG] [example.services.UserService]: User user@example.com has been found in database + [2020-09-04 16:06:00,750] [DEBUG] [example.services.AuthService]: User user@example.com has been successfully authenticated + [2020-09-04 16:06:00,750] [DEBUG] [example.services.PhotoService]: Photo photo.jpg has been successfully uploaded by user user@example.com diff --git a/examples/miniapps/application-multiple-containers/config.yml b/examples/miniapps/application-multiple-containers/config.yml new file mode 100644 index 00000000..6a568526 --- /dev/null +++ b/examples/miniapps/application-multiple-containers/config.yml @@ -0,0 +1,30 @@ +core: + + logging: + version: 1 + formatters: + formatter: + format: "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s" + handlers: + console: + class: "logging.StreamHandler" + level: "DEBUG" + formatter: "formatter" + stream: "ext://sys.stderr" + root: + level: "DEBUG" + handlers: ["console"] + +gateways: + + database: + dsn: ":memory:" + + aws: + access_key_id: "KEY" + secret_access_key: "SECRET" + +services: + + auth: + token_ttl: 3600 diff --git a/examples/miniapps/services_v1/example/__init__.py b/examples/miniapps/application-multiple-containers/example/__init__.py similarity index 100% rename from examples/miniapps/services_v1/example/__init__.py rename to examples/miniapps/application-multiple-containers/example/__init__.py diff --git a/examples/miniapps/application-multiple-containers/example/__main__.py b/examples/miniapps/application-multiple-containers/example/__main__.py new file mode 100644 index 00000000..1d8eefc4 --- /dev/null +++ b/examples/miniapps/application-multiple-containers/example/__main__.py @@ -0,0 +1,24 @@ +"""Main module.""" + +import sys + +from .containers import Application + + +def main(email: str, password: str, photo: str) -> None: + application = Application() + + application.config.from_yaml('config.yml') + application.core.configure_logging() + + user_service = application.services.user() + auth_service = application.services.auth() + photo_service = application.services.photo() + + user = user_service.get_user(email) + auth_service.authenticate(user, password) + photo_service.upload_photo(user, photo) + + +if __name__ == '__main__': + main(*sys.argv[1:]) diff --git a/examples/miniapps/application-multiple-containers/example/containers.py b/examples/miniapps/application-multiple-containers/example/containers.py new file mode 100644 index 00000000..5bcec302 --- /dev/null +++ b/examples/miniapps/application-multiple-containers/example/containers.py @@ -0,0 +1,80 @@ +"""Containers module.""" + +import logging.config +import sqlite3 + +import boto3 +from dependency_injector import containers, providers + +from . import services + + +class Core(containers.DeclarativeContainer): + + config = providers.Configuration() + + configure_logging = providers.Callable( + logging.config.dictConfig, + config=config.logging, + ) + + +class Gateways(containers.DeclarativeContainer): + + config = providers.Configuration() + + database_client = providers.Singleton( + sqlite3.connect, + config.database.dsn, + ) + + s3_client = providers.Singleton( + boto3.client, + service_name='s3', + aws_access_key_id=config.aws.access_key_id, + aws_secret_access_key=config.aws.secret_access_key, + ) + + +class Services(containers.DeclarativeContainer): + + config = providers.Configuration() + gateways = providers.DependenciesContainer() + + user = providers.Factory( + services.UserService, + db=gateways.database_client, + ) + + auth = providers.Factory( + services.AuthService, + db=gateways.database_client, + token_ttl=config.auth.token_ttl.as_int(), + ) + + photo = providers.Factory( + services.PhotoService, + db=gateways.database_client, + s3=gateways.s3_client, + ) + + +class Application(containers.DeclarativeContainer): + + config = providers.Configuration() + + core = providers.Container( + Core, + config=config.core, + ) + + gateways = providers.Container( + Gateways, + config=config.gateways, + ) + + services = providers.Container( + Services, + config=config.services, + gateways=gateways, + ) diff --git a/examples/miniapps/application-multiple-containers/example/services.py b/examples/miniapps/application-multiple-containers/example/services.py new file mode 100644 index 00000000..338888e3 --- /dev/null +++ b/examples/miniapps/application-multiple-containers/example/services.py @@ -0,0 +1,56 @@ +"""Services module.""" + +import logging +import sqlite3 +from typing import Dict + +from mypy_boto3_s3 import S3Client + + +class BaseService: + + def __init__(self) -> None: + self.logger = logging.getLogger( + f'{__name__}.{self.__class__.__name__}', + ) + + +class UserService(BaseService): + + def __init__(self, db: sqlite3.Connection) -> None: + self.db = db + super().__init__() + + def get_user(self, email: str) -> Dict[str, str]: + self.logger.debug('User %s has been found in database', email) + return {'email': email, 'password_hash': '...'} + + +class AuthService(BaseService): + + def __init__(self, db: sqlite3.Connection, token_ttl: int) -> None: + self.db = db + self.token_ttl = token_ttl + super().__init__() + + def authenticate(self, user: Dict[str, str], password: str) -> None: + assert password is not None + self.logger.debug( + 'User %s has been successfully authenticated', + user['email'], + ) + + +class PhotoService(BaseService): + + def __init__(self, db: sqlite3.Connection, s3: S3Client) -> None: + self.db = db + self.s3 = s3 + super().__init__() + + def upload_photo(self, user: Dict[str, str], photo_path: str) -> None: + self.logger.debug( + 'Photo %s has been successfully uploaded by user %s', + photo_path, + user['email'], + ) diff --git a/examples/miniapps/application-multiple-containers/requirements.txt b/examples/miniapps/application-multiple-containers/requirements.txt new file mode 100644 index 00000000..5d5e226a --- /dev/null +++ b/examples/miniapps/application-multiple-containers/requirements.txt @@ -0,0 +1,3 @@ +dependency-injector[yaml] +boto3 +boto3-stubs[s3] diff --git a/examples/miniapps/application-single-container/README.rst b/examples/miniapps/application-single-container/README.rst new file mode 100644 index 00000000..100693b9 --- /dev/null +++ b/examples/miniapps/application-single-container/README.rst @@ -0,0 +1,29 @@ +Application example (single container) +====================================== + +Create virtual env: + +.. code-block:: bash + + python3 -m venv venv + . venv/bin/activate + +Install requirements: + +.. code-block:: bash + + pip install -r requirements.txt + +Run: + +.. code-block:: bash + + python -m example user@example.com secret photo.jpg + +You should see: + +.. code-block:: bash + + [2020-09-04 15:27:27,727] [DEBUG] [example.services.UserService]: User user@example.com has been found in database + [2020-09-04 15:27:27,727] [DEBUG] [example.services.AuthService]: User user@example.com has been successfully authenticated + [2020-09-04 15:27:27,727] [DEBUG] [example.services.PhotoService]: Photo photo.jpg has been successfully uploaded by user user@example.com diff --git a/examples/miniapps/application-single-container/config.ini b/examples/miniapps/application-single-container/config.ini new file mode 100644 index 00000000..4da3e155 --- /dev/null +++ b/examples/miniapps/application-single-container/config.ini @@ -0,0 +1,9 @@ +[database] +dsn=:memory: + +[aws] +access_key_id=KEY +secret_access_key=SECRET + +[auth] +token_ttl=3600 diff --git a/examples/miniapps/services_v2/example/__init__.py b/examples/miniapps/application-single-container/example/__init__.py similarity index 100% rename from examples/miniapps/services_v2/example/__init__.py rename to examples/miniapps/application-single-container/example/__init__.py diff --git a/examples/miniapps/application-single-container/example/__main__.py b/examples/miniapps/application-single-container/example/__main__.py new file mode 100644 index 00000000..a14d92c9 --- /dev/null +++ b/examples/miniapps/application-single-container/example/__main__.py @@ -0,0 +1,24 @@ +"""Main module.""" + +import sys + +from .containers import Container + + +def main(email: str, password: str, photo: str) -> None: + container = Container() + + container.configure_logging() + container.config.from_ini('config.ini') + + user_service = container.user_service() + auth_service = container.auth_service() + photo_service = container.photo_service() + + user = user_service.get_user(email) + auth_service.authenticate(user, password) + photo_service.upload_photo(user, photo) + + +if __name__ == '__main__': + main(*sys.argv[1:]) diff --git a/examples/miniapps/application-single-container/example/containers.py b/examples/miniapps/application-single-container/example/containers.py new file mode 100644 index 00000000..ccc6a2d1 --- /dev/null +++ b/examples/miniapps/application-single-container/example/containers.py @@ -0,0 +1,52 @@ +"""Containers module.""" + +import logging.config +import sqlite3 + +import boto3 +from dependency_injector import containers, providers + +from . import services + + +class Container(containers.DeclarativeContainer): + + config = providers.Configuration() + + configure_logging = providers.Callable( + logging.config.fileConfig, + fname='logging.ini', + ) + + # Gateways + + database_client = providers.Singleton( + sqlite3.connect, + config.database.dsn, + ) + + s3_client = providers.Singleton( + boto3.client, + service_name='s3', + aws_access_key_id=config.aws.access_key_id, + aws_secret_access_key=config.aws.secret_access_key, + ) + + # Services + + user_service = providers.Factory( + services.UserService, + db=database_client, + ) + + auth_service = providers.Factory( + services.AuthService, + db=database_client, + token_ttl=config.auth.token_ttl.as_int(), + ) + + photo_service = providers.Factory( + services.PhotoService, + db=database_client, + s3=s3_client, + ) diff --git a/examples/miniapps/application-single-container/example/services.py b/examples/miniapps/application-single-container/example/services.py new file mode 100644 index 00000000..338888e3 --- /dev/null +++ b/examples/miniapps/application-single-container/example/services.py @@ -0,0 +1,56 @@ +"""Services module.""" + +import logging +import sqlite3 +from typing import Dict + +from mypy_boto3_s3 import S3Client + + +class BaseService: + + def __init__(self) -> None: + self.logger = logging.getLogger( + f'{__name__}.{self.__class__.__name__}', + ) + + +class UserService(BaseService): + + def __init__(self, db: sqlite3.Connection) -> None: + self.db = db + super().__init__() + + def get_user(self, email: str) -> Dict[str, str]: + self.logger.debug('User %s has been found in database', email) + return {'email': email, 'password_hash': '...'} + + +class AuthService(BaseService): + + def __init__(self, db: sqlite3.Connection, token_ttl: int) -> None: + self.db = db + self.token_ttl = token_ttl + super().__init__() + + def authenticate(self, user: Dict[str, str], password: str) -> None: + assert password is not None + self.logger.debug( + 'User %s has been successfully authenticated', + user['email'], + ) + + +class PhotoService(BaseService): + + def __init__(self, db: sqlite3.Connection, s3: S3Client) -> None: + self.db = db + self.s3 = s3 + super().__init__() + + def upload_photo(self, user: Dict[str, str], photo_path: str) -> None: + self.logger.debug( + 'Photo %s has been successfully uploaded by user %s', + photo_path, + user['email'], + ) diff --git a/examples/miniapps/application-single-container/logging.ini b/examples/miniapps/application-single-container/logging.ini new file mode 100644 index 00000000..5108c567 --- /dev/null +++ b/examples/miniapps/application-single-container/logging.ini @@ -0,0 +1,21 @@ +[loggers] +keys=root + +[handlers] +keys=stream_handler + +[formatters] +keys=formatter + +[logger_root] +level=DEBUG +handlers=stream_handler + +[handler_stream_handler] +class=StreamHandler +level=DEBUG +formatter=formatter +args=(sys.stderr,) + +[formatter_formatter] +format=[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s diff --git a/examples/miniapps/application-single-container/requirements.txt b/examples/miniapps/application-single-container/requirements.txt new file mode 100644 index 00000000..0c4ba267 --- /dev/null +++ b/examples/miniapps/application-single-container/requirements.txt @@ -0,0 +1,3 @@ +dependency-injector +boto3 +boto3-stubs[s3] diff --git a/examples/miniapps/services_v1/README.rst b/examples/miniapps/services_v1/README.rst deleted file mode 100644 index 87373537..00000000 --- a/examples/miniapps/services_v1/README.rst +++ /dev/null @@ -1,8 +0,0 @@ -Dependency Injector IoC containers example -========================================== - -Instructions for running - -.. code-block:: bash - - python run.py 1 secret photo.jpg diff --git a/examples/miniapps/services_v1/containers.py b/examples/miniapps/services_v1/containers.py deleted file mode 100644 index d1ae0a52..00000000 --- a/examples/miniapps/services_v1/containers.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Example of dependency injection in Python.""" - -import logging -import sqlite3 - -import boto3 - -import example.main -import example.services - -import dependency_injector.containers as containers -import dependency_injector.providers as providers - - -class Core(containers.DeclarativeContainer): - """IoC container of core component providers.""" - - config = providers.Configuration('config') - - logger = providers.Singleton(logging.Logger, name='example') - - -class Gateways(containers.DeclarativeContainer): - """IoC container of gateway (API clients to remote services) providers.""" - - database = providers.Singleton(sqlite3.connect, Core.config.database.dsn) - - s3 = providers.Singleton( - boto3.client, 's3', - aws_access_key_id=Core.config.aws.access_key_id, - aws_secret_access_key=Core.config.aws.secret_access_key) - - -class Services(containers.DeclarativeContainer): - """IoC container of business service providers.""" - - users = providers.Factory(example.services.UsersService, - db=Gateways.database, - logger=Core.logger) - - auth = providers.Factory(example.services.AuthService, - db=Gateways.database, - logger=Core.logger, - token_ttl=Core.config.auth.token_ttl) - - photos = providers.Factory(example.services.PhotosService, - db=Gateways.database, - s3=Gateways.s3, - logger=Core.logger) - - -class Application(containers.DeclarativeContainer): - """IoC container of application component providers.""" - - main = providers.Callable(example.main.main, - users_service=Services.users, - auth_service=Services.auth, - photos_service=Services.photos) diff --git a/examples/miniapps/services_v1/example/main.py b/examples/miniapps/services_v1/example/main.py deleted file mode 100644 index 2f80a1c7..00000000 --- a/examples/miniapps/services_v1/example/main.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Example main module.""" - - -def main(uid, password, photo, users_service, auth_service, photos_service): - """Authenticate user and upload photo.""" - user = users_service.get_user_by_id(uid) - auth_service.authenticate(user, password) - photos_service.upload_photo(user['uid'], photo) diff --git a/examples/miniapps/services_v1/example/services.py b/examples/miniapps/services_v1/example/services.py deleted file mode 100644 index 04206916..00000000 --- a/examples/miniapps/services_v1/example/services.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Example business services module.""" - - -class BaseService: - """Service base class.""" - - -class UsersService(BaseService): - """Users service.""" - - def __init__(self, logger, db): - """Initialize instance.""" - self.logger = logger - self.db = db - - def get_user_by_id(self, uid): - """Return user's data by identifier.""" - self.logger.debug('User %s has been found in database', uid) - return dict(uid=uid, password_hash='secret_hash') - - -class AuthService(BaseService): - """Authentication service.""" - - def __init__(self, logger, db, token_ttl): - """Initialize instance.""" - self.logger = logger - self.db = db - self.token_ttl = token_ttl - - def authenticate(self, user, password): - """Authenticate user.""" - assert user['password_hash'] == '_'.join((password, 'hash')) - self.logger.debug('User %s has been successfully authenticated', - user['uid']) - - -class PhotosService(BaseService): - """Photos service.""" - - def __init__(self, logger, db, s3): - """Initialize instance.""" - self.logger = logger - self.db = db - self.s3 = s3 - - def upload_photo(self, uid, photo_path): - """Upload user photo.""" - self.logger.debug('Photo %s has been successfully uploaded by user %s', - photo_path, uid) diff --git a/examples/miniapps/services_v1/run.py b/examples/miniapps/services_v1/run.py deleted file mode 100644 index da4f94b8..00000000 --- a/examples/miniapps/services_v1/run.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Run example application.""" - -import sys -import logging - -from containers import Core, Application - - -if __name__ == '__main__': - # Configure platform: - Core.config.override({'database': {'dsn': ':memory:'}, - 'aws': {'access_key_id': 'KEY', - 'secret_access_key': 'SECRET'}, - 'auth': {'token_ttl': 3600}}) - Core.logger().addHandler(logging.StreamHandler(sys.stdout)) - - # Run application: - Application.main(uid=sys.argv[1], - password=sys.argv[2], - photo=sys.argv[3]) diff --git a/examples/miniapps/services_v2/README.rst b/examples/miniapps/services_v2/README.rst deleted file mode 100644 index 87373537..00000000 --- a/examples/miniapps/services_v2/README.rst +++ /dev/null @@ -1,8 +0,0 @@ -Dependency Injector IoC containers example -========================================== - -Instructions for running - -.. code-block:: bash - - python run.py 1 secret photo.jpg diff --git a/examples/miniapps/services_v2/container.py b/examples/miniapps/services_v2/container.py deleted file mode 100644 index 8ca3c048..00000000 --- a/examples/miniapps/services_v2/container.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Example of dependency injection in Python.""" - -import logging -import sqlite3 - -import boto3 - -from dependency_injector import containers, providers -from example import services, main - - -class IocContainer(containers.DeclarativeContainer): - """Application IoC container.""" - - config = providers.Configuration('config') - logger = providers.Singleton(logging.Logger, name='example') - - # Gateways - - database_client = providers.Singleton(sqlite3.connect, config.database.dsn) - - s3_client = providers.Singleton( - boto3.client, 's3', - aws_access_key_id=config.aws.access_key_id, - aws_secret_access_key=config.aws.secret_access_key, - ) - - # Services - - users_service = providers.Factory( - services.UsersService, - db=database_client, - logger=logger, - ) - - auth_service = providers.Factory( - services.AuthService, - token_ttl=config.auth.token_ttl, - db=database_client, - logger=logger, - ) - - photos_service = providers.Factory( - services.PhotosService, - db=database_client, - s3=s3_client, - logger=logger, - ) - - # Misc - - main = providers.Callable( - main.main, - users_service=users_service, - auth_service=auth_service, - photos_service=photos_service, - ) diff --git a/examples/miniapps/services_v2/example/main.py b/examples/miniapps/services_v2/example/main.py deleted file mode 100644 index 2f80a1c7..00000000 --- a/examples/miniapps/services_v2/example/main.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Example main module.""" - - -def main(uid, password, photo, users_service, auth_service, photos_service): - """Authenticate user and upload photo.""" - user = users_service.get_user_by_id(uid) - auth_service.authenticate(user, password) - photos_service.upload_photo(user['uid'], photo) diff --git a/examples/miniapps/services_v2/example/services.py b/examples/miniapps/services_v2/example/services.py deleted file mode 100644 index 04206916..00000000 --- a/examples/miniapps/services_v2/example/services.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Example business services module.""" - - -class BaseService: - """Service base class.""" - - -class UsersService(BaseService): - """Users service.""" - - def __init__(self, logger, db): - """Initialize instance.""" - self.logger = logger - self.db = db - - def get_user_by_id(self, uid): - """Return user's data by identifier.""" - self.logger.debug('User %s has been found in database', uid) - return dict(uid=uid, password_hash='secret_hash') - - -class AuthService(BaseService): - """Authentication service.""" - - def __init__(self, logger, db, token_ttl): - """Initialize instance.""" - self.logger = logger - self.db = db - self.token_ttl = token_ttl - - def authenticate(self, user, password): - """Authenticate user.""" - assert user['password_hash'] == '_'.join((password, 'hash')) - self.logger.debug('User %s has been successfully authenticated', - user['uid']) - - -class PhotosService(BaseService): - """Photos service.""" - - def __init__(self, logger, db, s3): - """Initialize instance.""" - self.logger = logger - self.db = db - self.s3 = s3 - - def upload_photo(self, uid, photo_path): - """Upload user photo.""" - self.logger.debug('Photo %s has been successfully uploaded by user %s', - photo_path, uid) diff --git a/examples/miniapps/services_v2/run.py b/examples/miniapps/services_v2/run.py deleted file mode 100644 index 30edbabf..00000000 --- a/examples/miniapps/services_v2/run.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Run example of dependency injection in Python.""" - -import sys -import logging - -from container import IocContainer - - -if __name__ == '__main__': - # Configure container: - container = IocContainer( - config={ - 'database': { - 'dsn': ':memory:', - }, - 'aws': { - 'access_key_id': 'KEY', - 'secret_access_key': 'SECRET', - }, - 'auth': { - 'token_ttl': 3600, - }, - } - ) - container.logger().addHandler(logging.StreamHandler(sys.stdout)) - - # Run application: - container.main(*sys.argv[1:])