diff --git a/docs/examples-other/bundles_miniapp.rst b/docs/examples-other/bundles_miniapp.rst deleted file mode 100644 index 4be60cb5..00000000 --- a/docs/examples-other/bundles_miniapp.rst +++ /dev/null @@ -1,74 +0,0 @@ -Bundles mini application example --------------------------------- - -.. currentmodule:: dependency_injector.containers - -"Bundles" is an example mini application that is intended to demonstrate the -power of dependency injection for creation of re-usable application components -("bundles") with 100% transparency of their dependencies. - -Example application -~~~~~~~~~~~~~~~~~~~ - -"Bundles" mini application has next structure: - -.. code-block:: bash - - bundles/ - bundles/ <-- Bundles package - photos/ <-- Photos bundle - __init__.py <-- Photos bundle dependency injection container - entities.py - repositories.py - users/ <-- Users bundle - __init__.py <-- Users bundle dependency injection container - entities.py - repositories.py - run.py <-- Entrypoint - - -IoC containers -~~~~~~~~~~~~~~ - -Next two listings show :py:class:`DeclarativeContainer`'s for "users" and -"photos" bundles. - -Listing of ``bundles/users/__init__.py``: - -.. literalinclude:: ../../examples/miniapps/bundles/bundles/users/__init__.py - :language: python - -.. note:: - - - ``Users`` container has dependency on database. - -Listing of ``bundles/photos/__init__.py``: - -.. literalinclude:: ../../examples/miniapps/bundles/bundles/photos/__init__.py - :language: python - -.. note:: - - - ``Photos`` container has dependencies on database and file storage. - -Run application -~~~~~~~~~~~~~~~ - -Finally, both "bundles" are initialized by providing needed dependencies. -Initialization of dependencies happens right in the runtime, not earlier. -Generally, it means, that any part of any bundle could be overridden on the -fly. - -Listing of ``run.py``: - -.. literalinclude:: ../../examples/miniapps/bundles/run.py - :language: python - -Links -~~~~~ - -+ `Dependency Injector `_ -+ `Full example sources `_ - - -.. disqus:: diff --git a/docs/examples-other/index.rst b/docs/examples-other/index.rst index e99ec099..8b082fa8 100644 --- a/docs/examples-other/index.rst +++ b/docs/examples-other/index.rst @@ -8,14 +8,11 @@ Other examples 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. +This sections contains assorted ``Dependency Injector`` examples. .. toctree:: :maxdepth: 2 - bundles_miniapp use_cases_miniapp password_hashing_miniapp chained_factories diff --git a/docs/examples/decoupled-packages.rst b/docs/examples/decoupled-packages.rst new file mode 100644 index 00000000..a030938e --- /dev/null +++ b/docs/examples/decoupled-packages.rst @@ -0,0 +1,130 @@ +Decoupled packages example (multiple containers) +================================================ + +.. meta:: + :keywords: Python,Dependency Injection,Inversion of Control,Container,Example,Application, + Framework,AWS,boto3,client + :description: This example shows how to use Dependency Injector to create Python decoupled packages. + To achieve a decoupling each package has a container with the components. When + a component needs a dependency from the outside of the package scope we use the + Dependency provider. The package container has no knowledge on where the + dependencies come from. It states a need that the dependencies must be provided. + This helps to decouple a package from the 3rd party dependencies and other + packages. + +This example shows how to use ``Dependency Injector`` to create decoupled packages. + +To achieve a decoupling each package has a container with the components. When a component needs a +dependency from the outside of the package scope we use the ``Dependency`` provider. The package +container has no knowledge on where the dependencies come from. It states a need that the +dependencies must be provided. This helps to decouple a package from the 3rd party dependencies +and other packages. + +To wire the packages we use an application container. Application container has all 3rd party +dependencies and package containers. It wires the packages and dependencies to create a +complete application. + +We build an example micro application that consists of 3 packages: + +- ``user`` - a package with user domain logic, depends on a database +- ``photo`` - a package with photo domain logic, depends on a database and AWS S3 +- ``analytics`` - a package with analytics domain logic, depends on the ``user`` and ``photo`` + package components + +.. image:: images/decoupled-packages.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/ + │ ├── analytics/ + │ │ ├── __init__.py + │ │ ├── containers.py + │ │ └── services.py + │ ├── photo/ + │ │ ├── __init__.py + │ │ ├── containers.py + │ │ ├── entities.py + │ │ └── repositories.py + │ ├── user/ + │ │ ├── __init__.py + │ │ ├── containers.py + │ │ ├── entities.py + │ │ └── repositories.py + │ ├── __init__.py + │ ├── __main__.py + │ └── containers.py + ├── config.ini + └── requirements.txt + +Package containers +------------------ + +Listing of the ``example/user/containers.py``: + +.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/user/containers.py + :language: python + +Listing of the ``example/photo/containers.py``: + +.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/photo/containers.py + :language: python + +Listing of the ``example/analytics/containers.py``: + +.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/analytics/containers.py + :language: python + +Application container +--------------------- + +Application container consists of all packages and 3rd party dependencies. Its role is to wire +everything together in a complete application. + +Listing of the ``example/containers.py``: + +.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/containers.py + :language: python + +.. note:: + Package ``analytics`` has dependencies on the repositories from the ``user`` and + ``photo`` packages. This is an example of how you can pass the dependencies from one package + to another. + +Main module +----------- +Listing of the ``example/__main__.py``: + +.. literalinclude:: ../../examples/miniapps/decoupled-packages/example/__main__.py + :language: python + +Configuration +------------- + +Listing of the ``config.ini``: + +.. literalinclude:: ../../examples/miniapps/decoupled-packages/config.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/decoupled-packages.png b/docs/examples/images/decoupled-packages.png new file mode 100644 index 00000000..4e626b0c Binary files /dev/null and b/docs/examples/images/decoupled-packages.png differ diff --git a/docs/examples/index.rst b/docs/examples/index.rst index 2d3545f2..17c62462 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -12,5 +12,6 @@ Explore the examples to see the ``Dependency Injector`` in action. application-single-container application-multiple-containers + decoupled-packages .. disqus:: diff --git a/examples/miniapps/bundles/README.rst b/examples/miniapps/bundles/README.rst deleted file mode 100644 index a794d4b5..00000000 --- a/examples/miniapps/bundles/README.rst +++ /dev/null @@ -1,8 +0,0 @@ -Dependency Injector Bundles example -=================================== - -Instructions for running - -.. code-block:: bash - - python run.py diff --git a/examples/miniapps/bundles/bundles/__init__.py b/examples/miniapps/bundles/bundles/__init__.py deleted file mode 100644 index de248351..00000000 --- a/examples/miniapps/bundles/bundles/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Bundles package.""" diff --git a/examples/miniapps/bundles/bundles/photos/__init__.py b/examples/miniapps/bundles/bundles/photos/__init__.py deleted file mode 100644 index 08f443f4..00000000 --- a/examples/miniapps/bundles/bundles/photos/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Photos bundle.""" - -from dependency_injector import containers -from dependency_injector import providers - -from . import entities -from . import repositories - - -class Photos(containers.DeclarativeContainer): - """Photos bundle container.""" - - database = providers.Dependency() - file_storage = providers.Dependency() - - photo = providers.Factory(entities.Photo) - photo_repository = providers.Singleton(repositories.PhotoRepository, - object_factory=photo.provider, - fs=file_storage, - db=database) diff --git a/examples/miniapps/bundles/bundles/photos/entities.py b/examples/miniapps/bundles/bundles/photos/entities.py deleted file mode 100644 index f93b9f22..00000000 --- a/examples/miniapps/bundles/bundles/photos/entities.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Photos bundle entities module.""" - - -class Photo: - """Photo entity.""" diff --git a/examples/miniapps/bundles/bundles/photos/repositories.py b/examples/miniapps/bundles/bundles/photos/repositories.py deleted file mode 100644 index 229b907e..00000000 --- a/examples/miniapps/bundles/bundles/photos/repositories.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Photos bundle entity repositories module.""" - - -class PhotoRepository: - """Photo entity repository.""" - - def __init__(self, object_factory, fs, db): - """Initialize instance.""" - self.object_factory = object_factory - self.fs = fs - self.db = db diff --git a/examples/miniapps/bundles/bundles/users/__init__.py b/examples/miniapps/bundles/bundles/users/__init__.py deleted file mode 100644 index 6b23144e..00000000 --- a/examples/miniapps/bundles/bundles/users/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Users bundle.""" - -from dependency_injector import containers -from dependency_injector import providers - -from . import entities -from . import repositories - - -class Users(containers.DeclarativeContainer): - """Users bundle container.""" - - database = providers.Dependency() - - user = providers.Factory(entities.User) - user_repository = providers.Singleton(repositories.UserRepository, - object_factory=user.provider, - db=database) diff --git a/examples/miniapps/bundles/bundles/users/entities.py b/examples/miniapps/bundles/bundles/users/entities.py deleted file mode 100644 index 399b3164..00000000 --- a/examples/miniapps/bundles/bundles/users/entities.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Users bundle entities module.""" - - -class User: - """User entity.""" - - def __init__(self, id): - """Initialize instance.""" - self.id = id diff --git a/examples/miniapps/bundles/bundles/users/repositories.py b/examples/miniapps/bundles/bundles/users/repositories.py deleted file mode 100644 index cf4ad06b..00000000 --- a/examples/miniapps/bundles/bundles/users/repositories.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Users bundle entity repositories module.""" - - -class UserRepository: - """User entity repository.""" - - def __init__(self, object_factory, db): - """Initialize instance.""" - self.object_factory = object_factory - self.db = db - - def get(self, id): - """Return user entity with given identifier.""" - return self.object_factory(id=id) diff --git a/examples/miniapps/bundles/run.py b/examples/miniapps/bundles/run.py deleted file mode 100644 index 3749cea6..00000000 --- a/examples/miniapps/bundles/run.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Run 'Bundles' example application.""" - -import sqlite3 -import boto3 - -from dependency_injector import containers -from dependency_injector import providers - -from bundles.users import Users -from bundles.photos import Photos - - -class Core(containers.DeclarativeContainer): - """Core container.""" - - config = providers.Configuration('config') - sqlite = providers.Singleton(sqlite3.connect, config.database.dsn) - s3 = providers.Singleton( - boto3.client, 's3', - aws_access_key_id=config.aws.access_key_id, - aws_secret_access_key=config.aws.secret_access_key) - - -if __name__ == '__main__': - # Initializing containers - core = Core(config={'database': {'dsn': ':memory:'}, - 'aws': {'access_key_id': 'KEY', - 'secret_access_key': 'SECRET'}}) - users = Users(database=core.sqlite) - photos = Photos(database=core.sqlite, file_storage=core.s3) - - # Fetching few users - user_repository = users.user_repository() - user1 = user_repository.get(id=1) - user2 = user_repository.get(id=2) - - # Making some checks - assert user1.id == 1 - assert user2.id == 2 - assert user_repository.db is core.sqlite() diff --git a/examples/miniapps/decoupled-packages/README.rst b/examples/miniapps/decoupled-packages/README.rst new file mode 100644 index 00000000..94b42003 --- /dev/null +++ b/examples/miniapps/decoupled-packages/README.rst @@ -0,0 +1,29 @@ +Decoupled packages example +========================== + +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 + +You should see: + +.. code-block:: bash + + Retrieve user id=1, photos count=5 + Retrieve user id=2, photos count=10 + Aggregate analytics from user and photo bundles diff --git a/examples/miniapps/decoupled-packages/config.ini b/examples/miniapps/decoupled-packages/config.ini new file mode 100644 index 00000000..c2a88c21 --- /dev/null +++ b/examples/miniapps/decoupled-packages/config.ini @@ -0,0 +1,6 @@ +[database] +dsn=:memory: + +[aws] +access_key_id=KEY +secret_access_key=SECRET diff --git a/examples/miniapps/decoupled-packages/example/__init__.py b/examples/miniapps/decoupled-packages/example/__init__.py new file mode 100644 index 00000000..1c744ca5 --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/__init__.py @@ -0,0 +1 @@ +"""Top-level package.""" diff --git a/examples/miniapps/decoupled-packages/example/__main__.py b/examples/miniapps/decoupled-packages/example/__main__.py new file mode 100644 index 00000000..2e4e74da --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/__main__.py @@ -0,0 +1,24 @@ +"""Main module.""" + +from .containers import ApplicationContainer + + +if __name__ == '__main__': + application = ApplicationContainer() + application.config.from_ini('config.ini') + + user_repository = application.user_package.user_repository() + photo_repository = application.photo_package.photo_repository() + + user1 = user_repository.get(id=1) + user1_photos = photo_repository.get_photos(user1.id) + print(f'Retrieve user id={user1.id}, photos count={len(user1_photos)}') + + user2 = user_repository.get(id=2) + user2_photos = photo_repository.get_photos(user2.id) + print(f'Retrieve user id={user2.id}, photos count={len(user2_photos)}') + + aggregation_service = application.analytics_package.aggregation_service() + assert aggregation_service.user_repository is user_repository + assert aggregation_service.photo_repository is photo_repository + print('Aggregate analytics from user and photo packages') diff --git a/examples/miniapps/decoupled-packages/example/analytics/__init__.py b/examples/miniapps/decoupled-packages/example/analytics/__init__.py new file mode 100644 index 00000000..b224cd40 --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/analytics/__init__.py @@ -0,0 +1 @@ +"""Analytics package.""" diff --git a/examples/miniapps/decoupled-packages/example/analytics/containers.py b/examples/miniapps/decoupled-packages/example/analytics/containers.py new file mode 100644 index 00000000..c4b5abba --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/analytics/containers.py @@ -0,0 +1,17 @@ +"""Analytics containers module.""" + +from dependency_injector import containers, providers + +from . import services + + +class AnalyticsContainer(containers.DeclarativeContainer): + + user_repository = providers.Dependency() + photo_repository = providers.Dependency() + + aggregation_service = providers.Singleton( + services.AggregationService, + user_repository=user_repository, + photo_repository=photo_repository, + ) diff --git a/examples/miniapps/decoupled-packages/example/analytics/services.py b/examples/miniapps/decoupled-packages/example/analytics/services.py new file mode 100644 index 00000000..46b42ae8 --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/analytics/services.py @@ -0,0 +1,8 @@ +"""Analytics services module.""" + + +class AggregationService: + + def __init__(self, user_repository, photo_repository): + self.user_repository = user_repository + self.photo_repository = photo_repository diff --git a/examples/miniapps/decoupled-packages/example/containers.py b/examples/miniapps/decoupled-packages/example/containers.py new file mode 100644 index 00000000..ede2bae9 --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/containers.py @@ -0,0 +1,41 @@ +"""Containers module.""" + +import sqlite3 + +import boto3 +from dependency_injector import containers, providers + +from .user.containers import UserContainer +from .photo.containers import PhotoContainer +from .analytics.containers import AnalyticsContainer + + +class ApplicationContainer(containers.DeclarativeContainer): + + config = providers.Configuration() + + sqlite = providers.Singleton(sqlite3.connect, config.database.dsn) + + s3 = 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, + ) + + user_package = providers.Container( + UserContainer, + database=sqlite, + ) + + photo_package = providers.Container( + PhotoContainer, + database=sqlite, + file_storage=s3, + ) + + analytics_package = providers.Container( + AnalyticsContainer, + user_repository=user_package.user_repository, + photo_repository=photo_package.photo_repository, + ) diff --git a/examples/miniapps/decoupled-packages/example/photo/__init__.py b/examples/miniapps/decoupled-packages/example/photo/__init__.py new file mode 100644 index 00000000..8ba19ff9 --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/photo/__init__.py @@ -0,0 +1 @@ +"""Photo package.""" diff --git a/examples/miniapps/decoupled-packages/example/photo/containers.py b/examples/miniapps/decoupled-packages/example/photo/containers.py new file mode 100644 index 00000000..a192185f --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/photo/containers.py @@ -0,0 +1,20 @@ +"""Photo containers module.""" + +from dependency_injector import containers, providers + +from . import entities, repositories + + +class PhotoContainer(containers.DeclarativeContainer): + + database = providers.Dependency() + file_storage = providers.Dependency() + + photo = providers.Factory(entities.Photo) + + photo_repository = providers.Singleton( + repositories.PhotoRepository, + entity_factory=photo.provider, + fs=file_storage, + db=database, + ) diff --git a/examples/miniapps/decoupled-packages/example/photo/entities.py b/examples/miniapps/decoupled-packages/example/photo/entities.py new file mode 100644 index 00000000..16029e34 --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/photo/entities.py @@ -0,0 +1,5 @@ +"""Photo entities module.""" + + +class Photo: + ... diff --git a/examples/miniapps/decoupled-packages/example/photo/repositories.py b/examples/miniapps/decoupled-packages/example/photo/repositories.py new file mode 100644 index 00000000..b3c3d3b4 --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/photo/repositories.py @@ -0,0 +1,12 @@ +"""Photo repositories module.""" + + +class PhotoRepository: + + def __init__(self, entity_factory, fs, db): + self.entity_factory = entity_factory + self.fs = fs + self.db = db + + def get_photos(self, user_id): + return [self.entity_factory() for _ in range(user_id*5)] diff --git a/examples/miniapps/decoupled-packages/example/user/__init__.py b/examples/miniapps/decoupled-packages/example/user/__init__.py new file mode 100644 index 00000000..ba752168 --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/user/__init__.py @@ -0,0 +1 @@ +"""User package.""" diff --git a/examples/miniapps/decoupled-packages/example/user/containers.py b/examples/miniapps/decoupled-packages/example/user/containers.py new file mode 100644 index 00000000..e9e6ff72 --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/user/containers.py @@ -0,0 +1,18 @@ +"""User containers module.""" + +from dependency_injector import containers, providers + +from . import entities, repositories + + +class UserContainer(containers.DeclarativeContainer): + + database = providers.Dependency() + + user = providers.Factory(entities.User) + + user_repository = providers.Singleton( + repositories.UserRepository, + entity_factory=user.provider, + db=database, + ) diff --git a/examples/miniapps/decoupled-packages/example/user/entities.py b/examples/miniapps/decoupled-packages/example/user/entities.py new file mode 100644 index 00000000..b4a715ab --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/user/entities.py @@ -0,0 +1,7 @@ +"""User entities module.""" + + +class User: + + def __init__(self, id): + self.id = id diff --git a/examples/miniapps/decoupled-packages/example/user/repositories.py b/examples/miniapps/decoupled-packages/example/user/repositories.py new file mode 100644 index 00000000..75392433 --- /dev/null +++ b/examples/miniapps/decoupled-packages/example/user/repositories.py @@ -0,0 +1,11 @@ +"""User repositories module.""" + + +class UserRepository: + + def __init__(self, entity_factory, db): + self.entity_factory = entity_factory + self.db = db + + def get(self, id): + return self.entity_factory(id=id) diff --git a/examples/miniapps/decoupled-packages/requirements.txt b/examples/miniapps/decoupled-packages/requirements.txt new file mode 100644 index 00000000..5b522e73 --- /dev/null +++ b/examples/miniapps/decoupled-packages/requirements.txt @@ -0,0 +1,2 @@ +dependency-injector +boto3 diff --git a/src/dependency_injector/providers.pyi b/src/dependency_injector/providers.pyi index 53052c76..2b8b6855 100644 --- a/src/dependency_injector/providers.pyi +++ b/src/dependency_injector/providers.pyi @@ -59,7 +59,7 @@ class Delegate(Provider): class Dependency(Provider, Generic[T]): - def __init__(self, instance_of: Type[T]) -> None: ... + def __init__(self, instance_of: Type[T] = object) -> None: ... def __call__(self, *args: Injection, **kwargs: Injection) -> T: ... @property def provided(self) -> ProvidedInstance: ...