From 04f22e6152492e5eed7b0798437e8399a9253c13 Mon Sep 17 00:00:00 2001 From: Roman Mogylatov Date: Fri, 4 Sep 2020 16:10:01 -0400 Subject: [PATCH] Refactor multiple containers example --- .../services-multiple-containers/README.rst | 29 +++++++ .../services-multiple-containers/config.yml | 30 +++++++ .../example/__init__.py | 0 .../example/__main__.py | 24 ++++++ .../example/containers.py | 80 +++++++++++++++++++ .../example/services.py | 56 +++++++++++++ .../requirements.txt | 3 + examples/miniapps/services_v1/README.rst | 8 -- examples/miniapps/services_v1/containers.py | 58 -------------- examples/miniapps/services_v1/example/main.py | 8 -- .../miniapps/services_v1/example/services.py | 50 ------------ examples/miniapps/services_v1/run.py | 20 ----- 12 files changed, 222 insertions(+), 144 deletions(-) create mode 100644 examples/miniapps/services-multiple-containers/README.rst create mode 100644 examples/miniapps/services-multiple-containers/config.yml rename examples/miniapps/{services_v1 => services-multiple-containers}/example/__init__.py (100%) create mode 100644 examples/miniapps/services-multiple-containers/example/__main__.py create mode 100644 examples/miniapps/services-multiple-containers/example/containers.py create mode 100644 examples/miniapps/services-multiple-containers/example/services.py create mode 100644 examples/miniapps/services-multiple-containers/requirements.txt delete mode 100644 examples/miniapps/services_v1/README.rst delete mode 100644 examples/miniapps/services_v1/containers.py delete mode 100644 examples/miniapps/services_v1/example/main.py delete mode 100644 examples/miniapps/services_v1/example/services.py delete mode 100644 examples/miniapps/services_v1/run.py diff --git a/examples/miniapps/services-multiple-containers/README.rst b/examples/miniapps/services-multiple-containers/README.rst new file mode 100644 index 00000000..03b3f020 --- /dev/null +++ b/examples/miniapps/services-multiple-containers/README.rst @@ -0,0 +1,29 @@ +Services mini 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/services-multiple-containers/config.yml b/examples/miniapps/services-multiple-containers/config.yml new file mode 100644 index 00000000..6a568526 --- /dev/null +++ b/examples/miniapps/services-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/services-multiple-containers/example/__init__.py similarity index 100% rename from examples/miniapps/services_v1/example/__init__.py rename to examples/miniapps/services-multiple-containers/example/__init__.py diff --git a/examples/miniapps/services-multiple-containers/example/__main__.py b/examples/miniapps/services-multiple-containers/example/__main__.py new file mode 100644 index 00000000..1d8eefc4 --- /dev/null +++ b/examples/miniapps/services-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/services-multiple-containers/example/containers.py b/examples/miniapps/services-multiple-containers/example/containers.py new file mode 100644 index 00000000..5bcec302 --- /dev/null +++ b/examples/miniapps/services-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/services-multiple-containers/example/services.py b/examples/miniapps/services-multiple-containers/example/services.py new file mode 100644 index 00000000..338888e3 --- /dev/null +++ b/examples/miniapps/services-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/services-multiple-containers/requirements.txt b/examples/miniapps/services-multiple-containers/requirements.txt new file mode 100644 index 00000000..5d5e226a --- /dev/null +++ b/examples/miniapps/services-multiple-containers/requirements.txt @@ -0,0 +1,3 @@ +dependency-injector[yaml] +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])