From 4cf3bdb1fb6eaea6e21398bbfba8b3c45406ab80 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 20 Apr 2016 14:19:54 +0300 Subject: [PATCH] Update examples and docs --- README.rst | 273 +++--------------- docs/examples/index.rst | 18 ++ docs/examples/movie_lister.rst | 104 +++++++ docs/images/miniapps/movie_lister/classes.png | Bin 0 -> 34605 bytes docs/index.rst | 4 +- docs/introduction/di_in_python.rst | 1 - docs/introduction/index.rst | 1 - docs/introduction/key_features.rst | 1 - docs/introduction/structure.rst | 1 - docs/introduction/what_is_di.rst | 1 - docs/main/changelog.rst | 2 + examples/advanced_usage/config_provider.py | 1 - examples/api_client.py | 65 ----- examples/catalog.py | 63 ---- examples/catalogs/bundles/catalogs.py | 5 - examples/catalogs/declarative.py | 2 - examples/catalogs/declarative_inheritance.py | 2 - examples/catalogs/declarative_injections.py | 3 - .../declarative_provider_type/catalog.py | 2 - examples/catalogs/override_declarative.py | 3 - .../override_declarative_by_dynamic.py | 2 - .../override_declarative_decorator.py | 3 - examples/initial.py | 58 ++++ examples/miniapps/api_client/api.py | 15 + examples/miniapps/api_client/main.py | 36 +++ examples/miniapps/api_client/models.py | 14 + examples/miniapps/api_client/tests.py | 12 + examples/miniapps/flask_services/app.py | 10 + .../flask_services/services/__init__.py | 8 + .../miniapps/movie_lister/movies/__init__.py | 4 + examples/{ => misc}/auth_system.py | 0 .../{ => misc}/catalog_providing_callbacks.py | 0 examples/services.py | 40 +++ 33 files changed, 363 insertions(+), 391 deletions(-) create mode 100644 docs/images/miniapps/movie_lister/classes.png delete mode 100644 examples/api_client.py delete mode 100644 examples/catalog.py create mode 100644 examples/initial.py create mode 100644 examples/miniapps/api_client/api.py create mode 100644 examples/miniapps/api_client/main.py create mode 100644 examples/miniapps/api_client/models.py create mode 100644 examples/miniapps/api_client/tests.py create mode 100644 examples/miniapps/flask_services/app.py create mode 100644 examples/miniapps/flask_services/services/__init__.py rename examples/{ => misc}/auth_system.py (100%) rename examples/{ => misc}/catalog_providing_callbacks.py (100%) create mode 100644 examples/services.py diff --git a/README.rst b/README.rst index 1cde7ab1..dd181897 100644 --- a/README.rst +++ b/README.rst @@ -55,259 +55,66 @@ Installation Examples -------- -API client example: - .. code-block:: python - """Pythonic way for Dependency Injection - API Client.""" - - from dependency_injector import providers - - from mock import Mock - - - class ApiClient(object): - """Some API client.""" - - def __init__(self, host, api_key): - """Initializer.""" - self.host = host - self.api_key = api_key - - def call(self, operation, data): - """Make some network operations.""" - print 'API call [{0}:{1}], method - {2}, data - {3}'.format( - self.host, self.api_key, operation, repr(data)) - - - class User(object): - """User model.""" - - def __init__(self, id, api_client): - """Initializer.""" - self.id = id - self.api_client = api_client - - def register(self): - """Register user.""" - self.api_client.call('register', {'id': self.id}) - - - # Creating ApiClient and User providers: - api_client = providers.Singleton(ApiClient, - host='production.com', - api_key='PROD_API_KEY') - user_factory = providers.Factory(User, - api_client=api_client) - - # Creating several users and register them: - user1 = user_factory(1) - user1.register() - # API call [production.com:PROD_API_KEY], method - register, data - {'id': 1} - - user2 = user_factory(2) - user2.register() - # API call [production.com:PROD_API_KEY], method - register, data - {'id': 2} - - # Mock ApiClient for testing: - with api_client.override(Mock(ApiClient)) as api_client_mock: - user = user_factory('test') - user.register() - api_client_mock().call.assert_called_with('register', {'id': 'test'}) - - - # Overriding of ApiClient on dev environment: - api_client.override(providers.Singleton(ApiClient, - host='localhost', - api_key='DEV_API_KEY')) - - user3 = user_factory(3) - user3.register() - # API call [localhost:DEV_API_KEY], method - register, data - {'id': 3} - -Auth system example: - -.. code-block:: python - - """Pythonic way for Dependency Injection - Auth System.""" - - from dependency_injector import providers - from dependency_injector import injections - - - @providers.DelegatedCallable - def get_user_info(user_id): - """Return user info.""" - raise NotImplementedError() - - - @providers.Factory - @injections.inject(get_user_info=get_user_info) - class AuthComponent(object): - """Some authentication component.""" - - def __init__(self, get_user_info): - """Initializer.""" - self.get_user_info = get_user_info - - def authenticate_user(self, token): - """Authenticate user by token.""" - user_info = self.get_user_info(user_id=token + '1') - return user_info - - - print AuthComponent - print get_user_info - - - @providers.override(get_user_info) - @providers.DelegatedCallable - def get_user_info(user_id): - """Return user info.""" - return {'user_id': user_id} - - - print AuthComponent().authenticate_user(token='abc') - # {'user_id': 'abc1'} - -Service providers catalog example: - -.. code-block:: python - - """Pythonic way for Dependency Injection - Service Providers Catalog.""" + """Dependency Injector initial example.""" + import sys import sqlite3 + import boto.s3.connection + + import services from dependency_injector import catalogs from dependency_injector import providers from dependency_injector import injections - class UsersService(object): - """Users service, that has dependency on database.""" - - def __init__(self, db): - """Initializer.""" - self.db = db - - - class AuthService(object): - """Auth service, that has dependencies on users service and database.""" - - def __init__(self, db, users_service): - """Initializer.""" - self.db = db - self.users_service = users_service - - - class Services(catalogs.DeclarativeCatalog): - """Catalog of service providers.""" + class Platform(catalogs.DeclarativeCatalog): + """Catalog of platform service providers.""" database = providers.Singleton(sqlite3.connect, ':memory:') - users = providers.Factory(UsersService, - db=database) - - auth = providers.Factory(AuthService, - db=database, - users_service=users) - - - # Retrieving catalog providers: - users_service = Services.users() - auth_service = Services.auth() - - # Making some asserts: - assert users_service.db is auth_service.db is Services.database() - assert isinstance(auth_service.users_service, UsersService) - assert users_service is not Services.users() - assert auth_service is not Services.auth() - - - # Making some "inline" injections: - @injections.inject(users_service=Services.users) - @injections.inject(auth_service=Services.auth) - @injections.inject(database=Services.database) - def example(users_service, auth_service, database): - """Example callback.""" - assert users_service.db is auth_service.db - assert auth_service.db is database - assert database is Services.database() - - - # Making a call of decorated callback: - example() - -Providing callbacks catalog example: - -.. code-block:: python - - """Pythonic way for Dependency Injection - Providing Callbacks Catalog.""" - - import sqlite3 - - from dependency_injector import catalogs - from dependency_injector import providers - from dependency_injector import injections - - - class UsersService(object): - """Users service, that has dependency on database.""" - - def __init__(self, db): - """Initializer.""" - self.db = db - - - class AuthService(object): - """Auth service, that has dependencies on users service and database.""" - - def __init__(self, db, users_service): - """Initializer.""" - self.db = db - self.users_service = users_service + s3 = providers.Singleton(boto.s3.connection.S3Connection, + aws_access_key_id='KEY', + aws_secret_access_key='SECRET') class Services(catalogs.DeclarativeCatalog): - """Catalog of service providers.""" + """Catalog of business service providers.""" - @providers.Singleton - def database(): - """Provide database connection. + users = providers.Factory(services.Users, + db=Platform.database) - :rtype: providers.Provider -> sqlite3.Connection - """ - return sqlite3.connect(':memory:') + photos = providers.Factory(services.Photos, + db=Platform.database, + s3=Platform.s3) - @providers.Factory - @injections.inject(db=database) - def users(**kwargs): - """Provide users service. - - :rtype: providers.Provider -> UsersService - """ - return UsersService(**kwargs) - - @providers.Factory - @injections.inject(db=database) - @injections.inject(users_service=users) - def auth(**kwargs): - """Provide users service. - - :rtype: providers.Provider -> AuthService - """ - return AuthService(**kwargs) + auth = providers.Factory(services.Auth, + db=Platform.database, + token_ttl=3600) - # Retrieving catalog providers: - users_service = Services.users() - auth_service = Services.auth() + @injections.inject(users_service=Services.users) + @injections.inject(auth_service=Services.auth) + def main(argv, users_service, auth_service): + """Main function.""" + login, password, photo_path = argv[1:] - # Making some asserts: - assert users_service.db is auth_service.db is Services.database() - assert isinstance(auth_service.users_service, UsersService) - assert users_service is not Services.users() - assert auth_service is not Services.auth() + user = users_service.get_user(login) + auth_service.authenticate(user, password) + + upload_photo(user, photo_path) + + + @injections.inject(photos_service=Services.photos) + def upload_photo(user, photo_path, photos_service): + """Upload photo.""" + photos_service.upload_photo(user['id'], photo_path) + + + if __name__ == '__main__': + main(sys.argv) You can get more *Dependency Injector* examples in ``/examples`` directory on GitHub: @@ -334,8 +141,8 @@ Your feedback is quite important! .. _PyPi: https://pypi.python.org/pypi/dependency_injector -.. _User's guide: http://dependency_injector.readthedocs.org/en/stable/ -.. _API docs: http://dependency-injector.readthedocs.org/en/stable/api/ +.. _User's guide: http://dependency-injector.ets-labs.org/en/stable/ +.. _API docs: http://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 diff --git a/docs/examples/index.rst b/docs/examples/index.rst index e69de29b..907553f5 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -0,0 +1,18 @@ +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 on the top of inversion of control principle and +powered by *Dependency Injector* framework. + +.. toctree:: + :maxdepth: 2 + + movie_lister diff --git a/docs/examples/movie_lister.rst b/docs/examples/movie_lister.rst index e69de29b..8cf802e5 100644 --- a/docs/examples/movie_lister.rst +++ b/docs/examples/movie_lister.rst @@ -0,0 +1,104 @@ +Movie lister naive example +-------------------------- + +.. meta:: + :description: Dependency Injector is a Python dependency injection + framework. It was designed to be unified, developer's + friendly tool for managing any kind of Python objects and + their dependencies in formal, pretty way. + +This naive example was taken from Martin Fowler's article about dependency +injection and inversion of control: http://www.martinfowler.com/articles/injection.html + +Like Martin says: + +.. pull-quote:: + + *Like all of my examples it's one of those super-simple examples; + small enough to be unreal, but hopefully enough for you to visualize + what's going on without falling into the bog of a real example.* + +While original Martin's MovieLister example was a bit modified here, it +makes sense to provide some description. So, the idea of this example is to +create ``movies`` library that can be configurable to work with different +movie databases (csv, sqlite) and provide 2 main features: + +1. List all movies that were directed by certain person. +2. List all movies that were released in certain year. + +Also this example contains 3 mini applications that are based on ``movies`` +library : + +1. ``app_csv.py`` - list movies by certain criteria from csv file database. +2. ``app_db.py`` - list movies by certain criteria from sqlite database. +3. ``app_db_csv.py`` - list movies by certain criteria from csv file and + sqlite databases. + +Instructions for running: + +.. code-block:: bash + + python create_db.py + + python app_csv.py + python app_db.py + python app_db_csv.py + + +Full code of example could be found on GitHub_. + +Movies library +~~~~~~~~~~~~~~ + +Classes diagram: + +.. image:: /images/miniapps/movie_lister/classes.png + :width: 100% + :align: center + + +Movies library structure: + +.. code-block:: bash + + /movies + /__init__.py + /finders.py + /listers.py + /models.py + + +Listing of ``movies/__init__.py``: + +.. literalinclude:: ../../examples/miniapps/movie_lister/movies/__init__.py + :language: python + :linenos: + +Csv application +~~~~~~~~~~~~~~~ + +Listing of ``app_csv.py``: + +.. literalinclude:: ../../examples/miniapps/movie_lister/app_csv.py + :language: python + :linenos: + +Database application +~~~~~~~~~~~~~~~~~~~~ + +Listing of ``app_db.py``: + +.. literalinclude:: ../../examples/miniapps/movie_lister/app_db.py + :language: python + :linenos: + +Csv and database application +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Listing of ``app_db_csv.py``: + +.. literalinclude:: ../../examples/miniapps/movie_lister/app_db_csv.py + :language: python + :linenos: + +.. _GitHub: https://github.com/ets-labs/dependency_injector/tree/master/examples/miniapps/movie_lister diff --git a/docs/images/miniapps/movie_lister/classes.png b/docs/images/miniapps/movie_lister/classes.png new file mode 100644 index 0000000000000000000000000000000000000000..78db720d0181ff6b0531cceacf721d91b49af2bb GIT binary patch literal 34605 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

