From 8798ea1a566863fe646ba79453d44ef448402838 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Thu, 6 Oct 2016 22:48:43 +0300 Subject: [PATCH 1/4] Update services example --- README.rst | 48 ++++++++++++++++--- examples/miniapps/services/README.rst | 2 +- examples/miniapps/services/containers.py | 9 +++- .../services/containers_alt_syntax_1.py | 16 +++++-- .../services/containers_alt_syntax_2.py | 16 +++++-- examples/miniapps/services/example/main.py | 8 ++-- .../miniapps/services/example/services.py | 21 +++++--- examples/miniapps/services/run.py | 39 ++++++++++++--- 8 files changed, 125 insertions(+), 34 deletions(-) diff --git a/README.rst b/README.rst index 5df70f81..6fa6030a 100644 --- a/README.rst +++ b/README.rst @@ -58,9 +58,11 @@ system that consists from several business and platform services: .. code-block:: python - """Example of several Dependency Injector IoC containers.""" + """Example of dependency injection in Python.""" + import logging import sqlite3 + import boto.s3.connection import example.main @@ -73,6 +75,8 @@ system that consists from several business and platform services: class Platform(containers.DeclarativeContainer): """IoC container of platform service providers.""" + logger = providers.Singleton(logging.Logger, name='example') + database = providers.Singleton(sqlite3.connect, ':memory:') s3 = providers.Singleton(boto.s3.connection.S3Connection, @@ -84,13 +88,16 @@ system that consists from several business and platform services: """IoC container of business service providers.""" users = providers.Factory(example.services.Users, + logger=Platform.logger, db=Platform.database) auth = providers.Factory(example.services.Auth, + logger=Platform.logger, db=Platform.database, token_ttl=3600) photos = providers.Factory(example.services.Photos, + logger=Platform.logger, db=Platform.database, s3=Platform.s3) @@ -107,25 +114,52 @@ Next example demonstrates usage of IoC containers & providers defined above: .. code-block:: python - """Run example application.""" + """Run dependency injection example application. - import containers + Instructions for running: + + python run.py 1 secret photo.jpg + """ + + import sys + import logging + + from containers import Platform, Application if __name__ == '__main__': - containers.Application.main() + # Configure platform logger: + Platform.logger().addHandler(logging.StreamHandler(sys.stdout)) + + # Run application: + Application.main(uid=sys.argv[1], + password=sys.argv[2], + photo=sys.argv[3]) # Previous call is an equivalent of next operations: # + # logger = logging.Logger(name='example') # database = sqlite3.connect(':memory:') # s3 = boto.s3.connection.S3Connection(aws_access_key_id='KEY', # aws_secret_access_key='SECRET') # - # example.main.main(users_service=example.services.Users(db=database), - # auth_service=example.services.Auth(db=database, + # example.main.main(uid=sys.argv[1], + # password=sys.argv[2], + # photo=sys.argv[3], + # users_service=example.services.Users(logger=logger, + # db=database), + # auth_service=example.services.Auth(logger=logger, + # db=database, # token_ttl=3600), - # photos_service=example.services.Photos(db=database, + # photos_service=example.services.Photos(logger=logger, + # db=database, # s3=s3)) + # + # Output: + # + # User 1 has been found in database + # User 1 has been successfully authenticated + # Photo photo.jpg has been successfully uploaded by user 1 Alternative definition styles ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/miniapps/services/README.rst b/examples/miniapps/services/README.rst index 9fa352ae..87373537 100644 --- a/examples/miniapps/services/README.rst +++ b/examples/miniapps/services/README.rst @@ -5,4 +5,4 @@ Instructions for running .. code-block:: bash - python run.py + python run.py 1 secret photo.jpg diff --git a/examples/miniapps/services/containers.py b/examples/miniapps/services/containers.py index e12f1436..e859e248 100644 --- a/examples/miniapps/services/containers.py +++ b/examples/miniapps/services/containers.py @@ -1,6 +1,8 @@ -"""Example of several Dependency Injector IoC containers.""" +"""Example of dependency injection in Python.""" +import logging import sqlite3 + import boto.s3.connection import example.main @@ -13,6 +15,8 @@ import dependency_injector.providers as providers class Platform(containers.DeclarativeContainer): """IoC container of platform service providers.""" + logger = providers.Singleton(logging.Logger, name='example') + database = providers.Singleton(sqlite3.connect, ':memory:') s3 = providers.Singleton(boto.s3.connection.S3Connection, @@ -24,13 +28,16 @@ class Services(containers.DeclarativeContainer): """IoC container of business service providers.""" users = providers.Factory(example.services.Users, + logger=Platform.logger, db=Platform.database) auth = providers.Factory(example.services.Auth, + logger=Platform.logger, db=Platform.database, token_ttl=3600) photos = providers.Factory(example.services.Photos, + logger=Platform.logger, db=Platform.database, s3=Platform.s3) diff --git a/examples/miniapps/services/containers_alt_syntax_1.py b/examples/miniapps/services/containers_alt_syntax_1.py index 11803302..bf7389d0 100644 --- a/examples/miniapps/services/containers_alt_syntax_1.py +++ b/examples/miniapps/services/containers_alt_syntax_1.py @@ -1,9 +1,11 @@ -"""Example of several Dependency Injector IoC containers. +"""Example of dependency injection in Python. Alternative injections definition style #1. """ +import logging import sqlite3 + import boto.s3.connection import example.main @@ -16,6 +18,9 @@ import dependency_injector.providers as providers class Platform(containers.DeclarativeContainer): """IoC container of platform service providers.""" + logger = providers.Singleton(logging.Logger) \ + .add_kwargs(name='example') + database = providers.Singleton(sqlite3.connect) \ .add_args(':memory:') @@ -28,14 +33,17 @@ class Services(containers.DeclarativeContainer): """IoC container of business service providers.""" users = providers.Factory(example.services.Users) \ - .add_kwargs(db=Platform.database) + .add_kwargs(logger=Platform.logger, + db=Platform.database) auth = providers.Factory(example.services.Auth) \ - .add_kwargs(db=Platform.database, + .add_kwargs(logger=Platform.logger, + db=Platform.database, token_ttl=3600) photos = providers.Factory(example.services.Photos) \ - .add_kwargs(db=Platform.database, + .add_kwargs(logger=Platform.logger, + db=Platform.database, s3=Platform.s3) diff --git a/examples/miniapps/services/containers_alt_syntax_2.py b/examples/miniapps/services/containers_alt_syntax_2.py index 8e196e62..1388c298 100644 --- a/examples/miniapps/services/containers_alt_syntax_2.py +++ b/examples/miniapps/services/containers_alt_syntax_2.py @@ -1,9 +1,11 @@ -"""Example of several Dependency Injector IoC containers. +"""Example of dependency injection in Python. Alternative injections definition style #2. """ +import logging import sqlite3 + import boto.s3.connection import example.main @@ -16,6 +18,9 @@ import dependency_injector.providers as providers class Platform(containers.DeclarativeContainer): """IoC container of platform service providers.""" + logger = providers.Singleton(logging.Logger) + logger.add_kwargs(name='example') + database = providers.Singleton(sqlite3.connect) database.add_args(':memory:') @@ -28,14 +33,17 @@ class Services(containers.DeclarativeContainer): """IoC container of business service providers.""" users = providers.Factory(example.services.Users) - users.add_kwargs(db=Platform.database) + users.add_kwargs(logger=Platform.logger, + db=Platform.database) auth = providers.Factory(example.services.Auth) - auth.add_kwargs(db=Platform.database, + auth.add_kwargs(logger=Platform.logger, + db=Platform.database, token_ttl=3600) photos = providers.Factory(example.services.Photos) - photos.add_kwargs(db=Platform.database, + photos.add_kwargs(logger=Platform.logger, + db=Platform.database, s3=Platform.s3) diff --git a/examples/miniapps/services/example/main.py b/examples/miniapps/services/example/main.py index 49de9e0f..d3399ff7 100644 --- a/examples/miniapps/services/example/main.py +++ b/examples/miniapps/services/example/main.py @@ -1,8 +1,8 @@ """Example main module.""" -def main(users_service, auth_service, photos_service): +def main(uid, password, photo, users_service, auth_service, photos_service): """Example main function.""" - user = users_service.get_user('user') - auth_service.authenticate(user, 'secret') - photos_service.upload_photo(user['id'], 'photo.jpg') + 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/example/services.py b/examples/miniapps/services/example/services.py index 615a80d5..171f2506 100644 --- a/examples/miniapps/services/example/services.py +++ b/examples/miniapps/services/example/services.py @@ -4,37 +4,44 @@ class Users(object): """Users service.""" - def __init__(self, db): + def __init__(self, logger, db): """Initializer.""" + self.logger = logger self.db = db - def get_user(self, login): + def get_user_by_id(self, uid): """Return user's information by login.""" - return {'id': 1, - 'login': login, + self.logger.debug('User %s has been found in database', uid) + return {'uid': uid, 'password_hash': 'secret_hash'} class Auth(object): """Auth service.""" - def __init__(self, db, token_ttl): + def __init__(self, logger, db, token_ttl): """Initializer.""" + 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 Photos(object): """Photos service.""" - def __init__(self, db, s3): + def __init__(self, logger, db, s3): """Initializer.""" + self.logger = logger self.db = db self.s3 = s3 - def upload_photo(self, user_id, photo_path): + 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/run.py b/examples/miniapps/services/run.py index 5c854202..ba97c5cd 100644 --- a/examples/miniapps/services/run.py +++ b/examples/miniapps/services/run.py @@ -1,19 +1,46 @@ -"""Run example application.""" +"""Run dependency injection example application. -import containers +Instructions for running: + + python run.py 1 secret photo.jpg +""" + +import sys +import logging + +from containers import Platform, Application if __name__ == '__main__': - containers.Application.main() + # Configure platform logger: + Platform.logger().addHandler(logging.StreamHandler(sys.stdout)) + + # Run application: + Application.main(uid=sys.argv[1], + password=sys.argv[2], + photo=sys.argv[3]) # Previous call is an equivalent of next operations: # + # logger = logging.Logger(name='example') # database = sqlite3.connect(':memory:') # s3 = boto.s3.connection.S3Connection(aws_access_key_id='KEY', # aws_secret_access_key='SECRET') # - # example.main.main(users_service=example.services.Users(db=database), - # auth_service=example.services.Auth(db=database, + # example.main.main(uid=sys.argv[1], + # password=sys.argv[2], + # photo=sys.argv[3], + # users_service=example.services.Users(logger=logger, + # db=database), + # auth_service=example.services.Auth(logger=logger, + # db=database, # token_ttl=3600), - # photos_service=example.services.Photos(db=database, + # photos_service=example.services.Photos(logger=logger, + # db=database, # s3=s3)) + # + # Output: + # + # User 1 has been found in database + # User 1 has been successfully authenticated + # Photo photo.jpg has been successfully uploaded by user 1 From 8e290fb674ff4beb92b0365e9c00b3dc55966c04 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Thu, 6 Oct 2016 23:15:41 +0300 Subject: [PATCH 2/4] Update short description --- README.rst | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index 6fa6030a..50188df5 100644 --- a/README.rst +++ b/README.rst @@ -3,8 +3,8 @@ Dependency Injector - Python dependency injection framework =========================================================== *Dependency Injector* is a Python dependency injection framework. It was -designed to be unified, developer-friendly tool for managing any kind -of Python objects and their dependencies in formal, pretty way. +designed to be unified, developer-friendly tool that helps to implement +dependency injection pattern in formal, pretty, Pythonic way. *Dependency Injector* framework key features are: @@ -45,16 +45,22 @@ Status Installation ------------ -*Dependency Injector* library is available on PyPi_:: +*Dependency Injector* library is available on `PyPi`_:: pip install dependency_injector -Example -------- +Dependency Injection +-------------------- + +Inversion of control +-------------------- + +Example of dependency injection +------------------------------- Brief example below demonstrates usage of *Dependency Injector* containers and providers for definition of several IoC containers for some microservice -system that consists from several business and platform services: +system: .. code-block:: python @@ -110,7 +116,8 @@ system that consists from several business and platform services: auth_service=Services.auth, photos_service=Services.photos) -Next example demonstrates usage of IoC containers & providers defined above: +Next example demonstrates run of dependency injection example application +defined above: .. code-block:: python @@ -161,8 +168,8 @@ Next example demonstrates usage of IoC containers & providers defined above: # User 1 has been successfully authenticated # Photo photo.jpg has been successfully uploaded by user 1 -Alternative definition styles -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Alternative definition styles of providers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *Dependecy Injector* supports few other styles of dependency injections definition. @@ -174,6 +181,9 @@ IoC containers from previous example could look like these: class Platform(containers.DeclarativeContainer): """IoC container of platform service providers.""" + logger = providers.Singleton(logging.Logger) \ + .add_kwargs(name='example') + database = providers.Singleton(sqlite3.connect) \ .add_args(':memory:') @@ -188,6 +198,9 @@ or like this these: class Platform(containers.DeclarativeContainer): """IoC container of platform service providers.""" + logger = providers.Singleton(logging.Logger) + logger.add_kwargs(name='example') + database = providers.Singleton(sqlite3.connect) database.add_args(':memory:') @@ -195,6 +208,7 @@ or like this these: s3.add_kwargs(aws_access_key_id='KEY', aws_secret_access_key='SECRET') + You can get more *Dependency Injector* examples in ``/examples`` directory on GitHub: @@ -222,7 +236,3 @@ Your feedback is quite important! .. _PyPi: https://pypi.python.org/pypi/dependency_injector .. _User's guide: http://python-dependency-injector.ets-labs.org/en/stable/ .. _API docs: http://python-dependency-injector.ets-labs.org/en/stable/api/ -.. _SLOC: http://en.wikipedia.org/wiki/Source_lines_of_code -.. _SOLID: http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29 -.. _IoC: http://en.wikipedia.org/wiki/Inversion_of_control -.. _dependency injection: http://en.wikipedia.org/wiki/Dependency_injection From 6c25c91146f22c80728dd5f34d301cf0c336276f Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Thu, 6 Oct 2016 23:25:14 +0300 Subject: [PATCH 3/4] Update short description on docs index page --- docs/index.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 9ac141f1..6703324f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,3 +1,4 @@ +============================================================= Dependency Injector --- Python dependency injection framework ============================================================= @@ -10,8 +11,8 @@ Dependency Injector --- Python dependency injection framework their dependencies in formal, pretty way. *Dependency Injector* is a Python dependency injection framework. It was -designed to be unified, developer-friendly tool for managing any kind -of Python objects and their dependencies in formal, pretty way. +designed to be unified, developer-friendly tool that helps to implement +dependency injection pattern in formal, pretty, Pythonic way. *Dependency Injector* framework key features are: From 34a5cf8515d8164e981a3f786d4681f85d45abf9 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Fri, 7 Oct 2016 14:52:28 +0300 Subject: [PATCH 4/4] Update readme - add info about dependency injection --- README.rst | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 50188df5..f1055937 100644 --- a/README.rst +++ b/README.rst @@ -52,15 +52,94 @@ Installation Dependency Injection -------------------- -Inversion of control --------------------- +`Dependency injection`_ is a software design pattern that implements +`Inversion of control`_ for resolving dependencies. Formally, if object **A** +depends on object **B**, object **A** must not create or import object **B**, +but provide a way for injecting object **B** (object **B** could be injected +into object **A** in several ways: by passing it as ``__init__`` argument, by +setting it as attribute's value or by passing it as method's argument). + +Dependency injection pattern has few strict rules that should be followed: + ++ Object **A** (the client) delegates to external code (the dependency + injector) the responsibility of providing its dependencies - object **B** + (the service). ++ The client doesn't know how to create the service, it knows only interface + of service. ++ The service doesn't know that it is used by the client. ++ The dependency injector knows how to create the client. ++ The dependency injector knows how to create the service. ++ The dependency injector knows that the client depends on the service. ++ The dependency injector knows how to inject the service into the client. ++ The client knows nothing about the dependency injector. ++ The service knows nothing about the dependency injector. + +Next two examples demonstrate refactoring of a small piece of code to +dependency injection pattern: + +.. code-block:: python + + """Car & Engine example.""" + + + class Engine(object): + """Example engine.""" + + + class Car(object): + """Example car.""" + + def __init__(self): + """Initializer.""" + self.engine = Engine() # Engine is a "hardcoded" dependency + + + if __name__ == '__main__': + car = Car() # Application creates Car's instance + +``Car`` **creates** an ``Engine`` during its creation. Really? Does it make +more sense than creating an ``Engine`` separately and then +**inject it into** ``Car`` when ``Car`` is being created? Looks more +realistic, right? + +.. code-block:: python + + """Refactored Car & Engine example that demonstrates dependency injection.""" + + + class Engine(object): + """Example engine.""" + + + class Car(object): + """Example car.""" + + def __init__(self, engine): + """Initializer.""" + self.engine = engine # Engine is an "injected" dependency + + + if __name__ == '__main__': + engine = Engine() # Application creates Engine's instance + car = Car(engine) # and inject it into the Car's instance + +Advantages of dependency injection +---------------------------------- + +Dependency injection pattern provides next advantages: + ++ Control on application structure. ++ Decreased coupling between application components. ++ Increased code reusability. ++ Increased testability. ++ Increased maintainability. ++ Reconfiguration of system without rebuilding. Example of dependency injection ------------------------------- -Brief example below demonstrates usage of *Dependency Injector* containers and -providers for definition of several IoC containers for some microservice -system: +Brief example below demonstrates usage of *Dependency Injector* for creating +several IoC containers for some microservice system: .. code-block:: python @@ -233,6 +312,8 @@ Feel free to post questions, bugs, feature requests, proposals etc. on Your feedback is quite important! +.. _Dependency injection: http://en.wikipedia.org/wiki/Dependency_injection +.. _Inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control .. _PyPi: https://pypi.python.org/pypi/dependency_injector .. _User's guide: http://python-dependency-injector.ets-labs.org/en/stable/ .. _API docs: http://python-dependency-injector.ets-labs.org/en/stable/api/