From 2d34a056c9a9cdb28d6499823427341411a29ed1 Mon Sep 17 00:00:00 2001 From: Roman Mogylatov Date: Fri, 4 Sep 2020 15:30:31 -0400 Subject: [PATCH] Refactor services mini app with single container --- .../services-single-container/README.rst | 29 ++++++++++ .../services-single-container/config.ini | 9 +++ .../example/__init__.py | 0 .../example/__main__.py | 24 ++++++++ .../example/containers.py | 52 +++++++++++++++++ .../example/services.py | 56 ++++++++++++++++++ .../services-single-container/logging.ini | 21 +++++++ .../requirements.txt | 2 + examples/miniapps/services_v2/README.rst | 8 --- examples/miniapps/services_v2/container.py | 57 ------------------- examples/miniapps/services_v2/example/main.py | 8 --- .../miniapps/services_v2/example/services.py | 50 ---------------- examples/miniapps/services_v2/run.py | 28 --------- 13 files changed, 193 insertions(+), 151 deletions(-) create mode 100644 examples/miniapps/services-single-container/README.rst create mode 100644 examples/miniapps/services-single-container/config.ini rename examples/miniapps/{services_v2 => services-single-container}/example/__init__.py (100%) create mode 100644 examples/miniapps/services-single-container/example/__main__.py create mode 100644 examples/miniapps/services-single-container/example/containers.py create mode 100644 examples/miniapps/services-single-container/example/services.py create mode 100644 examples/miniapps/services-single-container/logging.ini create mode 100644 examples/miniapps/services-single-container/requirements.txt delete mode 100644 examples/miniapps/services_v2/README.rst delete mode 100644 examples/miniapps/services_v2/container.py delete mode 100644 examples/miniapps/services_v2/example/main.py delete mode 100644 examples/miniapps/services_v2/example/services.py delete mode 100644 examples/miniapps/services_v2/run.py diff --git a/examples/miniapps/services-single-container/README.rst b/examples/miniapps/services-single-container/README.rst new file mode 100644 index 00000000..2679d9a5 --- /dev/null +++ b/examples/miniapps/services-single-container/README.rst @@ -0,0 +1,29 @@ +Services mini 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/services-single-container/config.ini b/examples/miniapps/services-single-container/config.ini new file mode 100644 index 00000000..4da3e155 --- /dev/null +++ b/examples/miniapps/services-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/services-single-container/example/__init__.py similarity index 100% rename from examples/miniapps/services_v2/example/__init__.py rename to examples/miniapps/services-single-container/example/__init__.py diff --git a/examples/miniapps/services-single-container/example/__main__.py b/examples/miniapps/services-single-container/example/__main__.py new file mode 100644 index 00000000..a14d92c9 --- /dev/null +++ b/examples/miniapps/services-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/services-single-container/example/containers.py b/examples/miniapps/services-single-container/example/containers.py new file mode 100644 index 00000000..9fcff43f --- /dev/null +++ b/examples/miniapps/services-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('config') + + 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/services-single-container/example/services.py b/examples/miniapps/services-single-container/example/services.py new file mode 100644 index 00000000..338888e3 --- /dev/null +++ b/examples/miniapps/services-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/services-single-container/logging.ini b/examples/miniapps/services-single-container/logging.ini new file mode 100644 index 00000000..5108c567 --- /dev/null +++ b/examples/miniapps/services-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/services-single-container/requirements.txt b/examples/miniapps/services-single-container/requirements.txt new file mode 100644 index 00000000..9c1de97c --- /dev/null +++ b/examples/miniapps/services-single-container/requirements.txt @@ -0,0 +1,2 @@ +boto3 +boto3-stubs[s3] 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:])