`l8JYuKnGeT^ns1t$79Z^H?&Qx3 z^6y?PSf^vHTp!`eD{ptV?mx#|((`cAtSIsfufNoxdm=!*tT!9er)rCB|17W=5e6`k z{t;JTo-{@`@jtG>{}&I?eF>;K(_-p2-}>YLv)hmlql5X|n*Yy@c;<*KBIwomll`-Y z>g*_P!q4K^Z+A~$X3zBn@pKDch5-=r0HKKpEH5v=`y}Yczyfvi$8bi1==p;SUt%gI zCQj=1z>t%J5LKTP!^x?sf2No=XvNAgV=<_N+T-TI7ZX4+d-K6bW5`Nq7E^9#514)1KZLhnyVXG>4Zj8;*5EbOfBJ8&m( zem|hjAJTc7R2AIFZ=Q0D54Al$^a<7+Zn!x+We@O2IpyV{jN&z&5jzu$Ggm~ue20eX zXxTCIQn>HwSqj=GK;dD^jAA-qXyka}KZtt00HSGGEgvG!^dM>DW zE^ECXR>ZLLLvv1#iE6%uuliS(HoGhK4Eam#@4Na#QunT|cb=5V9D&4?h)=n4F2n7Z zK3H%JS^7RdEM#(~mB2!X9jXQ;PqHm5IE>7f{w#FerD8#A!5djxm&3o%^v%gA<|ci`glok6CuJ%) z;&gAI!A~VPk~HF|kP}Q;+BNtvMMsDae0&{2A+wl5w!Aaz+`C0>j#WQ9+f|oku8tYd zDjdk^5^%~&t;=l~6TwptkVk)O{^)jOKK^GhwFJSEwN4`W?ftq`k|tR(7w@hVCz8y- za{rDHLTG)!R+5|VMWQrOZbO>IyC*uFAH)d@SYhmBkQK!(Cc=!p6M|-gZZQwjR3I3n zjl>ug6@yZ)fvLVbgiGofrz!KY#O%VnR;8g?ZJAyAOvFBaLz(oz3|p(aujh{-QmXXP zKH)kldi4f5hS)2Wn#7xX3m&k^!VIEw!#~&q!VhAn0Xyz1kxkBzjLV#J-{-B!p(V*F zPm-AMCB-HuJ&R%ulj88usUda%dHDL3d%*3bK9I+S?@1jdT1@f5m^9&Fp0ieb8XLIp zYCI~#y=MRt;OYT9)$M%u3$3`ANPvLdSB9nZL6o^R_~Mcao>a4I@|7D{?SIxC4!mg= zOa2vD71Q~&e(d22aFa%Qz}z}tBJd-JuJLIJI6DgH-$s!51p5SZ`2Dz|f#uDwtfSJ- zQ!Vq*&qr$xwO2XkP*=sU{L-s8-g}+~xC6XWH-5e)Ob!F{$IH2Yl7l^V$%$qfzn80! zP<&HcSr{5!_Po$aNCM*y%~xgbgONZadLHvQE7lN~@J@G`Oq@sy)fCJ}_dEaaZ=fQ) zlQ%o4n_-Q!q~85*iL+-5T`Tov`Kl2canVCBOsw+7qHIBN(dOCi_7MwYpi`XYdN&-5 zma}o44Vt3{cl5w!N$vg(K}^F>ePIb4f4;Wn>RHWyT#5X1@eqA+9o;=_{am8^+mCAB z6V^r39a(qev^_=UfO>5(&+j?eRYzth4r73zY^CMo-ZUqz?}fYn)2T%dk(?f>B&qzy z5-qPz&A!3umDb(7pdY#TfD?IHoG9H#6l&XkvQbUU_s%-5Oy-tRi0e1JGK-+!y0cC;$?E34>A zl_yQ3Sij8h|35#rjc;V%CMAsRxr}OqKp+lJWn)iUD^EK~8+SY42S^AiD9R5N;TIG) y6nr2lC@v`?!V85;LZM>ZO|<{z3ol)49qoPp*I!WagcJi`0I4hMD1B6bh5k2b9?+ct literal 0 HcmV?d00001 diff --git a/docs/index.rst b/docs/index.rst index e4fcde00..a7242a4b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,8 +2,7 @@ Dependency Injector --- Python dependency injection framework ============================================================= .. meta:: - :keywords: Python,DI,Dependency injection,IoC,Inversion of Control - :google-site-verification: mPdgVBidFHVWRNh0lhxj7q9zi-CpwIU970jINTBKLYQ + :google-site-verification: 6it89zX0_wccKEhAqbAiYQooS95f0BA8YfesHk6bsNA :description: Dependency Injector is a Python dependency injection framework. It was designed to be unified, developer's friendly tool for managing any kind of Python objects and @@ -63,6 +62,7 @@ Contents providers/index catalogs/index advanced_usage/index + examples/index api/index main/feedback main/changelog diff --git a/docs/introduction/di_in_python.rst b/docs/introduction/di_in_python.rst index 0e81c1b2..27010b14 100644 --- a/docs/introduction/di_in_python.rst +++ b/docs/introduction/di_in_python.rst @@ -2,7 +2,6 @@ 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 diff --git a/docs/introduction/index.rst b/docs/introduction/index.rst index 3e7ce7cc..a94b6bf0 100644 --- a/docs/introduction/index.rst +++ b/docs/introduction/index.rst @@ -2,7 +2,6 @@ Introduction ============ .. meta:: - :keywords: Python,DI,Dependency injection,IoC,Inversion of Control :description: Current section of documentation is designed to give some overview about dependency injection pattern, inversion of control principle and "Dependency Injector" framework. diff --git a/docs/introduction/key_features.rst b/docs/introduction/key_features.rst index a4f11038..cbec717b 100644 --- a/docs/introduction/key_features.rst +++ b/docs/introduction/key_features.rst @@ -2,7 +2,6 @@ Key features of Dependency Injector ----------------------------------- .. meta:: - :keywords: Python,DI,Dependency injection,IoC,Inversion of Control :description: This article describes key features of "Dependency Injector" framework. It also provides some cases and recommendations about usage of "Dependency Injector" framework. diff --git a/docs/introduction/structure.rst b/docs/introduction/structure.rst index 3756e352..e58c274a 100644 --- a/docs/introduction/structure.rst +++ b/docs/introduction/structure.rst @@ -2,7 +2,6 @@ Structure of Dependency Injector -------------------------------- .. meta:: - :keywords: Python,DI,Dependency injection,IoC,Inversion of Control :description: This article describes "Dependency Injector" framework components and their interaction between each other. Catalogs, providers and injections are the former diff --git a/docs/introduction/what_is_di.rst b/docs/introduction/what_is_di.rst index 05b9b169..4d464586 100644 --- a/docs/introduction/what_is_di.rst +++ b/docs/introduction/what_is_di.rst @@ -2,7 +2,6 @@ What is dependency injection and inversion of control? ------------------------------------------------------ .. meta:: - :keywords: Python,DI,Dependency injection,IoC,Inversion of Control :description: This article provides definition of dependency injection, inversion of control and dependency inversion. It contains example code in Python that is refactored to be following diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index bf723565..8422c6eb 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -11,6 +11,8 @@ Development version ------------------- - Add ``@copy`` decorator for copying declarative catalog providers. - Add line numbers for all code samples in documentation. +- Move project documentation into organisation's domain + (dependency-injector.ets-labs.org). 1.15.2 ------ diff --git a/examples/advanced_usage/config_provider.py b/examples/advanced_usage/config_provider.py index 15060b88..7794b2f4 100644 --- a/examples/advanced_usage/config_provider.py +++ b/examples/advanced_usage/config_provider.py @@ -24,7 +24,6 @@ class Catalog(catalogs.DeclarativeCatalog): fee=config.FEE, price=config.PRICE, timezone=config.GLOBAL.TIMEZONE) - """:type: providers.Provider -> ObjectA""" # Setting config value and making some tests. diff --git a/examples/api_client.py b/examples/api_client.py deleted file mode 100644 index c547b9e6..00000000 --- a/examples/api_client.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Pythonic way for Dependency Injection - API Client.""" - -from dependency_injector import providers - -from mock import Mock - - -class ApiClient(object): - """Some API client.""" - - def __init__(self, host, api_key): - """Initializer.""" - self.host = host - self.api_key = api_key - - def call(self, operation, data): - """Make some network operations.""" - print 'API call [{0}:{1}], method - {2}, data - {3}'.format( - self.host, self.api_key, operation, repr(data)) - - -class User(object): - """User model.""" - - def __init__(self, id, api_client): - """Initializer.""" - self.id = id - self.api_client = api_client - - def register(self): - """Register user.""" - self.api_client.call('register', {'id': self.id}) - - -# Creating ApiClient and User providers: -api_client = providers.Singleton(ApiClient, - host='production.com', - api_key='PROD_API_KEY') -user_factory = providers.Factory(User, - api_client=api_client) - -# Creating several users and register them: -user1 = user_factory(1) -user1.register() -# API call [production.com:PROD_API_KEY], method - register, data - {'id': 1} - -user2 = user_factory(2) -user2.register() -# API call [production.com:PROD_API_KEY], method - register, data - {'id': 2} - -# Mock ApiClient for testing: -with api_client.override(Mock(ApiClient)) as api_client_mock: - user = user_factory('test') - user.register() - api_client_mock().call.assert_called_with('register', {'id': 'test'}) - - -# Overriding of ApiClient on dev environment: -api_client.override(providers.Singleton(ApiClient, - host='localhost', - api_key='DEV_API_KEY')) - -user3 = user_factory(3) -user3.register() -# API call [localhost:DEV_API_KEY], method - register, data - {'id': 3} diff --git a/examples/catalog.py b/examples/catalog.py deleted file mode 100644 index 771e3f71..00000000 --- a/examples/catalog.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Pythonic way for Dependency Injection - Service Providers Catalog.""" - -import sqlite3 - -from dependency_injector import catalogs -from dependency_injector import providers -from dependency_injector import injections - - -class UsersService(object): - """Users service, that has dependency on database.""" - - def __init__(self, db): - """Initializer.""" - self.db = db - - -class AuthService(object): - """Auth service, that has dependencies on users service and database.""" - - def __init__(self, db, users_service): - """Initializer.""" - self.db = db - self.users_service = users_service - - -class Services(catalogs.DeclarativeCatalog): - """Catalog of service providers.""" - - database = providers.Singleton(sqlite3.connect, ':memory:') - - users = providers.Factory(UsersService, - db=database) - - auth = providers.Factory(AuthService, - db=database, - users_service=users) - - -# Retrieving catalog providers: -users_service = Services.users() -auth_service = Services.auth() - -# Making some asserts: -assert users_service.db is auth_service.db is Services.database() -assert isinstance(auth_service.users_service, UsersService) -assert users_service is not Services.users() -assert auth_service is not Services.auth() - - -# Making some "inline" injections: -@injections.inject(users_service=Services.users) -@injections.inject(auth_service=Services.auth) -@injections.inject(database=Services.database) -def example(users_service, auth_service, database): - """Example callback.""" - assert users_service.db is auth_service.db - assert auth_service.db is database - assert database is Services.database() - - -# Making a call of decorated callback: -example() diff --git a/examples/catalogs/bundles/catalogs.py b/examples/catalogs/bundles/catalogs.py index 52e16861..e5a9b397 100644 --- a/examples/catalogs/bundles/catalogs.py +++ b/examples/catalogs/bundles/catalogs.py @@ -13,13 +13,10 @@ class Services(catalogs.DeclarativeCatalog): """Example catalog of service providers.""" users = providers.Factory(services.Users) - """:type: providers.Provider -> services.Users""" auth = providers.Factory(services.Auth) - """:type: providers.Provider -> services.Auth""" photos = providers.Factory(services.Photos) - """:type: providers.Provider -> services.Photos""" # Declaring views catalog: @@ -29,12 +26,10 @@ class Views(catalogs.DeclarativeCatalog): auth = providers.Factory(views.Auth, services=Services.Bundle(Services.users, Services.auth)) - """:type: providers.Provider -> views.Auth""" photos = providers.Factory(views.Photos, services=Services.Bundle(Services.users, Services.photos)) - """:type: providers.Provider -> views.Photos""" # Creating example views: diff --git a/examples/catalogs/declarative.py b/examples/catalogs/declarative.py index f11dbe35..a483fc88 100644 --- a/examples/catalogs/declarative.py +++ b/examples/catalogs/declarative.py @@ -9,10 +9,8 @@ class Catalog(catalogs.DeclarativeCatalog): """Providers catalog.""" factory1 = providers.Factory(object) - """:type: providers.Provider -> object""" factory2 = providers.Factory(object) - """:type: providers.Provider -> object""" # Creating some objects: object1 = Catalog.factory1() diff --git a/examples/catalogs/declarative_inheritance.py b/examples/catalogs/declarative_inheritance.py index d73b71a7..e0ff494c 100644 --- a/examples/catalogs/declarative_inheritance.py +++ b/examples/catalogs/declarative_inheritance.py @@ -8,14 +8,12 @@ class CatalogA(catalogs.DeclarativeCatalog): """Example catalog A.""" provider1 = providers.Factory(object) - """:type: providers.Provider -> object""" class CatalogB(CatalogA): """Example catalog B.""" provider2 = providers.Singleton(object) - """:type: providers.Provider -> object""" # Making some asserts for `providers` attribute: diff --git a/examples/catalogs/declarative_injections.py b/examples/catalogs/declarative_injections.py index 0f277f3f..85dc8f87 100644 --- a/examples/catalogs/declarative_injections.py +++ b/examples/catalogs/declarative_injections.py @@ -27,16 +27,13 @@ class Services(catalogs.DeclarativeCatalog): """Catalog of service providers.""" database = providers.Singleton(sqlite3.connect, ':memory:') - """:type: providers.Provider -> sqlite3.Connection""" users = providers.Factory(UsersService, db=database) - """:type: providers.Provider -> UsersService""" auth = providers.Factory(AuthService, db=database, users_service=users) - """:type: providers.Provider -> AuthService""" # Retrieving service providers from catalog: diff --git a/examples/catalogs/declarative_provider_type/catalog.py b/examples/catalogs/declarative_provider_type/catalog.py index d7ed6b9c..520da6cc 100644 --- a/examples/catalogs/declarative_provider_type/catalog.py +++ b/examples/catalogs/declarative_provider_type/catalog.py @@ -31,13 +31,11 @@ class Services(services.Catalog): users = services.Provider(UsersService, config={'option1': '111', 'option2': '222'}) - """:type: services.Provider -> UsersService""" auth = services.Provider(AuthService, config={'option3': '333', 'option4': '444'}, users_service=users) - """:type: services.Provider -> AuthService""" # Creating users & auth services: diff --git a/examples/catalogs/override_declarative.py b/examples/catalogs/override_declarative.py index 825ffe91..a38777ad 100644 --- a/examples/catalogs/override_declarative.py +++ b/examples/catalogs/override_declarative.py @@ -18,18 +18,15 @@ class Catalog(catalogs.DeclarativeCatalog): object1_factory = providers.Factory(Object1, arg1=1, arg2=2) - """:type: providers.Provider -> Object1""" object2_factory = providers.Factory(Object2, object1=object1_factory) - """:type: providers.Provider -> Object2""" class AnotherCatalog(catalogs.DeclarativeCatalog): """Overriding catalog.""" object2_factory = providers.Factory(ExtendedObject2) - """:type: providers.Provider -> ExtendedObject2""" # Overriding `Catalog` with `AnotherCatalog`: diff --git a/examples/catalogs/override_declarative_by_dynamic.py b/examples/catalogs/override_declarative_by_dynamic.py index 8886a271..7246e5f4 100644 --- a/examples/catalogs/override_declarative_by_dynamic.py +++ b/examples/catalogs/override_declarative_by_dynamic.py @@ -18,11 +18,9 @@ class Catalog(catalogs.DeclarativeCatalog): object1_factory = providers.Factory(Object1, arg1=1, arg2=2) - """:type: providers.Provider -> Object1""" object2_factory = providers.Factory(Object2, object1=object1_factory) - """:type: providers.Provider -> Object2""" # Overriding `Catalog` with some `DynamicCatalog` instance: diff --git a/examples/catalogs/override_declarative_decorator.py b/examples/catalogs/override_declarative_decorator.py index 2dcbc828..cb59a752 100644 --- a/examples/catalogs/override_declarative_decorator.py +++ b/examples/catalogs/override_declarative_decorator.py @@ -17,11 +17,9 @@ class Catalog(catalogs.DeclarativeCatalog): object1_factory = providers.Factory(Object1, arg1=1, arg2=2) - """:type: providers.Provider -> Object1""" object2_factory = providers.Factory(Object2, object1=object1_factory) - """:type: providers.Provider -> Object2""" # Overriding `Catalog` with `AnotherCatalog`: @@ -30,7 +28,6 @@ class AnotherCatalog(catalogs.DeclarativeCatalog): """Overriding catalog.""" object2_factory = providers.Factory(ExtendedObject2) - """:type: providers.Provider -> ExtendedObject2""" # Creating some objects using overridden catalog: diff --git a/examples/initial.py b/examples/initial.py new file mode 100644 index 00000000..1bca84dc --- /dev/null +++ b/examples/initial.py @@ -0,0 +1,58 @@ +"""Dependency Injector initial example.""" + +import sys +import sqlite3 +import boto.s3.connection + +import services + +from dependency_injector import catalogs +from dependency_injector import providers +from dependency_injector import injections + + +class Platform(catalogs.DeclarativeCatalog): + """Catalog of platform service providers.""" + + database = providers.Singleton(sqlite3.connect, ':memory:') + + s3 = providers.Singleton(boto.s3.connection.S3Connection, + aws_access_key_id='KEY', + aws_secret_access_key='SECRET') + + +class Services(catalogs.DeclarativeCatalog): + """Catalog of business service providers.""" + + users = providers.Factory(services.Users, + db=Platform.database) + + photos = providers.Factory(services.Photos, + db=Platform.database, + s3=Platform.s3) + + auth = providers.Factory(services.Auth, + db=Platform.database, + token_ttl=3600) + + +@injections.inject(users_service=Services.users) +@injections.inject(auth_service=Services.auth) +def main(argv, users_service, auth_service): + """Main function.""" + login, password, photo_path = argv[1:] + + user = users_service.get_user(login) + auth_service.authenticate(user, password) + + upload_photo(user, photo_path) + + +@injections.inject(photos_service=Services.photos) +def upload_photo(user, photo_path, photos_service): + """Upload photo.""" + photos_service.upload_photo(user['id'], photo_path) + + +if __name__ == '__main__': + main(sys.argv) diff --git a/examples/miniapps/api_client/api.py b/examples/miniapps/api_client/api.py new file mode 100644 index 00000000..425c7fec --- /dev/null +++ b/examples/miniapps/api_client/api.py @@ -0,0 +1,15 @@ +"""asd.""" + + +class ApiClient(object): + """Some API client.""" + + def __init__(self, host, api_key): + """Initializer.""" + self.host = host + self.api_key = api_key + + def call(self, operation, data): + """Make some network operations.""" + print 'API call [{0}:{1}], method - {2}, data - {3}'.format( + self.host, self.api_key, operation, repr(data)) diff --git a/examples/miniapps/api_client/main.py b/examples/miniapps/api_client/main.py new file mode 100644 index 00000000..09cc9bf6 --- /dev/null +++ b/examples/miniapps/api_client/main.py @@ -0,0 +1,36 @@ +"""asd.""" + +from dependency_injector import providers + +import api +import models + + +# Creating ApiClient and User providers: +api_client = providers.Singleton(api.ApiClient, + host='production.com', + api_key='PROD_API_KEY') +user_factory = providers.Factory(models.User, + api_client=api_client) + + +if __name__ == '__main__': + # Creating several users and register them: + user1 = user_factory(1) + user1.register() + # API call [production.com:PROD_API_KEY], method - register, data - + # {'id': 1} + + user2 = user_factory(2) + user2.register() + # API call [production.com:PROD_API_KEY], method - register, data - + # {'id': 2} + + # Overriding of ApiClient on dev environment: + api_client.override(providers.Singleton(api.ApiClient, + host='localhost', + api_key='DEV_API_KEY')) + + user3 = user_factory(3) + user3.register() + # API call [localhost:DEV_API_KEY], method - register, data - {'id': 3} diff --git a/examples/miniapps/api_client/models.py b/examples/miniapps/api_client/models.py new file mode 100644 index 00000000..4652dfa6 --- /dev/null +++ b/examples/miniapps/api_client/models.py @@ -0,0 +1,14 @@ +"""asd.""" + + +class User(object): + """User model.""" + + def __init__(self, id, api_client): + """Initializer.""" + self.id = id + self.api_client = api_client + + def register(self): + """Register user.""" + self.api_client.call('register', {'id': self.id}) diff --git a/examples/miniapps/api_client/tests.py b/examples/miniapps/api_client/tests.py new file mode 100644 index 00000000..f19995d9 --- /dev/null +++ b/examples/miniapps/api_client/tests.py @@ -0,0 +1,12 @@ +"""asd.""" + +from mock import Mock + +import main +import api + +# Mock ApiClient for testing: +with main.api_client.override(Mock(api.ApiClient)) as api_client_mock: + user = main.user_factory('test') + user.register() + api_client_mock().call.assert_called_with('register', {'id': 'test'}) diff --git a/examples/miniapps/flask_services/app.py b/examples/miniapps/flask_services/app.py new file mode 100644 index 00000000..9737eadf --- /dev/null +++ b/examples/miniapps/flask_services/app.py @@ -0,0 +1,10 @@ +"""Flask application.""" + +import flask + + +app = flask.Flask(__name__) + + +if __name__ == '__main__': + app.run() diff --git a/examples/miniapps/flask_services/services/__init__.py b/examples/miniapps/flask_services/services/__init__.py new file mode 100644 index 00000000..3d091cc8 --- /dev/null +++ b/examples/miniapps/flask_services/services/__init__.py @@ -0,0 +1,8 @@ +"""Services package.""" + +from dependency_injector import catalogs +from dependency_injector import providers + + +class ServicesModule(catalogs.DeclarativeCatalog): + """Service providers module.""" diff --git a/examples/miniapps/movie_lister/movies/__init__.py b/examples/miniapps/movie_lister/movies/__init__.py index e7d8437f..360c3979 100644 --- a/examples/miniapps/movie_lister/movies/__init__.py +++ b/examples/miniapps/movie_lister/movies/__init__.py @@ -5,6 +5,10 @@ module component providers - ``MoviesModule``. It is recommended to use movies library functionality by fetching required instances from ``MoviesModule`` providers. +``MoviesModule.movie_finder`` is a factory that provides abstract component +``finders.MovieFinder``. This provider should be overridden by provider of +concrete finder implementation in terms of library configuration. + Each of ``MoviesModule`` providers could be overridden. """ diff --git a/examples/auth_system.py b/examples/misc/auth_system.py similarity index 100% rename from examples/auth_system.py rename to examples/misc/auth_system.py diff --git a/examples/catalog_providing_callbacks.py b/examples/misc/catalog_providing_callbacks.py similarity index 100% rename from examples/catalog_providing_callbacks.py rename to examples/misc/catalog_providing_callbacks.py diff --git a/examples/services.py b/examples/services.py new file mode 100644 index 00000000..d512e31b --- /dev/null +++ b/examples/services.py @@ -0,0 +1,40 @@ +"""Services module.""" + + +class Users(object): + """Users service.""" + + def __init__(self, db): + """Initializer.""" + self.db = db + + def get_user(self, login): + """Return user's information by login.""" + return {'id': 1, + 'login': login, + 'password_hash': 'secret_hash'} + + +class Auth(object): + """Auth service.""" + + def __init__(self, db, token_ttl): + """Initializer.""" + self.db = db + self.token_ttl = token_ttl + + def authenticate(self, user, password): + """Authenticate user.""" + assert user['password_hash'] == '_'.join((password, 'hash')) + + +class Photos(object): + """Photos service.""" + + def __init__(self, db, s3): + """Initializer.""" + self.db = db + self.s3 = s3 + + def upload_photo(self, user_id, photo_path): + """Upload user photo."""