diff --git a/.gitignore b/.gitignore index a419efbc..a1c86b0b 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,6 @@ venv/ # JointJS Experiments jointjs/ + +# Vim Rope +.ropeproject/ diff --git a/LICENSE b/LICENSE index 5442b8b7..a5d52629 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015, Roman +Copyright (c) 2015, Roman Mogilatov All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.rst b/README.rst index 138c1834..0066048a 100644 --- a/README.rst +++ b/README.rst @@ -1,27 +1,27 @@ Objects ======= -Dependency management tool for Python projects +Dependency injection framework for Python projects. +---------------------------------------+-------------------------------------------------------------------+ -| *PyPi* | .. image:: https://pypip.in/version/Objects/badge.svg | +| *PyPi* | .. image:: https://img.shields.io/pypi/v/Objects.svg | | | :target: https://pypi.python.org/pypi/Objects/ | | | :alt: Latest Version | -| | .. image:: https://pypip.in/download/Objects/badge.svg | +| | .. image:: https://img.shields.io/pypi/dm/Objects.svg | | | :target: https://pypi.python.org/pypi/Objects/ | | | :alt: Downloads | -| | .. image:: https://pypip.in/license/Objects/badge.svg | +| | .. image:: https://img.shields.io/pypi/l/Objects.svg | | | :target: https://pypi.python.org/pypi/Objects/ | | | :alt: License | +---------------------------------------+-------------------------------------------------------------------+ -| *Python versions and implementations* | .. image:: https://pypip.in/py_versions/Objects/badge.svg | +| *Python versions and implementations* | .. image:: https://img.shields.io/pypi/pyversions/Objects.svg | | | :target: https://pypi.python.org/pypi/Objects/ | | | :alt: Supported Python versions | -| | .. image:: https://pypip.in/implementation/Objects/badge.svg | +| | .. image:: https://img.shields.io/pypi/implementation/Objects.svg | | | :target: https://pypi.python.org/pypi/Objects/ | | | :alt: Supported Python implementations | +---------------------------------------+-------------------------------------------------------------------+ -| *Builds and test coverage* | .. image:: https://travis-ci.org/rmk135/objects.svg?branch=master | +| *Builds and tests coverage* | .. image:: https://travis-ci.org/rmk135/objects.svg?branch=master | | | :target: https://travis-ci.org/rmk135/objects | | | :alt: Build Status | | | .. image:: https://coveralls.io/repos/rmk135/objects/badge.svg | @@ -32,31 +32,45 @@ Dependency management tool for Python projects Introduction ------------ -Python ecosystem consists of a big amount of various classes, functions and -objects that could be used for applications development. Each of them has its -own role. +Python ecosystem consists of a big amount of various libraries that contain +different classes and functions that could be used for applications +development. Each of them has its own role. Modern Python applications are mostly the composition of well-known open -source systems, frameworks, libraries and some turnkey functionality. +source systems / frameworks / libraries and some turnkey functionality. -When application goes bigger, its amount of objects and their dependencies -also increased extremely fast and became hard to maintain. +When application goes bigger, its complexity and SLOC_ are also increased. +Being driven by SOLID_ (for example), developers often start to split +application's sources into not so big classes, functions and modules. It +always helps, but there is another problem on the horizon. -**Objects** is designed to be developer's friendly tool for managing objects -and their dependencies in formal, pretty way. Main idea of **Objects** is to -keep dependencies under control. +It sounds like "I have so many classes and functions! They are great, now I can +understand each of them, but it is so hard to see the whole picture! How are +they linked with each other? What dependencies does this class have?". And +this is a key question: "What dependencies do certain class / function have?". +To resolve this issues developers have to go inside with IoC_ principles and +implementation patterns. + +One of such IoC_ implementation patterns is called `dependency injection`_. + +*Objects* is a dependency injection framework for Python projects. + +It was designed to be developer's friendly tool for managing any kind of +Python objects and their dependencies in formal, pretty way. + +Main idea of *Objects* is to keep dependencies under control. Installation ------------ -**Objects** library is available on PyPi_:: +*Objects* library is available on PyPi_:: pip install objects Documentation ------------- -**Objects** documentation is hosted on ReadTheDocs: +*Objects* documentation is hosted on ReadTheDocs: - `Stable version`_ - `Latest version`_ @@ -70,12 +84,12 @@ Examples from objects.catalog import AbstractCatalog + from objects.providers import Factory from objects.providers import Singleton - from objects.providers import NewInstance from objects.injections import KwArg from objects.injections import Attribute - from objects.injections import inject + from objects.decorators import inject import sqlite3 @@ -108,19 +122,19 @@ Examples Attribute('row_factory', sqlite3.Row)) """:type: (objects.Provider) -> sqlite3.Connection""" - object_a = NewInstance(ObjectA, - KwArg('db', database)) + object_a_factory = Factory(ObjectA, + KwArg('db', database)) """:type: (objects.Provider) -> ObjectA""" - object_b = NewInstance(ObjectB, - KwArg('a', object_a), - KwArg('db', database)) + object_b_factory = Factory(ObjectB, + KwArg('a', object_a_factory), + KwArg('db', database)) """:type: (objects.Provider) -> ObjectB""" # Catalog static provides. - a1, a2 = Catalog.object_a(), Catalog.object_a() - b1, b2 = Catalog.object_b(), Catalog.object_b() + a1, a2 = Catalog.object_a_factory(), Catalog.object_a_factory() + b1, b2 = Catalog.object_b_factory(), Catalog.object_b_factory() assert a1 is not a2 assert b1 is not b2 @@ -128,8 +142,8 @@ Examples # Example of inline injections. - @inject(KwArg('a', Catalog.object_a)) - @inject(KwArg('b', Catalog.object_b)) + @inject(KwArg('a', Catalog.object_a_factory)) + @inject(KwArg('b', Catalog.object_b_factory)) @inject(KwArg('database', Catalog.database)) def example(a, b, database): assert a.db is b.db is database is Catalog.database() @@ -137,7 +151,8 @@ Examples example() -You can get more **Objects** examples in ``/examples`` directory on + +You can get more *Objects* examples in ``/examples`` directory on GitHub: https://github.com/rmk135/objects @@ -147,7 +162,7 @@ Feedback -------- Feel free to post questions, bugs, feature requests, proposals etc. on -**Objects** GitHub Issues: +*Objects* GitHub Issues: https://github.com/rmk135/objects/issues @@ -157,3 +172,7 @@ Your feedback is quite important! .. _PyPi: https://pypi.python.org/pypi/Objects .. _Stable version: http://objects.readthedocs.org/en/stable/ .. _Latest version: http://objects.readthedocs.org/en/latest/ +.. _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 diff --git a/VERSION b/VERSION index 8bd6ba8c..c0062185 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.5 +0.7.6 diff --git a/docs/_providers.rst b/docs/_providers.rst new file mode 100644 index 00000000..c898fd6b --- /dev/null +++ b/docs/_providers.rst @@ -0,0 +1,83 @@ +Providers +========= + + +Providers delegation +-------------------- + +Overriding of providers +----------------------- + +Any provider can be overridden by another provider. + +Example: + +.. code-block:: python + + """Providers overriding example.""" + + import sqlite3 + + from objects.providers import Factory + from objects.providers import Singleton + + from objects.injections import KwArg + from objects.injections import Attribute + + + class ObjectA(object): + + """ObjectA has dependency on database.""" + + def __init__(self, database): + """Initializer. + + Database dependency need to be injected via init arg.""" + self.database = database + + def get_one(self): + """Select one from database and return it.""" + return self.database.execute('SELECT 1') + + + class ObjectAMock(ObjectA): + + """Mock of ObjectA. + + Has no dependency on database. + """ + + def __init__(self): + """Initializer.""" + + def get_one(self): + """Select one from database and return it. + + Mock makes no database queries and always returns two instead of one. + """ + return 2 + + + # Database and `ObjectA` providers. + database = Singleton(sqlite3.Connection, + KwArg('database', ':memory:'), + KwArg('timeout', 30), + KwArg('detect_types', True), + KwArg('isolation_level', 'EXCLUSIVE'), + Attribute('row_factory', sqlite3.Row)) + + object_a_factory = Factory(ObjectA, + KwArg('database', database)) + + + # Overriding `ObjectA` provider with `ObjectAMock` provider. + object_a_factory.override(Factory(ObjectAMock)) + + # Creating several `ObjectA` instances. + object_a_1 = object_a_factory() + object_a_2 = object_a_factory() + + # Making some asserts. + assert object_a_1 is not object_a_2 + assert object_a_1.get_one() == object_a_2.get_one() == 2 + diff --git a/docs/advanced_usage.rst b/docs/advanced_usage.rst index 7161bb62..22ae9f1b 100644 --- a/docs/advanced_usage.rst +++ b/docs/advanced_usage.rst @@ -1,13 +1,13 @@ Advanced usage ============== -Below you can find some variants of advanced usage of **Objects**. +Below you can find some variants of advanced usage of *Objects*. @inject decorator ----------------- ``@inject`` decorator could be used for patching any callable with injection. -Any Python object will be injected *as is*, except **Objects** providers, +Any Python object will be injected *as is*, except *Objects* providers, that will be called to provide injectable value. .. code-block:: python diff --git a/docs/catalogs.rst b/docs/catalogs.rst new file mode 100644 index 00000000..20124eaf --- /dev/null +++ b/docs/catalogs.rst @@ -0,0 +1,2 @@ +Catalogs +======== diff --git a/docs/decorators.rst b/docs/decorators.rst new file mode 100644 index 00000000..5023260a --- /dev/null +++ b/docs/decorators.rst @@ -0,0 +1,51 @@ +Decorators +========== + +Current section of *Objects* documentation describes several useful decorators. + +@inject decorator +----------------- + +``@inject`` decorator can be used for making *inline* dependency injections. +It *patches* decorated callable in such way that dependency injection will be +done before every call of decorated callable. + +``@inject`` decorator takes only argument that is supposed to be an +``objects.injections.Injection`` instance. + +Any Python object will be injected *as is*, except *Objects* providers, +that will be called to provide injectable value. + +Below is an example of how Flask's view could be patched using ``@inject`` +decorator: + +.. code-block:: python + + """`@inject` decorator example.""" + + from objects.providers import NewInstance + + from objects.injections import KwArg + from objects.injections import inject + + + new_object = NewInstance(object) + + + @inject(KwArg('object_a', new_object)) + @inject(KwArg('some_setting', 1334)) + def example_callback(object_a, some_setting): + """This function has dependencies on object a and b. + + Dependencies are injected using `@inject` decorator. + """ + assert isinstance(object_a, object) + assert some_setting == 1334 + + + example_callback() + example_callback() + + +@override decorator +------------------- diff --git a/docs/entities.rst b/docs/entities.rst index 7bb31c88..654d0b63 100644 --- a/docs/entities.rst +++ b/docs/entities.rst @@ -1,7 +1,7 @@ Entities ======== -Current section describes main **Objects** entities and their interaction. +Current section describes main *Objects* entities and their interaction. Providers --------- @@ -45,10 +45,10 @@ dependencies of objects. Objects can take dependencies in various forms. Some objects take init arguments, other are using attributes or methods to be initialized. Injection, -in terms of **Objects**, is an instruction how to provide dependency for the +in terms of *Objects*, is an instruction how to provide dependency for the particular object. -Every Python object could be an injection's value. Special case is a **Objects** +Every Python object could be an injection's value. Special case is a *Objects* provider as an injection's value. In such case, injection value is a result of injectable provider call (every time injection is done). @@ -107,7 +107,7 @@ Catalogs Catalogs are named set of providers. -**Objects** catalogs can be used for grouping of providers by some +*Objects* catalogs can be used for grouping of providers by some kind of rules. In example below, there are two catalogs: ``Resources`` and ``Models``. diff --git a/docs/examples.rst b/docs/examples.rst index 5b2b4fa2..9cd3a619 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,7 +1,7 @@ Examples ======== -You can get more **Objects** examples in ``/examples`` directory on +You can get more *Objects* examples in ``/examples`` directory on GitHub: https://github.com/rmk135/objects diff --git a/docs/feedback.rst b/docs/feedback.rst index df25c405..b34ebda2 100644 --- a/docs/feedback.rst +++ b/docs/feedback.rst @@ -2,7 +2,7 @@ Feedback ======== Feel free to post questions, bugs, feature requests, proposals etc. on -**Objects** GitHub Issues: +*Objects* GitHub Issues: https://github.com/rmk135/objects/issues diff --git a/docs/images/callable.png b/docs/images/callable.png new file mode 100644 index 00000000..5dec3a65 Binary files /dev/null and b/docs/images/callable.png differ diff --git a/docs/images/external_dependency.png b/docs/images/external_dependency.png new file mode 100644 index 00000000..4195412e Binary files /dev/null and b/docs/images/external_dependency.png differ diff --git a/docs/images/factory.png b/docs/images/factory.png new file mode 100644 index 00000000..65c4072e Binary files /dev/null and b/docs/images/factory.png differ diff --git a/docs/images/factory_attribute_injections.png b/docs/images/factory_attribute_injections.png new file mode 100644 index 00000000..01273e8a Binary files /dev/null and b/docs/images/factory_attribute_injections.png differ diff --git a/docs/images/factory_delegation.png b/docs/images/factory_delegation.png new file mode 100644 index 00000000..3aa65279 Binary files /dev/null and b/docs/images/factory_delegation.png differ diff --git a/docs/images/factory_init_injections.png b/docs/images/factory_init_injections.png new file mode 100644 index 00000000..8a104712 Binary files /dev/null and b/docs/images/factory_init_injections.png differ diff --git a/docs/images/factory_init_injections_and_contexts.png b/docs/images/factory_init_injections_and_contexts.png new file mode 100644 index 00000000..2eb6c328 Binary files /dev/null and b/docs/images/factory_init_injections_and_contexts.png differ diff --git a/docs/images/factory_method_injections.png b/docs/images/factory_method_injections.png new file mode 100644 index 00000000..a30cf299 Binary files /dev/null and b/docs/images/factory_method_injections.png differ diff --git a/docs/images/singleton.png b/docs/images/singleton.png new file mode 100644 index 00000000..0fc8ae7f Binary files /dev/null and b/docs/images/singleton.png differ diff --git a/docs/images/singleton_internals.png b/docs/images/singleton_internals.png new file mode 100644 index 00000000..3bb05828 Binary files /dev/null and b/docs/images/singleton_internals.png differ diff --git a/docs/index.rst b/docs/index.rst index f472cb2f..b766bd5f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,27 +1,27 @@ Objects ======= -Dependency management tool for Python projects. +Dependency injection framework for Python projects. +---------------------------------------+-------------------------------------------------------------------+ -| *PyPi* | .. image:: https://pypip.in/version/Objects/badge.svg | +| *PyPi* | .. image:: https://img.shields.io/pypi/v/Objects.svg | | | :target: https://pypi.python.org/pypi/Objects/ | | | :alt: Latest Version | -| | .. image:: https://pypip.in/download/Objects/badge.svg | +| | .. image:: https://img.shields.io/pypi/dm/Objects.svg | | | :target: https://pypi.python.org/pypi/Objects/ | | | :alt: Downloads | -| | .. image:: https://pypip.in/license/Objects/badge.svg | +| | .. image:: https://img.shields.io/pypi/l/Objects.svg | | | :target: https://pypi.python.org/pypi/Objects/ | | | :alt: License | +---------------------------------------+-------------------------------------------------------------------+ -| *Python versions and implementations* | .. image:: https://pypip.in/py_versions/Objects/badge.svg | +| *Python versions and implementations* | .. image:: https://img.shields.io/pypi/pyversions/Objects.svg | | | :target: https://pypi.python.org/pypi/Objects/ | | | :alt: Supported Python versions | -| | .. image:: https://pypip.in/implementation/Objects/badge.svg | +| | .. image:: https://img.shields.io/pypi/implementation/Objects.svg | | | :target: https://pypi.python.org/pypi/Objects/ | | | :alt: Supported Python implementations | +---------------------------------------+-------------------------------------------------------------------+ -| *Builds and test coverage* | .. image:: https://travis-ci.org/rmk135/objects.svg?branch=master | +| *Builds and tests coverage* | .. image:: https://travis-ci.org/rmk135/objects.svg?branch=master | | | :target: https://travis-ci.org/rmk135/objects | | | :alt: Build Status | | | .. image:: https://coveralls.io/repos/rmk135/objects/badge.svg | @@ -38,7 +38,9 @@ Contents introduction installation - entities - advanced_usage + providers/index + injections + catalogs + decorators examples feedback diff --git a/docs/injections.rst b/docs/injections.rst new file mode 100644 index 00000000..c9104855 --- /dev/null +++ b/docs/injections.rst @@ -0,0 +1,37 @@ +Injections +========== + +Injections are *Objects* entities that are used for specification of dependency +injection types. + +Different functions, classes and objects can take dependency injections in +various forms. Some of them take dependencies like keyword arguments during +call time, other require setting of attributes or calling of specialized +methods for doing dependency injections. + +So, when you are doing dependency injection you need to specify its type and +that is the place where *Injections* need to be used. + +Some key points of *Objects* injections: + + - Every *Objects* injection always takes injectable value as an + ``injectable`` param. Every Python object could be an injectable. + - Every *Objects* injection always has ``value`` property that returns + injection's injectable. ``value`` property is calculated every time it is + accessed. Every Python object, except of *Objects* providers, that was + provided as and ``injectable`` will be returned by ``value`` property + *"as is"*. *Objects* providers will be called every time during ``value`` + accessing and result of such calls will be returned. + - Every *Objects* *Injection* can have additional params that are needed + for doing particular type of injection. + +There are several types of *Injections*: + + - ``KwArg`` - is used for making keyword argument injections for any kind + of callables (functions, methods, objects instantiation and so on). Takes + keyword argument name as string and injectable. + - ``Attribute`` - is used for making injections by setting of injection's + value to a particular attribute. Takes attribute name as string and + injectable. + - ``Method`` - is used for making injections by calling of method with + injectable value. Takes method name as string and injectable. diff --git a/docs/installation.rst b/docs/installation.rst index 046009bf..008bdaf5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,15 +1,19 @@ Installation ============ -Latest stable version of **Objects** library can be installed from PyPi_:: +Latest stable version of *Objects* framework can be installed from PyPi_: + +.. code-block:: bash pip install objects -Sources can be cloned from GitHub_:: +Sources can be cloned from GitHub_: + +.. code-block:: bash git clone https://github.com/rmk135/objects.git -All **Objects** releases can be found on GitHub: https://github.com/rmk135/objects/releases +All *Objects* releases can be found on GitHub: https://github.com/rmk135/objects/releases .. _PyPi: https://pypi.python.org/pypi/Objects .. _GitHub: https://github.com/rmk135/objects diff --git a/docs/introduction.rst b/docs/introduction.rst index a60cd478..f1428465 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -1,16 +1,36 @@ Introduction ============ -Python ecosystem consists of a big amount of various classes, functions and -objects that could be used for applications development. Each of them has its -own role. +Python ecosystem consists of a big amount of various libraries that contain +different classes and functions that could be used for applications +development. Each of them has its own role. Modern Python applications are mostly the composition of well-known open -source systems, frameworks, libraries and some turnkey functionality. +source systems / frameworks / libraries and some turnkey functionality. -When application goes bigger, its amount of objects and their dependencies -also increased extremely fast and became hard to maintain. +When application goes bigger, its complexity and SLOC_ are also increased. +Being driven by SOLID_ (for example), developers often start to split +application's sources into not so big classes, functions and modules. It +always helps, but there is another problem on the horizon. -**Objects** is designed to be developer's friendly tool for managing objects -and their dependencies in formal, pretty way. Main idea of **Objects** is to -keep dependencies under control. +It sounds like "I have so many classes and functions! They are great, now I can +understand each of them, but it is so hard to see the whole picture! How are +they linked with each other? What dependencies does this class have?". And +this is a key question: "What dependencies do certain class / function have?". +To resolve this issues developers have to go inside with IoC_ principles and +implementation patterns. + +One of such IoC_ implementation patterns is called `dependency injection`_. + +*Objects* is a dependency injection framework for Python projects. + +It was designed to be developer's friendly tool for managing any kind of +Python objects and their dependencies in formal, pretty way. + +Main idea of *Objects* is to keep dependencies under control. + + +.. _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 diff --git a/docs/providers/callable.rst b/docs/providers/callable.rst new file mode 100644 index 00000000..a2ca017e --- /dev/null +++ b/docs/providers/callable.rst @@ -0,0 +1,73 @@ +Callable providers +------------------ + +``Callable`` provider is a provider that wraps particular callable with +some injections. Every call of this provider returns result of call of initial +callable. + +Callable providers and injections +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Callable`` provider uses ``KwArg`` injections. ``KwArg`` injections are +done by passing injectable values as keyword arguments during call time. + +Context keyword arguments have higher priority than ``KwArg`` injections. + +Example: + +.. image:: /images/callable.png + :width: 100% + :align: center + +.. code-block:: python + + """`Callable` providers example.""" + + from passlib.hash import sha256_crypt + + from objects.providers import Callable + from objects.injections import KwArg + + + # Password hasher and verifier providers (hash function could be changed + # anytime (for example, to sha512) without any changes in client's code): + password_hasher = Callable(sha256_crypt.encrypt, + KwArg('salt_size', 16), + KwArg('rounds', 10000)) + password_verifier = Callable(sha256_crypt.verify) + + # Making some asserts (client's code): + hashed_password = password_hasher('super secret') + assert password_verifier('super secret', hashed_password) + +Callable providers delegation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Callable`` provider could be delegated to any other provider via any kind of +injection. Delegation of ``Callable`` providers is the same as ``Factory`` and +``Singleton`` providers delegation, please follow *Factory providers +delegation* section for example. + +``Callable`` delegate could be created obviously using +``Delegate(Callable())`` or by calling ``Callable.delegate()`` method. + +Example: + +.. code-block:: python + + """`Callable` providers delegation example.""" + + import sys + + from objects.providers import Callable + from objects.providers import Delegate + + + # Some callable provider and few delegates of it: + callable_provider = Callable(sys.exit) + callable_provider_delegate1 = callable_provider.delegate() + callable_provider_delegate2 = Delegate(callable_provider) + + # Making some asserts: + assert callable_provider_delegate1() is callable_provider + assert callable_provider_delegate2() is callable_provider diff --git a/docs/providers/extending.rst b/docs/providers/extending.rst new file mode 100644 index 00000000..fd272267 --- /dev/null +++ b/docs/providers/extending.rst @@ -0,0 +1,2 @@ +Extending of providers +---------------------- diff --git a/docs/providers/external_dependency.rst b/docs/providers/external_dependency.rst new file mode 100644 index 00000000..91fd629c --- /dev/null +++ b/docs/providers/external_dependency.rst @@ -0,0 +1,121 @@ +External dependency providers +----------------------------- + +``ExternalDependency`` provider can be useful for development of +self-sufficient libraries / modules / applications that has required external +dependencies. + +For example, you have created self-sufficient library / module / application, +that has dependency on *database connection*. + +Second step you want to do is to make this software component to be easy +reusable by wide amount of developers and to be easily integrated into many +applications. + +It may be good idea, to move all external dependencies (like +*database connection*) to the top level and make them to be injected on your +software component's initialization. It will make third party developers feel +themselves free about integration of yours component in their applications, +because they would be able to find right place / right way for doing this +in their application's architectures. + +At the same time, you can be sure, that your external dependency will be +satisfied with appropriate instance. + + +Example: + + +.. note:: + + Class ``UserService`` is a part of some library. ``UserService`` has + dependency on database connection, which can be satisfied with any + DBAPI 2.0 database connection. Being a self-sufficient library, + ``UserService`` doesn't hardcode any kind of database management logic. + Instead of this, ``UserService`` has external dependency, that has to + be satisfied by cleint's code, out of library's scope. + +.. image:: /images/external_dependency.png + +.. code-block:: python + + """`ExternalDependency` providers example.""" + + from objects.providers import ExternalDependency + from objects.providers import Factory + from objects.providers import Singleton + + from objects.injections import KwArg + from objects.injections import Attribute + + # Importing SQLITE3 and contextlib.closing for working with cursors: + import sqlite3 + from contextlib import closing + + + # Definition of example UserService: + class UserService(object): + + """Example class UserService. + + UserService has dependency on DBAPI 2.0 database connection. + """ + + def __init__(self, database): + """Initializer. + + Database dependency need to be injected via init arg. + """ + self.database = database + + def init_database(self): + """Initialize database, if it has not been initialized yet.""" + with closing(self.database.cursor()) as cursor: + cursor.execute(""" + CREATE TABLE IF NOT EXISTS users( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(32) + ) + """) + + def create(self, name): + """Create user with provided name and return his id.""" + with closing(self.database.cursor()) as cursor: + cursor.execute('INSERT INTO users(name) VALUES (?)', (name,)) + return cursor.lastrowid + + def get_by_id(self, id): + """Return user info by user id.""" + with closing(self.database.cursor()) as cursor: + cursor.execute('SELECT id, name FROM users WHERE id=?', (id,)) + return cursor.fetchone() + + + # Database and UserService providers: + database = ExternalDependency(instance_of=sqlite3.dbapi2.Connection) + users_service_factory = Factory(UserService, + KwArg('database', database)) + + # Out of library's scope. + # + # Setting database provider: + database.provided_by(Singleton(sqlite3.dbapi2.Connection, + KwArg('database', ':memory:'), + KwArg('timeout', 30), + KwArg('detect_types', True), + KwArg('isolation_level', 'EXCLUSIVE'), + Attribute('row_factory', sqlite3.Row))) + + # Creating UserService instance: + users_service = users_service_factory() + + # Initializing UserService database: + users_service.init_database() + + # Creating test user and retrieving full information about him: + test_user_id = users_service.create(name='test_user') + test_user = users_service.get_by_id(test_user_id) + + # Making some asserts: + assert test_user['id'] == 1 + assert test_user['name'] == 'test_user' diff --git a/docs/providers/factory.rst b/docs/providers/factory.rst new file mode 100644 index 00000000..d8e13cb1 --- /dev/null +++ b/docs/providers/factory.rst @@ -0,0 +1,430 @@ +Factory providers +----------------- + +``Factory`` provider creates new instance of specified class on every call. + +Nothing could be better than brief example: + +.. image:: /images/factory.png + :width: 80% + :align: center + +.. code-block:: python + + """`Factory` providers example.""" + + from objects.providers import Factory + + + class User(object): + + """Example class User.""" + + # Factory provider creates new instance of specified class on every call. + users_factory = Factory(User) + + # Creating several User objects: + user1 = users_factory() + user2 = users_factory() + + # Making some asserts: + assert user1 is not user2 + assert isinstance(user1, User) and isinstance(user2, User) + + +Factory providers and injections +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Objects can take dependencies in different forms. Some objects take init +arguments, other are using attributes setting or method calls to be +initialized. It affects how such objects need to be created and initialized, +and that is the place where ``objects.injections`` need to be used. + +``Factory`` provider takes various number of positional arguments, that define +what kind of dependency injections need to be done. + +All of those instructions are defined in ``objects.injections`` module and are +subclasses of ``objects.injections.Injection``. There are several types of +injections that are used by ``Factory`` provider: + + - ``KwArg`` - injection is done by passing injectable value in object's + ``__init__()`` method in time of object's creation via keyword argument. + Takes keyword name of ``__init__()`` argument and injectable value. + - ``Attribute`` - injection is done by setting specified attribute with + injectable value right after object's creation. Takes attribute's name + and injectable value. + - ``Method`` - injection is done by calling of specified method with + injectable value right after object's creation and attribute injections + are done. Takes method name and injectable value. + +All ``Injection``'s injectable values are provided *"as is"*, except of +providers. Providers will be called every time, when injection needs to be +done. + + +Factory providers and __init__ injections +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Example below shows how to create ``Factory`` of particular class with +``__init__`` keyword argument injections which injectable values are also +provided by another factories: + +.. image:: /images/factory_init_injections.png + +.. code-block:: python + + """`Factory` providers with init injections example.""" + + from objects.providers import Factory + from objects.injections import KwArg + + + class User(object): + + """Example class User.""" + + def __init__(self, main_photo): + """Initializer. + + :param main_photo: Photo + :return: + """ + self.main_photo = main_photo + super(User, self).__init__() + + + class Photo(object): + + """Example class Photo.""" + + # User and Photo factories: + photos_factory = Factory(Photo) + users_factory = Factory(User, + KwArg('main_photo', photos_factory)) + + # Creating several User objects: + user1 = users_factory() # Same as: user1 = User(main_photo=Photo()) + user2 = users_factory() # Same as: user2 = User(main_photo=Photo()) + + # Making some asserts: + assert isinstance(user1, User) + assert isinstance(user1.main_photo, Photo) + + assert isinstance(user2, User) + assert isinstance(user2.main_photo, Photo) + + assert user1 is not user2 + assert user1.main_photo is not user2.main_photo + +Next example shows how ``Factory`` provider deals with positional and keyword +``__init__`` context arguments. In few words, ``Factory`` provider fully +passes positional context arguments to class's ``__init__`` method, but +keyword context arguments have priority on ``KwArg`` injections (this could be +useful for testing). + +So, please, follow the example below: + +.. image:: /images/factory_init_injections_and_contexts.png + +.. code-block:: python + + """`Factory` providers with init injections and context arguments example.""" + + from objects.providers import Factory + from objects.injections import KwArg + + + class User(object): + + """Example class User. + + Class User has to be provided with user id. + + Also Class User has dependencies on class Photo and class CreditCard + objects. + + All of the dependencies have to be provided like __init__ arguments. + """ + + def __init__(self, id, main_photo, credit_card): + """Initializer. + + :param id: int + :param main_photo: Photo + :param credit_card: CreditCard + :return: + """ + self.id = id + self.main_photo = main_photo + self.credit_card = credit_card + super(User, self).__init__() + + + class Photo(object): + + """Example class Photo.""" + + + class CreditCard(object): + + """Example class CreditCard.""" + + # User, Photo and CreditCard factories: + credit_cards_factory = Factory(CreditCard) + photos_factory = Factory(Photo) + users_factory = Factory(User, + KwArg('main_photo', photos_factory), + KwArg('credit_card', credit_cards_factory)) + + # Creating several User objects: + user1 = users_factory(1) + # Same as: user1 = User(1, + # main_photo=Photo(), + # credit_card=CreditCard()) + user2 = users_factory(2) + # Same as: user2 = User(2, + # main_photo=Photo(), + # credit_card=CreditCard()) + + # Making some asserts: + assert user1.id == 1 + assert isinstance(user1.main_photo, Photo) + assert isinstance(user1.credit_card, CreditCard) + + assert user2.id == 2 + assert isinstance(user2.main_photo, Photo) + assert isinstance(user2.credit_card, CreditCard) + + assert user1.main_photo is not user2.main_photo + assert user1.credit_card is not user2.credit_card + + # Context keyword arguments have priority on KwArg injections priority: + main_photo_mock = Photo() + credit_card_mock = CreditCard() + + user3 = users_factory(3, main_photo=main_photo_mock, + credit_card=credit_card_mock) + + assert user3.id == 3 + assert user3.main_photo is main_photo_mock + assert user3.credit_card is credit_card_mock + +Factory providers and attribute injections +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Example below shows how to create ``Factory`` of particular class with +attribute injections. Those injections are done by setting specified attributes +with injectable values right after object's creation. + +Example: + +.. image:: /images/factory_attribute_injections.png + +.. code-block:: python + + """`Factory` providers with attribute injections example.""" + + from objects.providers import Factory + from objects.injections import Attribute + + + class User(object): + + """Example class User.""" + + def __init__(self): + """Initializer.""" + self.main_photo = None + self.credit_card = None + + + class Photo(object): + + """Example class Photo.""" + + + class CreditCard(object): + + """Example class CreditCard.""" + + # User, Photo and CreditCard factories: + credit_cards_factory = Factory(CreditCard) + photos_factory = Factory(Photo) + users_factory = Factory(User, + Attribute('main_photo', photos_factory), + Attribute('credit_card', credit_cards_factory)) + + # Creating several User objects: + user1 = users_factory() + # Same as: user1 = User() + # user1.main_photo = Photo() + # user1.credit_card = CreditCard() + user2 = users_factory() + # Same as: user2 = User() + # user2.main_photo = Photo() + # user2.credit_card = CreditCard() + + # Making some asserts: + assert user1 is not user2 + + assert isinstance(user1.main_photo, Photo) + assert isinstance(user1.credit_card, CreditCard) + + assert isinstance(user2.main_photo, Photo) + assert isinstance(user2.credit_card, CreditCard) + + assert user1.main_photo is not user2.main_photo + assert user1.credit_card is not user2.credit_card + +Factory providers and method injections +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Current example shows how to create ``Factory`` of particular class with +method injections. Those injections are done by calling of specified method +with injectable value right after object's creation and attribute injections +are done. + +Method injections are not very popular in Python due Python best practices +(usage of public attributes instead of setter methods), but it may appear in +some cases. + +Example: + +.. image:: /images/factory_method_injections.png + +.. code-block:: python + + """`Factory` providers with method injections example.""" + + from objects.providers import Factory + from objects.injections import Method + + + class User(object): + + """Example class User.""" + + def __init__(self): + """Initializer.""" + self.main_photo = None + self.credit_card = None + + def set_main_photo(self, photo): + """Set user's main photo.""" + self.main_photo = photo + + def set_credit_card(self, credit_card): + """Set user's credit card.""" + self.credit_card = credit_card + + + class Photo(object): + + """Example class Photo.""" + + + class CreditCard(object): + + """Example class CreditCard.""" + + # User, Photo and CreditCard factories: + credit_cards_factory = Factory(CreditCard) + photos_factory = Factory(Photo) + users_factory = Factory(User, + Method('set_main_photo', photos_factory), + Method('set_credit_card', credit_cards_factory)) + + # Creating several User objects: + user1 = users_factory() + # Same as: user1 = User() + # user1.set_main_photo(Photo()) + # user1.set_credit_card(CreditCard()) + user2 = users_factory() + # Same as: user2 = User() + # user2.set_main_photo(Photo()) + # user2.set_credit_card(CreditCard()) + + # Making some asserts: + assert user1 is not user2 + + assert isinstance(user1.main_photo, Photo) + assert isinstance(user1.credit_card, CreditCard) + + assert isinstance(user2.main_photo, Photo) + assert isinstance(user2.credit_card, CreditCard) + + assert user1.main_photo is not user2.main_photo + assert user1.credit_card is not user2.credit_card + +Factory providers delegation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Factory`` provider could be delegated to any other provider via any kind of +injection. Saying in other words, delegation of factories - is a way to inject +factories themselves, instead of results of their calls. + +As it was mentioned earlier, ``Injection`` calls ``Factory`` if ``Factory`` is +injectable value. ``Factory`` delegation is performed by wrapping delegated +``Factory`` into special provider type - ``Delegate``, that just returns +``Factory`` itself. + +Another one, more *convenient*, method of creating ``Delegate`` for ``Factory`` +is just calling ``Factory.delegate()`` method that returns delegate for current +factory. + +Example: + +.. image:: /images/factory_delegation.png + +.. code-block:: python + + """`Factory` providers delegation example.""" + + from objects.providers import Factory + from objects.injections import KwArg + + + class User(object): + + """Example class User.""" + + def __init__(self, photos_factory): + """Initializer. + + :param photos_factory: objects.providers.Factory + :return: + """ + self.photos_factory = photos_factory + self._main_photo = None + super(User, self).__init__() + + @property + def main_photo(self): + """Return user's main photo.""" + if not self._main_photo: + self._main_photo = self.photos_factory() + return self._main_photo + + + class Photo(object): + + """Example class Photo.""" + + # User and Photo factories: + photos_factory = Factory(Photo) + users_factory = Factory(User, + KwArg('photos_factory', photos_factory.delegate())) + + # Creating several User objects: + user1 = users_factory() + user2 = users_factory() + + # Making some asserts: + assert isinstance(user1, User) + assert isinstance(user1.main_photo, Photo) + + assert isinstance(user2, User) + assert isinstance(user2.main_photo, Photo) + + assert user1 is not user2 + assert user1.main_photo is not user2.main_photo diff --git a/docs/providers/index.rst b/docs/providers/index.rst new file mode 100644 index 00000000..2909d655 --- /dev/null +++ b/docs/providers/index.rst @@ -0,0 +1,17 @@ +Providers +========= + +Providers are strategies of accessing objects. + +All providers are callable. They describe how particular objects are provided. + +.. toctree:: + :maxdepth: 2 + + factory + singleton + static + callable + external_dependency + overriding + extending diff --git a/docs/providers/overriding.rst b/docs/providers/overriding.rst new file mode 100644 index 00000000..e100fd0f --- /dev/null +++ b/docs/providers/overriding.rst @@ -0,0 +1,4 @@ +Overriding of providers +----------------------- + +Every provider could be overridden by another provider. diff --git a/docs/providers/singleton.rst b/docs/providers/singleton.rst new file mode 100644 index 00000000..4da14692 --- /dev/null +++ b/docs/providers/singleton.rst @@ -0,0 +1,134 @@ +Singleton providers +------------------- + +``Singleton`` provider creates new instance of specified class on first call +and returns same instance on every next call. + +Example: + +.. image:: /images/singleton.png + :width: 80% + :align: center + +.. code-block:: python + + """`Singleton` providers example.""" + + from objects.providers import Singleton + + + class UserService(object): + + """Example class UserService.""" + + # Singleton provider creates new instance of specified class on first call and + # returns same instance on every next call. + users_service_provider = Singleton(UserService) + + # Retrieving several UserService objects: + user_service1 = users_service_provider() + user_service2 = users_service_provider() + + # Making some asserts: + assert user_service1 is user_service2 + assert isinstance(user_service1, UserService) + assert isinstance(user_service2, UserService) + +Singleton providers and injections +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Singleton`` providers use ``Factory`` providers for first creation of +specified class instance, so, all of the rules about injections are the same, +as for ``Factory`` providers. + +.. image:: /images/singleton_internals.png + :width: 80% + :align: center + +.. note:: + + Due that ``Singleton`` provider creates specified class instance only on + the first call, all injections are done once, during the first call, also. + Every next call, while instance has been already created and memorized, no + injections are done, ``Singleton`` provider just returns memorized earlier + instance. + + This may cause some problems, for example, in case of trying to bind + ``Factory`` provider with ``Singleton`` provider (provided by dependent + ``Factory`` instance will be injected only once, during the first call). + Be aware that such behaviour was made with opened eyes and is not a bug. + + By the way, in such case, ``Delegate`` provider can be useful. It makes + possible to inject providers *as is*. Please check out full example in + *Providers delegation* section. + +Singleton providers resetting +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Created and memorized by ``Singleton`` instance can be reset. Reset of +``Singleton``'s memorized instance is done by clearing reference to it. Further +lifecycle of memorized instance is out of ``Singleton`` provider's control. + +Example: + +.. code-block:: python + + """`Singleton` providers resetting example.""" + + from objects.providers import Singleton + + + class UserService(object): + + """Example class UserService.""" + + # Users service singleton provider: + users_service_provider = Singleton(UserService) + + # Retrieving several UserService objects: + user_service1 = users_service_provider() + user_service2 = users_service_provider() + + # Making some asserts: + assert user_service1 is user_service2 + assert isinstance(user_service1, UserService) + assert isinstance(user_service2, UserService) + + # Resetting of memorized instance: + users_service_provider.reset() + + # Retrieving one more UserService object: + user_service3 = users_service_provider() + + # Making some asserts: + assert user_service3 is not user_service1 + +Singleton providers delegation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Singleton`` provider could be delegated to any other provider via any kind of +injection. Delegation of ``Singleton`` providers is the same as ``Factory`` +providers delegation, please follow *Factory providers delegation* section for +example. + +``Singleton`` delegate could be created obviously using +``Delegate(Singleton())`` or by calling ``Singleton.delegate()`` method. + +Example: + +.. code-block:: python + + """`Singleton` providers delegation example.""" + + from objects.providers import Singleton + from objects.providers import Delegate + + + # Some singleton provider and few delegates of it: + singleton_provider = Singleton(object) + singleton_provider_delegate1 = singleton_provider.delegate() + singleton_provider_delegate2 = Delegate(singleton_provider) + + # Making some asserts: + assert singleton_provider_delegate1() is singleton_provider + assert singleton_provider_delegate2() is singleton_provider diff --git a/docs/providers/static.rst b/docs/providers/static.rst new file mode 100644 index 00000000..c9e2d5df --- /dev/null +++ b/docs/providers/static.rst @@ -0,0 +1,42 @@ +Static providers +---------------- + +Static providers are family of providers that return their values "as is". +There are four types of static providers: + + - ``Class`` + - ``Object`` + - ``Function`` + - ``Value`` + +All of them have the same behaviour, but usage of anyone is predicted by +readability and providing object's type. + +Example: + +.. code-block:: python + + """`Static` providers example.""" + + from objects.providers import Class + from objects.providers import Object + from objects.providers import Function + from objects.providers import Value + + + # Provides class - `object`: + cls_provider = Class(object) + assert cls_provider() is object + + # Provides object - `object()`: + object_provider = Object(object()) + assert isinstance(object_provider(), object) + + # Provides function - `len`: + function_provider = Function(len) + assert function_provider() is len + + # Provides value - `123`: + value_provider = Value(123) + assert value_provider() == 123 + diff --git a/examples/callable_provider.py b/examples/callable_provider.py deleted file mode 100644 index feef362c..00000000 --- a/examples/callable_provider.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Callable provider examples.""" - -from objects.catalog import AbstractCatalog - -from objects.providers import Singleton -from objects.providers import Callable - -from objects.injections import KwArg -from objects.injections import Attribute - -import sqlite3 - - -def consuming_function(arg, db): - """Example function that has input arg and dependency on database.""" - return arg, db - - -class Catalog(AbstractCatalog): - - """Catalog of objects providers.""" - - database = Singleton(sqlite3.Connection, - KwArg('database', ':memory:'), - Attribute('row_factory', sqlite3.Row)) - """:type: (objects.Provider) -> sqlite3.Connection""" - - consuming_function = Callable(consuming_function, - KwArg('db', database)) - """:type: (objects.Provider) -> consuming_function""" - - -# Some calls. -arg1, db1 = Catalog.consuming_function(1) -arg2, db2 = Catalog.consuming_function(2) -arg3, db3 = Catalog.consuming_function(3) - -# Some asserts. -assert db1 is db2 is db3 -assert arg1 == 1 -assert arg2 == 2 -assert arg3 == 3 diff --git a/examples/concept.py b/examples/concept.py index 6d934b48..3a7b1feb 100644 --- a/examples/concept.py +++ b/examples/concept.py @@ -2,12 +2,12 @@ from objects.catalog import AbstractCatalog +from objects.providers import Factory from objects.providers import Singleton -from objects.providers import NewInstance from objects.injections import KwArg from objects.injections import Attribute -from objects.injections import inject +from objects.decorators import inject import sqlite3 @@ -40,19 +40,19 @@ class Catalog(AbstractCatalog): Attribute('row_factory', sqlite3.Row)) """:type: (objects.Provider) -> sqlite3.Connection""" - object_a = NewInstance(ObjectA, - KwArg('db', database)) + object_a_factory = Factory(ObjectA, + KwArg('db', database)) """:type: (objects.Provider) -> ObjectA""" - object_b = NewInstance(ObjectB, - KwArg('a', object_a), - KwArg('db', database)) + object_b_factory = Factory(ObjectB, + KwArg('a', object_a_factory), + KwArg('db', database)) """:type: (objects.Provider) -> ObjectB""" # Catalog static provides. -a1, a2 = Catalog.object_a(), Catalog.object_a() -b1, b2 = Catalog.object_b(), Catalog.object_b() +a1, a2 = Catalog.object_a_factory(), Catalog.object_a_factory() +b1, b2 = Catalog.object_b_factory(), Catalog.object_b_factory() assert a1 is not a2 assert b1 is not b2 @@ -60,8 +60,8 @@ assert a1.db is a2.db is b1.db is b2.db is Catalog.database() # Example of inline injections. -@inject(KwArg('a', Catalog.object_a)) -@inject(KwArg('b', Catalog.object_b)) +@inject(KwArg('a', Catalog.object_a_factory)) +@inject(KwArg('b', Catalog.object_b_factory)) @inject(KwArg('database', Catalog.database)) def example(a, b, database): assert a.db is b.db is database is Catalog.database() diff --git a/examples/delegate.py b/examples/delegate.py deleted file mode 100644 index df4a9068..00000000 --- a/examples/delegate.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Provider delegation example.""" - -from objects.catalog import AbstractCatalog - -from objects.providers import Singleton -from objects.providers import NewInstance - -from objects.injections import KwArg -from objects.injections import Attribute - -import sqlite3 - - -class ObjectA(object): - - """Example class ObjectA, that has dependency on database.""" - - def __init__(self, db): - """Initializer.""" - self.db = db - - -class ObjectB(object): - - """Example class ObjectB, that has dependency on ObjectA provider.""" - - def __init__(self, a_provider): - """Initializer.""" - self.a_provider = a_provider - - -class Catalog(AbstractCatalog): - - """Catalog of objects providers.""" - - database = Singleton(sqlite3.Connection, - KwArg('database', ':memory:'), - Attribute('row_factory', sqlite3.Row)) - """:type: (objects.Provider) -> sqlite3.Connection""" - - object_a = NewInstance(ObjectA, - KwArg('db', database)) - """:type: (objects.Provider) -> ObjectA""" - - object_b = Singleton(ObjectB, - KwArg('a_provider', object_a.delegate())) - """:type: (objects.Provider) -> ObjectB""" - - -# Catalog static provides. -b = Catalog.object_b() -a1, a2 = b.a_provider(), b.a_provider() - -# Some asserts. -assert a1 is not a2 -assert a1.db is a2.db is Catalog.database() diff --git a/examples/external_dependency.py b/examples/external_dependency.py deleted file mode 100644 index 5c921f0f..00000000 --- a/examples/external_dependency.py +++ /dev/null @@ -1,63 +0,0 @@ -"""External dependency example.""" - -from objects.catalog import AbstractCatalog - -from objects.providers import Singleton -from objects.providers import NewInstance -from objects.providers import ExternalDependency - -from objects.injections import KwArg -from objects.injections import Attribute - -import sqlite3 - - -class ObjectA(object): - - """Example class ObjectA, that has dependency on database.""" - - def __init__(self, db): - """Initializer.""" - self.db = db - - -class ObjectB(object): - - """Example class ObjectB, that has dependencies on ObjectA and database.""" - - def __init__(self, a, db): - """Initializer.""" - self.a = a - self.db = db - - -class Catalog(AbstractCatalog): - - """Catalog of objects providers.""" - - database = ExternalDependency(instance_of=sqlite3.Connection) - """:type: (objects.Provider) -> sqlite3.Connection""" - - object_a = NewInstance(ObjectA, - KwArg('db', database)) - """:type: (objects.Provider) -> ObjectA""" - - object_b = NewInstance(ObjectB, - KwArg('a', object_a), - KwArg('db', database)) - """:type: (objects.Provider) -> ObjectB""" - - -# Satisfaction of external dependency. -Catalog.database.override(Singleton(sqlite3.Connection, - KwArg('database', ':memory:'), - Attribute('row_factory', sqlite3.Row))) - -# Catalog static provides. -a1, a2 = Catalog.object_a(), Catalog.object_a() -b1, b2 = Catalog.object_b(), Catalog.object_b() - -# Some asserts. -assert a1 is not a2 -assert b1 is not b2 -assert a1.db is a2.db is b1.db is b2.db is Catalog.database() diff --git a/examples/override.py b/examples/override.py index 6cffbab8..98f1c91b 100644 --- a/examples/override.py +++ b/examples/override.py @@ -1,7 +1,6 @@ """Override example.""" from objects.catalog import AbstractCatalog -from objects.catalog import override from objects.providers import Singleton from objects.providers import NewInstance @@ -9,6 +8,8 @@ from objects.providers import NewInstance from objects.injections import KwArg from objects.injections import Attribute +from objects.decorators import override + import sqlite3 diff --git a/examples/providers/callable_delegation.py b/examples/providers/callable_delegation.py new file mode 100644 index 00000000..d8569afe --- /dev/null +++ b/examples/providers/callable_delegation.py @@ -0,0 +1,16 @@ +"""`Callable` providers delegation example.""" + +import sys + +from objects.providers import Callable +from objects.providers import Delegate + + +# Some callable provider and few delegates of it: +callable_provider = Callable(sys.exit) +callable_provider_delegate1 = callable_provider.delegate() +callable_provider_delegate2 = Delegate(callable_provider) + +# Making some asserts: +assert callable_provider_delegate1() is callable_provider +assert callable_provider_delegate2() is callable_provider diff --git a/examples/providers/callable_injections.py b/examples/providers/callable_injections.py new file mode 100644 index 00000000..a85023dc --- /dev/null +++ b/examples/providers/callable_injections.py @@ -0,0 +1,18 @@ +"""`Callable` providers example.""" + +from passlib.hash import sha256_crypt + +from objects.providers import Callable +from objects.injections import KwArg + + +# Password hasher and verifier providers (hash function could be changed +# anytime (for example, to sha512) without any changes in client's code): +password_hasher = Callable(sha256_crypt.encrypt, + KwArg('salt_size', 16), + KwArg('rounds', 10000)) +password_verifier = Callable(sha256_crypt.verify) + +# Making some asserts (client's code): +hashed_password = password_hasher('super secret') +assert password_verifier('super secret', hashed_password) diff --git a/examples/providers/external_dependency.py b/examples/providers/external_dependency.py new file mode 100644 index 00000000..796be0e3 --- /dev/null +++ b/examples/providers/external_dependency.py @@ -0,0 +1,80 @@ +"""`ExternalDependency` providers example.""" + +from objects.providers import ExternalDependency +from objects.providers import Factory +from objects.providers import Singleton + +from objects.injections import KwArg +from objects.injections import Attribute + +# Importing SQLITE3 and contextlib.closing for working with cursors: +import sqlite3 +from contextlib import closing + + +# Definition of example UserService: +class UserService(object): + + """Example class UserService. + + UserService has dependency on DBAPI 2.0 database connection. + """ + + def __init__(self, database): + """Initializer. + + Database dependency need to be injected via init arg. + """ + self.database = database + + def init_database(self): + """Initialize database, if it has not been initialized yet.""" + with closing(self.database.cursor()) as cursor: + cursor.execute(""" + CREATE TABLE IF NOT EXISTS users( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(32) + ) + """) + + def create(self, name): + """Create user with provided name and return his id.""" + with closing(self.database.cursor()) as cursor: + cursor.execute('INSERT INTO users(name) VALUES (?)', (name,)) + return cursor.lastrowid + + def get_by_id(self, id): + """Return user info by user id.""" + with closing(self.database.cursor()) as cursor: + cursor.execute('SELECT id, name FROM users WHERE id=?', (id,)) + return cursor.fetchone() + + +# Database and UserService providers: +database = ExternalDependency(instance_of=sqlite3.dbapi2.Connection) +users_service_factory = Factory(UserService, + KwArg('database', database)) + +# Out of library's scope. +# +# Setting database provider: +database.provided_by(Singleton(sqlite3.dbapi2.Connection, + KwArg('database', ':memory:'), + KwArg('timeout', 30), + KwArg('detect_types', True), + KwArg('isolation_level', 'EXCLUSIVE'), + Attribute('row_factory', sqlite3.Row))) + +# Creating UserService instance: +users_service = users_service_factory() + +# Initializing UserService database: +users_service.init_database() + +# Creating test user and retrieving full information about him: +test_user_id = users_service.create(name='test_user') +test_user = users_service.get_by_id(test_user_id) + +# Making some asserts: +assert test_user['id'] == 1 +assert test_user['name'] == 'test_user' diff --git a/examples/providers/factory.py b/examples/providers/factory.py new file mode 100644 index 00000000..f1a48df2 --- /dev/null +++ b/examples/providers/factory.py @@ -0,0 +1,19 @@ +"""`Factory` providers example.""" + +from objects.providers import Factory + + +class User(object): + + """Example class User.""" + +# Factory provider creates new instance of specified class on every call. +users_factory = Factory(User) + +# Creating several User objects: +user1 = users_factory() +user2 = users_factory() + +# Making some asserts: +assert user1 is not user2 +assert isinstance(user1, User) and isinstance(user2, User) diff --git a/examples/providers/factory_attribute_injections.py b/examples/providers/factory_attribute_injections.py new file mode 100644 index 00000000..f28c179e --- /dev/null +++ b/examples/providers/factory_attribute_injections.py @@ -0,0 +1,53 @@ +"""`Factory` providers with attribute injections example.""" + +from objects.providers import Factory +from objects.injections import Attribute + + +class User(object): + + """Example class User.""" + + def __init__(self): + """Initializer.""" + self.main_photo = None + self.credit_card = None + + +class Photo(object): + + """Example class Photo.""" + + +class CreditCard(object): + + """Example class CreditCard.""" + +# User, Photo and CreditCard factories: +credit_cards_factory = Factory(CreditCard) +photos_factory = Factory(Photo) +users_factory = Factory(User, + Attribute('main_photo', photos_factory), + Attribute('credit_card', credit_cards_factory)) + +# Creating several User objects: +user1 = users_factory() +# Same as: user1 = User() +# user1.main_photo = Photo() +# user1.credit_card = CreditCard() +user2 = users_factory() +# Same as: user2 = User() +# user2.main_photo = Photo() +# user2.credit_card = CreditCard() + +# Making some asserts: +assert user1 is not user2 + +assert isinstance(user1.main_photo, Photo) +assert isinstance(user1.credit_card, CreditCard) + +assert isinstance(user2.main_photo, Photo) +assert isinstance(user2.credit_card, CreditCard) + +assert user1.main_photo is not user2.main_photo +assert user1.credit_card is not user2.credit_card diff --git a/examples/providers/factory_delegation.py b/examples/providers/factory_delegation.py new file mode 100644 index 00000000..d9fc33e0 --- /dev/null +++ b/examples/providers/factory_delegation.py @@ -0,0 +1,50 @@ +"""`Factory` providers delegation example.""" + +from objects.providers import Factory +from objects.injections import KwArg + + +class User(object): + + """Example class User.""" + + def __init__(self, photos_factory): + """Initializer. + + :param photos_factory: objects.providers.Factory + :return: + """ + self.photos_factory = photos_factory + self._main_photo = None + super(User, self).__init__() + + @property + def main_photo(self): + """Return user's main photo.""" + if not self._main_photo: + self._main_photo = self.photos_factory() + return self._main_photo + + +class Photo(object): + + """Example class Photo.""" + +# User and Photo factories: +photos_factory = Factory(Photo) +users_factory = Factory(User, + KwArg('photos_factory', photos_factory.delegate())) + +# Creating several User objects: +user1 = users_factory() +user2 = users_factory() + +# Making some asserts: +assert isinstance(user1, User) +assert isinstance(user1.main_photo, Photo) + +assert isinstance(user2, User) +assert isinstance(user2.main_photo, Photo) + +assert user1 is not user2 +assert user1.main_photo is not user2.main_photo diff --git a/examples/providers/factory_init_injections.py b/examples/providers/factory_init_injections.py new file mode 100644 index 00000000..18755eb9 --- /dev/null +++ b/examples/providers/factory_init_injections.py @@ -0,0 +1,42 @@ +"""`Factory` providers with init injections example.""" + +from objects.providers import Factory +from objects.injections import KwArg + + +class User(object): + + """Example class User.""" + + def __init__(self, main_photo): + """Initializer. + + :param main_photo: Photo + :return: + """ + self.main_photo = main_photo + super(User, self).__init__() + + +class Photo(object): + + """Example class Photo.""" + +# User and Photo factories: +photos_factory = Factory(Photo) +users_factory = Factory(User, + KwArg('main_photo', photos_factory)) + +# Creating several User objects: +user1 = users_factory() # Same as: user1 = User(main_photo=Photo()) +user2 = users_factory() # Same as: user2 = User(main_photo=Photo()) + +# Making some asserts: +assert isinstance(user1, User) +assert isinstance(user1.main_photo, Photo) + +assert isinstance(user2, User) +assert isinstance(user2.main_photo, Photo) + +assert user1 is not user2 +assert user1.main_photo is not user2.main_photo diff --git a/examples/providers/factory_init_injections_and_contexts.py b/examples/providers/factory_init_injections_and_contexts.py new file mode 100644 index 00000000..dcc39f35 --- /dev/null +++ b/examples/providers/factory_init_injections_and_contexts.py @@ -0,0 +1,80 @@ +"""`Factory` providers with init injections and context arguments example.""" + +from objects.providers import Factory +from objects.injections import KwArg + + +class User(object): + + """Example class User. + + Class User has to be provided with user id. + + Also Class User has dependencies on class Photo and class CreditCard + objects. + + All of the dependencies have to be provided like __init__ arguments. + """ + + def __init__(self, id, main_photo, credit_card): + """Initializer. + + :param id: int + :param main_photo: Photo + :param credit_card: CreditCard + :return: + """ + self.id = id + self.main_photo = main_photo + self.credit_card = credit_card + super(User, self).__init__() + + +class Photo(object): + + """Example class Photo.""" + + +class CreditCard(object): + + """Example class CreditCard.""" + +# User, Photo and CreditCard factories: +credit_cards_factory = Factory(CreditCard) +photos_factory = Factory(Photo) +users_factory = Factory(User, + KwArg('main_photo', photos_factory), + KwArg('credit_card', credit_cards_factory)) + +# Creating several User objects: +user1 = users_factory(1) +# Same as: user1 = User(1, +# main_photo=Photo(), +# credit_card=CreditCard()) +user2 = users_factory(2) +# Same as: user2 = User(2, +# main_photo=Photo(), +# credit_card=CreditCard()) + +# Making some asserts: +assert user1.id == 1 +assert isinstance(user1.main_photo, Photo) +assert isinstance(user1.credit_card, CreditCard) + +assert user2.id == 2 +assert isinstance(user2.main_photo, Photo) +assert isinstance(user2.credit_card, CreditCard) + +assert user1.main_photo is not user2.main_photo +assert user1.credit_card is not user2.credit_card + +# Context keyword arguments have priority on KwArg injections priority: +main_photo_mock = Photo() +credit_card_mock = CreditCard() + +user3 = users_factory(3, main_photo=main_photo_mock, + credit_card=credit_card_mock) + +assert user3.id == 3 +assert user3.main_photo is main_photo_mock +assert user3.credit_card is credit_card_mock diff --git a/examples/providers/factory_method_injections.py b/examples/providers/factory_method_injections.py new file mode 100644 index 00000000..67acc041 --- /dev/null +++ b/examples/providers/factory_method_injections.py @@ -0,0 +1,61 @@ +"""`Factory` providers with method injections example.""" + +from objects.providers import Factory +from objects.injections import Method + + +class User(object): + + """Example class User.""" + + def __init__(self): + """Initializer.""" + self.main_photo = None + self.credit_card = None + + def set_main_photo(self, photo): + """Set user's main photo.""" + self.main_photo = photo + + def set_credit_card(self, credit_card): + """Set user's credit card.""" + self.credit_card = credit_card + + +class Photo(object): + + """Example class Photo.""" + + +class CreditCard(object): + + """Example class CreditCard.""" + +# User, Photo and CreditCard factories: +credit_cards_factory = Factory(CreditCard) +photos_factory = Factory(Photo) +users_factory = Factory(User, + Method('set_main_photo', photos_factory), + Method('set_credit_card', credit_cards_factory)) + +# Creating several User objects: +user1 = users_factory() +# Same as: user1 = User() +# user1.set_main_photo(Photo()) +# user1.set_credit_card(CreditCard()) +user2 = users_factory() +# Same as: user2 = User() +# user2.set_main_photo(Photo()) +# user2.set_credit_card(CreditCard()) + +# Making some asserts: +assert user1 is not user2 + +assert isinstance(user1.main_photo, Photo) +assert isinstance(user1.credit_card, CreditCard) + +assert isinstance(user2.main_photo, Photo) +assert isinstance(user2.credit_card, CreditCard) + +assert user1.main_photo is not user2.main_photo +assert user1.credit_card is not user2.credit_card diff --git a/examples/providers/singleton.py b/examples/providers/singleton.py new file mode 100644 index 00000000..92caca00 --- /dev/null +++ b/examples/providers/singleton.py @@ -0,0 +1,21 @@ +"""`Singleton` providers example.""" + +from objects.providers import Singleton + + +class UserService(object): + + """Example class UserService.""" + +# Singleton provider creates new instance of specified class on first call and +# returns same instance on every next call. +users_service_provider = Singleton(UserService) + +# Retrieving several UserService objects: +user_service1 = users_service_provider() +user_service2 = users_service_provider() + +# Making some asserts: +assert user_service1 is user_service2 +assert isinstance(user_service1, UserService) +assert isinstance(user_service2, UserService) diff --git a/examples/providers/singleton_delegation.py b/examples/providers/singleton_delegation.py new file mode 100644 index 00000000..9ca2fce9 --- /dev/null +++ b/examples/providers/singleton_delegation.py @@ -0,0 +1,14 @@ +"""`Singleton` providers delegation example.""" + +from objects.providers import Singleton +from objects.providers import Delegate + + +# Some singleton provider and few delegates of it: +singleton_provider = Singleton(object) +singleton_provider_delegate1 = singleton_provider.delegate() +singleton_provider_delegate2 = Delegate(singleton_provider) + +# Making some asserts: +assert singleton_provider_delegate1() is singleton_provider +assert singleton_provider_delegate2() is singleton_provider diff --git a/examples/providers/singleton_reseting.py b/examples/providers/singleton_reseting.py new file mode 100644 index 00000000..be5780ef --- /dev/null +++ b/examples/providers/singleton_reseting.py @@ -0,0 +1,29 @@ +"""`Singleton` providers resetting example.""" + +from objects.providers import Singleton + + +class UserService(object): + + """Example class UserService.""" + +# Users service singleton provider: +users_service_provider = Singleton(UserService) + +# Retrieving several UserService objects: +user_service1 = users_service_provider() +user_service2 = users_service_provider() + +# Making some asserts: +assert user_service1 is user_service2 +assert isinstance(user_service1, UserService) +assert isinstance(user_service2, UserService) + +# Resetting of memorized instance: +users_service_provider.reset() + +# Retrieving one more UserService object: +user_service3 = users_service_provider() + +# Making some asserts: +assert user_service3 is not user_service1 diff --git a/examples/providers/static.py b/examples/providers/static.py new file mode 100644 index 00000000..941756ed --- /dev/null +++ b/examples/providers/static.py @@ -0,0 +1,23 @@ +"""`Static` providers example.""" + +from objects.providers import Class +from objects.providers import Object +from objects.providers import Function +from objects.providers import Value + + +# Provides class - `object`: +cls_provider = Class(object) +assert cls_provider() is object + +# Provides object - `object()`: +object_provider = Object(object()) +assert isinstance(object_provider(), object) + +# Provides function - `len`: +function_provider = Function(len) +assert function_provider() is len + +# Provides value - `123`: +value_provider = Value(123) +assert value_provider() == 123 diff --git a/examples/readme/inject_decorator.py b/examples/readme/inject_decorator.py index 702f2727..6bfd3c7f 100644 --- a/examples/readme/inject_decorator.py +++ b/examples/readme/inject_decorator.py @@ -1,9 +1,8 @@ """`@inject` decorator example.""" from objects.providers import NewInstance - from objects.injections import KwArg -from objects.injections import inject +from objects.decorators import inject new_object = NewInstance(object) diff --git a/examples/readme/overriding_catalog.py b/examples/readme/overriding_catalog.py index 657def1a..d05e4ac0 100644 --- a/examples/readme/overriding_catalog.py +++ b/examples/readme/overriding_catalog.py @@ -3,7 +3,6 @@ import sqlite3 from objects.catalog import AbstractCatalog -from objects.catalog import override from objects.providers import Singleton from objects.providers import NewInstance @@ -11,6 +10,8 @@ from objects.providers import NewInstance from objects.injections import KwArg from objects.injections import Attribute +from objects.decorators import override + class ObjectA(object): diff --git a/examples/readme/overriding_provider.py b/examples/readme/overriding_provider.py deleted file mode 100644 index 41ff0fe3..00000000 --- a/examples/readme/overriding_provider.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Provider overriding example.""" - -import sqlite3 - -from objects.providers import Singleton -from objects.providers import NewInstance - -from objects.injections import KwArg -from objects.injections import Attribute - - -class ObjectA(object): - - """ObjectA has dependency on database.""" - - def __init__(self, database): - """Initializer. - - Database dependency need to be injected via init arg.""" - self.database = database - - def get_one(self): - """Select one from database and return it.""" - return self.database.execute('SELECT 1') - - -class ObjectAMock(ObjectA): - - """Mock of ObjectA. - - Has no dependency on database. - """ - - def __init__(self): - """Initializer.""" - - def get_one(self): - """Select one from database and return it. - - Mock makes no database queries and always returns two instead of one. - """ - return 2 - - -# Database and `ObjectA` providers. -database = Singleton(sqlite3.Connection, - KwArg('database', ':memory:'), - KwArg('timeout', 30), - KwArg('detect_types', True), - KwArg('isolation_level', 'EXCLUSIVE'), - Attribute('row_factory', sqlite3.Row)) - -object_a = NewInstance(ObjectA, - KwArg('database', database)) - - -# Overriding `ObjectA` provider with `ObjectAMock` provider. -object_a.override(NewInstance(ObjectAMock)) - -# Creating several `ObjectA` instances. -object_a_1 = object_a() -object_a_2 = object_a() - -# Making some asserts. -assert object_a_1 is not object_a_2 -assert object_a_1.get_one() == object_a_2.get_one() == 2 diff --git a/examples/readme/providers.py b/examples/readme/providers.py deleted file mode 100644 index ce662c38..00000000 --- a/examples/readme/providers.py +++ /dev/null @@ -1,23 +0,0 @@ -"""`NewInstance` and `Singleton` providers example.""" - -from objects.providers import NewInstance -from objects.providers import Singleton - - -# NewInstance provider will create new instance of specified class -# on every call. -new_object = NewInstance(object) - -object_1 = new_object() -object_2 = new_object() - -assert object_1 is not object_2 - -# Singleton provider will create new instance of specified class on first call, -# and return same instance on every next call. -single_object = Singleton(object) - -single_object_1 = single_object() -single_object_2 = single_object() - -assert single_object_1 is single_object_2 diff --git a/examples/readme/injections.py b/examples/readme2/inject_decorator.py similarity index 52% rename from examples/readme/injections.py rename to examples/readme2/inject_decorator.py index 52590a7e..fe04e639 100644 --- a/examples/readme/injections.py +++ b/examples/readme2/inject_decorator.py @@ -1,12 +1,17 @@ -"""`KwArg` and `Attribute` injections example.""" +"""`@inject` decorator example. + +Flask is required to make this example work. +""" import sqlite3 -from objects.providers import Singleton -from objects.providers import NewInstance +from flask import Flask +from objects.providers import Factory +from objects.providers import Singleton from objects.injections import KwArg from objects.injections import Attribute +from objects.decorators import inject class ObjectA(object): @@ -32,14 +37,25 @@ database = Singleton(sqlite3.Connection, KwArg('isolation_level', 'EXCLUSIVE'), Attribute('row_factory', sqlite3.Row)) -object_a = NewInstance(ObjectA, - KwArg('database', database)) +object_a_factory = Factory(ObjectA, + KwArg('database', database)) -# Creating several `ObjectA` instances. -object_a_1 = object_a() -object_a_2 = object_a() -# Making some asserts. -assert object_a_1 is not object_a_2 -assert object_a_1.database is object_a_2.database -assert object_a_1.get_one() == object_a_2.get_one() == 1 +# Flask application. +app = Flask(__name__) + + +# Flask view with inject decorator. +@app.route('/') +@inject(KwArg('database', database)) +@inject(KwArg('object_a', object_a_factory)) +def hello(database): + one = database.execute('SELECT 1').fetchone()[0] + return 'Query returned {0}, db connection {1}'.format(one, database) + + +if __name__ == '__main__': + app.run() + +# Example output of "GET / HTTP/1.1" is: +# Query returned 1, db connection diff --git a/objects/__init__.py b/objects/__init__.py index b6d6d6bb..4b9e34d3 100644 --- a/objects/__init__.py +++ b/objects/__init__.py @@ -4,11 +4,10 @@ Dependency management tool for Python projects. """ from .catalog import AbstractCatalog -from .catalog import override from .providers import Provider from .providers import Delegate -from .providers import NewInstance +from .providers import Factory from .providers import Singleton from .providers import ExternalDependency from .providers import Class @@ -22,16 +21,18 @@ from .injections import KwArg from .injections import Attribute from .injections import Method +from .decorators import override +from .decorators import inject + from .errors import Error __all__ = ('AbstractCatalog', - 'override', # Providers 'Provider', 'Delegate', - 'NewInstance', + 'Factory', 'Singleton', 'ExternalDependency', 'Class', @@ -46,5 +47,9 @@ __all__ = ('AbstractCatalog', 'Attribute', 'Method', + # Decorators + 'override', + 'inject', + # Errors 'Error') diff --git a/objects/catalog.py b/objects/catalog.py index 81d12bcf..4630a76a 100644 --- a/objects/catalog.py +++ b/objects/catalog.py @@ -1,58 +1,67 @@ """Catalog module.""" -from .providers import Provider +from six import iteritems +from six import add_metaclass + from .errors import Error +from .utils import is_provider +class CatalogMetaClass(type): + + """Providers catalog meta class.""" + + def __new__(mcs, class_name, bases, attributes): + """Meta class factory.""" + providers = dict() + new_attributes = dict() + for name, value in iteritems(attributes): + if is_provider(value): + providers[name] = value + new_attributes[name] = value + + cls = type.__new__(mcs, class_name, bases, new_attributes) + cls.providers = cls.providers.copy() + cls.providers.update(providers) + return cls + + +@add_metaclass(CatalogMetaClass) class AbstractCatalog(object): - """Abstract object provides catalog.""" + """Abstract providers catalog.""" - __slots__ = ('__used_providers__',) + providers = dict() + + __slots__ = ('_used_providers',) def __init__(self, *used_providers): """Initializer.""" - self.__used_providers__ = set(used_providers) + self._used_providers = set(used_providers) def __getattribute__(self, item): """Return providers.""" attribute = super(AbstractCatalog, self).__getattribute__(item) - if item in ('__used_providers__',): + if item in ('providers', '_used_providers',): return attribute - if attribute not in self.__used_providers__: + if attribute not in self._used_providers: raise Error('Provider \'{0}\' '.format(item) + 'is not listed in dependencies') return attribute @classmethod - def all_providers(cls, provider_type=Provider): - """Return set of all class providers.""" - providers = set() - for attr_name in set(dir(cls)) - set(dir(AbstractCatalog)): - provider = getattr(cls, attr_name) - if not isinstance(provider, provider_type): - continue - providers.add((attr_name, provider)) - return providers + def filter(cls, provider_type): + """Return dict of providers, that are instance of provided type.""" + return dict([(name, provider) + for name, provider in iteritems(cls.providers) + if isinstance(provider, provider_type)]) @classmethod def override(cls, overriding): + """Override current catalog providers by overriding catalog providers. + + :type overriding: AbstractCatalog """ - Override current catalog providers by overriding catalog providers. - - :param overriding: AbstractCatalog - """ - overridden = overriding.all_providers() - cls.all_providers() - for name, provider in overridden: - overridden_provider = getattr(cls, name) - overridden_provider.override(provider) - - -def override(catalog): - """Catalog overriding decorator.""" - def decorator(overriding_catalog): - """Overriding decorator.""" - catalog.override(overriding_catalog) - return overriding_catalog - return decorator + for name, provider in iteritems(overriding.providers): + cls.providers[name].override(provider) diff --git a/objects/decorators.py b/objects/decorators.py new file mode 100644 index 00000000..fea9d86d --- /dev/null +++ b/objects/decorators.py @@ -0,0 +1,41 @@ +"""Decorators module.""" + +from six import wraps + +from .utils import ensure_is_injection + + +def override(catalog): + """Catalog overriding decorator.""" + def decorator(overriding_catalog): + """Overriding decorator.""" + catalog.override(overriding_catalog) + return overriding_catalog + return decorator + + +def inject(injection): + """Dependency injection decorator. + + :type injection: Injection + :return: (callable) -> (callable) + """ + injection = ensure_is_injection(injection) + + def decorator(callback): + """Dependency injection decorator.""" + if hasattr(callback, '_injections'): + callback._injections += (injection,) + + @wraps(callback) + def decorated(*args, **kwargs): + """Decorated with dependency injection callback.""" + for injection in getattr(decorated, '_injections'): + if injection.name not in kwargs: + kwargs[injection.name] = injection.value + return callback(*args, **kwargs) + + setattr(decorated, '_injections', (injection,)) + + return decorated + return decorator diff --git a/objects/injections.py b/objects/injections.py index 99245e88..3bb70df7 100644 --- a/objects/injections.py +++ b/objects/injections.py @@ -1,16 +1,13 @@ """Injections module.""" -from six import wraps - from .utils import is_provider -from .utils import ensure_is_injection class Injection(object): """Base injection class.""" - __IS_OBJECTS_INJECTION__ = True + __IS_INJECTION__ = True __slots__ = ('name', 'injectable') def __init__(self, name, injectable): @@ -30,38 +27,18 @@ class KwArg(Injection): """Keyword argument injection.""" - __IS_OBJECTS_KWARG_INJECTION__ = True + __IS_KWARG_INJECTION__ = True class Attribute(Injection): """Attribute injection.""" - __IS_OBJECTS_ATTRIBUTE_INJECTION__ = True + __IS_ATTRIBUTE_INJECTION__ = True class Method(Injection): """Method injection.""" - __IS_OBJECTS_METHOD_INJECTION__ = True - - -def inject(injection): - """Inject decorator. - - :type injection: Injection - :return: (callable) -> (callable) - """ - injection = ensure_is_injection(injection) - - def decorator(callback): - """Decorator.""" - @wraps(callback) - def decorated(*args, **kwargs): - """Decorated.""" - if injection.name not in kwargs: - kwargs[injection.name] = injection.value - return callback(*args, **kwargs) - return decorated - return decorator + __IS_METHOD_INJECTION__ = True diff --git a/objects/providers.py b/objects/providers.py index a40471a8..7857cd4c 100644 --- a/objects/providers.py +++ b/objects/providers.py @@ -14,15 +14,26 @@ class Provider(object): """Base provider class.""" - __IS_OBJECTS_PROVIDER__ = True - __slots__ = ('overridden',) + __IS_PROVIDER__ = True + __slots__ = ('_overridden',) def __init__(self): """Initializer.""" - self.overridden = None + self._overridden = None def __call__(self, *args, **kwargs): """Return provided instance.""" + if self._overridden: + return self.last_overriding(*args, **kwargs) + return self._provide(*args, **kwargs) + + def _provide(self, *args, **kwargs): + """Providing strategy implementation. + + Abstract protected method that implements providing strategy of + particular provider. Current method is called every time when not + overridden provider is called. Need to be overridden in subclasses. + """ raise NotImplementedError() def delegate(self): @@ -31,112 +42,130 @@ class Provider(object): def override(self, provider): """Override provider with another provider.""" - if not self.overridden: - self.overridden = (ensure_is_provider(provider),) + if not self._overridden: + self._overridden = (ensure_is_provider(provider),) else: - self.overridden = self.overridden + (ensure_is_provider(provider),) + self._overridden += (ensure_is_provider(provider),) - def reset_override(self): - """Reset all overriding providers.""" - self.overridden = None + @property + def is_overridden(self): + """Check if provider is overridden by another provider.""" + return bool(self._overridden) @property def last_overriding(self): """Return last overriding provider.""" try: - return self.overridden[-1] + return self._overridden[-1] except (TypeError, IndexError): - raise Error('Provider {0} '.format(str(self)) + - 'is not overridden') + raise Error('Provider {0} is not overridden'.format(str(self))) + + def reset_last_overriding(self): + """Reset last overriding provider.""" + if not self._overridden: + raise Error('Provider {0} is not overridden'.format(str(self))) + self._overridden = self._overridden[:-1] + + def reset_override(self): + """Reset all overriding providers.""" + self._overridden = None class Delegate(Provider): """Provider's delegate.""" - __slots__ = ('delegated',) + __slots__ = ('_delegated',) def __init__(self, delegated): """Initializer. :type delegated: Provider """ - self.delegated = ensure_is_provider(delegated) + self._delegated = ensure_is_provider(delegated) super(Delegate, self).__init__() - def __call__(self): + def _provide(self, *args, **kwargs): """Return provided instance.""" - return self.delegated + return self._delegated -class NewInstance(Provider): +class Factory(Provider): - """New instance provider. + """Factory provider. - New instance providers will create and return new instance on every call. + Factory provider creates new instance of specified class on every call. """ - __slots__ = ('provides', 'kwargs', 'attributes', 'methods') + __slots__ = ('_provides', '_kwargs', '_attributes', '_methods') def __init__(self, provides, *injections): """Initializer.""" - if not isinstance(provides, class_types): - raise Error('NewInstance provider expects to get class, ' + + if not callable(provides): + raise Error('Factory provider expects to get callable, ' + 'got {0} instead'.format(str(provides))) - self.provides = provides - self.kwargs = tuple((injection - for injection in injections - if is_kwarg_injection(injection))) - self.attributes = tuple((injection - for injection in injections - if is_attribute_injection(injection))) - self.methods = tuple((injection + self._provides = provides + self._kwargs = tuple((injection for injection in injections - if is_method_injection(injection))) - super(NewInstance, self).__init__() + if is_kwarg_injection(injection))) + self._attributes = tuple((injection + for injection in injections + if is_attribute_injection(injection))) + self._methods = tuple((injection + for injection in injections + if is_method_injection(injection))) + super(Factory, self).__init__() - def __call__(self, *args, **kwargs): + def _provide(self, *args, **kwargs): """Return provided instance.""" - if self.overridden: - return self.last_overriding(*args, **kwargs) - init_kwargs = dict(((injection.name, injection.value) - for injection in self.kwargs)) + for injection in self._kwargs)) init_kwargs.update(kwargs) - instance = self.provides(*args, **init_kwargs) + instance = self._provides(*args, **init_kwargs) - for attribute in self.attributes: + for attribute in self._attributes: setattr(instance, attribute.name, attribute.value) - for method in self.methods: + for method in self._methods: getattr(instance, method.name)(method.value) return instance -class Singleton(NewInstance): +class NewInstance(Factory): + + """NewInstance provider. + + It is synonym of Factory provider. NewInstance provider is considered to + be deprecated, but will be able to use for further backward + compatibility. + """ + + +class Singleton(Provider): """Singleton provider. Singleton provider will create instance once and return it on every call. """ - __slots__ = ('instance',) + __slots__ = ('_instance', '_factory') - def __init__(self, *args, **kwargs): + def __init__(self, provides, *injections): """Initializer.""" - self.instance = None - super(Singleton, self).__init__(*args, **kwargs) + self._instance = None + self._factory = Factory(provides, *injections) + super(Singleton, self).__init__() - def __call__(self, *args, **kwargs): + def _provide(self, *args, **kwargs): """Return provided instance.""" - if not self.instance: - self.instance = super(Singleton, self).__call__(*args, **kwargs) - return self.instance + if not self._instance: + self._instance = self._factory(*args, **kwargs) + return self._instance def reset(self): """Reset instance.""" - self.instance = None + self._instance = None class ExternalDependency(Provider): @@ -147,29 +176,33 @@ class ExternalDependency(Provider): the client's code, but it's interface is known. """ - __slots__ = ('instance_of',) + __slots__ = ('_instance_of',) def __init__(self, instance_of): """Initializer.""" if not isinstance(instance_of, class_types): raise Error('ExternalDependency provider expects to get class, ' + 'got {0} instead'.format(str(instance_of))) - self.instance_of = instance_of + self._instance_of = instance_of super(ExternalDependency, self).__init__() def __call__(self, *args, **kwargs): """Return provided instance.""" - if not self.overridden: + if not self._overridden: raise Error('Dependency is not defined') instance = self.last_overriding(*args, **kwargs) - if not isinstance(instance, self.instance_of): + if not isinstance(instance, self._instance_of): raise Error('{0} is not an '.format(instance) + - 'instance of {0}'.format(self.instance_of)) + 'instance of {0}'.format(self._instance_of)) return instance + def provided_by(self, provider): + """Set external dependency provider.""" + return self.override(provider) + class _StaticProvider(Provider): @@ -179,18 +212,16 @@ class _StaticProvider(Provider): it got on input. """ - __slots__ = ('provides',) + __slots__ = ('_provides',) def __init__(self, provides): """Initializer.""" - self.provides = provides + self._provides = provides super(_StaticProvider, self).__init__() - def __call__(self): + def _provide(self, *args, **kwargs): """Return provided instance.""" - if self.overridden: - return self.last_overriding() - return self.provides + return self._provides class Class(_StaticProvider): @@ -221,28 +252,25 @@ class Callable(Provider): with some predefined dependency injections. """ - __slots__ = ('callback', 'injections') + __slots__ = ('_callback', '_injections') def __init__(self, callback, *injections): """Initializer.""" if not callable(callback): raise Error('Callable expected, got {0}'.format(str(callback))) - self.callback = callback - self.injections = tuple((injection - for injection in injections - if is_kwarg_injection(injection))) + self._callback = callback + self._injections = tuple((injection + for injection in injections + if is_kwarg_injection(injection))) super(Callable, self).__init__() - def __call__(self, *args, **kwargs): + def _provide(self, *args, **kwargs): """Return provided instance.""" - if self.overridden: - return self.last_overriding() - injections = dict(((injection.name, injection.value) - for injection in self.injections)) + for injection in self._injections)) injections.update(kwargs) - return self.callback(*args, **injections) + return self._callback(*args, **injections) class Config(Provider): @@ -254,18 +282,22 @@ class Config(Provider): to create deferred config value provider. """ - __slots__ = ('value',) + __slots__ = ('_value',) def __init__(self, value=None): """Initializer.""" if not value: value = dict() - self.value = value + self._value = value super(Config, self).__init__() - def __call__(self, paths=None): + def __getattr__(self, item): + """Return instance of deferred config.""" + return _ChildConfig(parents=(item,), root_config=self) + + def _provide(self, paths=None): """Return provided instance.""" - value = self.value + value = self._value if paths: for path in paths: try: @@ -275,13 +307,9 @@ class Config(Provider): '"{0}" is undefined'.format('.'.join(paths))) return value - def __getattr__(self, item): - """Return instance of deferred config.""" - return _ChildConfig(parents=(item,), root_config=self) - def update_from(self, value): """Update current value from another one.""" - self.value.update(value) + self._value.update(value) class _ChildConfig(Provider): @@ -292,19 +320,19 @@ class _ChildConfig(Provider): the current path in the config tree. """ - __slots__ = ('parents', 'root_config') + __slots__ = ('_parents', '_root_config') def __init__(self, parents, root_config): """Initializer.""" - self.parents = parents - self.root_config = root_config + self._parents = parents + self._root_config = root_config super(_ChildConfig, self).__init__() - def __call__(self, *args, **kwargs): - """Return provided instance.""" - return self.root_config(self.parents) - def __getattr__(self, item): """Return instance of deferred config.""" - return _ChildConfig(parents=self.parents + (item,), - root_config=self.root_config) + return _ChildConfig(parents=self._parents + (item,), + root_config=self._root_config) + + def _provide(self, *args, **kwargs): + """Return provided instance.""" + return self._root_config(self._parents) diff --git a/objects/utils.py b/objects/utils.py index 89319021..392fad96 100644 --- a/objects/utils.py +++ b/objects/utils.py @@ -8,7 +8,7 @@ from .errors import Error def is_provider(instance): """Check if instance is provider instance.""" return (not isinstance(instance, class_types) and - hasattr(instance, '__IS_OBJECTS_PROVIDER__')) + getattr(instance, '__IS_PROVIDER__', False) is True) def ensure_is_provider(instance): @@ -22,7 +22,7 @@ def ensure_is_provider(instance): def is_injection(instance): """Check if instance is injection instance.""" return (not isinstance(instance, class_types) and - hasattr(instance, '__IS_OBJECTS_INJECTION__')) + getattr(instance, '__IS_INJECTION__', False) is True) def ensure_is_injection(instance): @@ -36,16 +36,16 @@ def ensure_is_injection(instance): def is_kwarg_injection(instance): """Check if instance is keyword argument injection instance.""" return (not isinstance(instance, class_types) and - hasattr(instance, '__IS_OBJECTS_KWARG_INJECTION__')) + getattr(instance, '__IS_KWARG_INJECTION__', False) is True) def is_attribute_injection(instance): """Check if instance is attribute injection instance.""" return (not isinstance(instance, class_types) and - hasattr(instance, '__IS_OBJECTS_ATTRIBUTE_INJECTION__')) + getattr(instance, '__IS_ATTRIBUTE_INJECTION__', False) is True) def is_method_injection(instance): """Check if instance is method injection instance.""" return (not isinstance(instance, class_types) and - hasattr(instance, '__IS_OBJECTS_METHOD_INJECTION__')) + getattr(instance, '__IS_METHOD_INJECTION__', False) is True) diff --git a/setup.py b/setup.py index a000fa98..ca1de764 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ from setuptools import setup from setuptools import Command -SHORT_DESCRIPTION = 'Dependency management tool for Python projects' +SHORT_DESCRIPTION = 'Dependency injection framework for Python projects' # Getting description. @@ -47,8 +47,6 @@ class PublishCommand(Command): self.run_command('upload') os.system('git tag -a {0} -m \'version {0}\''.format(version)) os.system('git push --tags') - - setup(name='Objects', version=version, description=SHORT_DESCRIPTION, @@ -69,9 +67,9 @@ setup(name='Objects', 'Dependency management', 'Dependency injection', 'Dependency injection container', + 'Dependency injector', 'DI', 'DIC', - 'Dependency injector', 'Inversion of Control', 'Inversion of Control container', 'IoC', diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 065e56f5..16bedf1c 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -3,7 +3,6 @@ import unittest2 as unittest from objects.catalog import AbstractCatalog -from objects.catalog import override from objects.providers import Object from objects.providers import Value @@ -34,32 +33,38 @@ class CatalogTests(unittest.TestCase): def test_all_providers(self): """Test getting of all catalog providers.""" - all_providers = self.Catalog.all_providers() - all_providers_dict = dict(all_providers) + self.assertTrue(len(self.Catalog.providers) == 2) - self.assertIsInstance(all_providers, set) - self.assertTrue(len(all_providers) == 2) + self.assertIn('obj', self.Catalog.providers) + self.assertIn(self.Catalog.obj, self.Catalog.providers.values()) - self.assertIn('obj', all_providers_dict) - self.assertIn(self.Catalog.obj, all_providers_dict.values()) - - self.assertIn('another_obj', all_providers_dict) - self.assertIn(self.Catalog.another_obj, all_providers_dict.values()) + self.assertIn('another_obj', self.Catalog.providers) + self.assertIn(self.Catalog.another_obj, + self.Catalog.providers.values()) def test_all_providers_by_type(self): """Test getting of all catalog providers of specific type.""" - self.assertTrue(len(self.Catalog.all_providers(Object)) == 2) - self.assertTrue(len(self.Catalog.all_providers(Value)) == 0) + self.assertTrue(len(self.Catalog.filter(Object)) == 2) + self.assertTrue(len(self.Catalog.filter(Value)) == 0) - def test_overriding(self): - """Test catalog overriding with another catalog.""" - @override(self.Catalog) - class OverridingCatalog(self.Catalog): + def test_metaclass_with_several_catalogs(self): + """Test that metaclass work well with several catalogs.""" + class Catalog1(AbstractCatalog): - """Overriding catalog.""" + """Catalog1.""" - obj = Value(1) - another_obj = Value(2) + provider = Object(object()) - self.assertEqual(self.Catalog.obj(), 1) - self.assertEqual(self.Catalog.another_obj(), 2) + class Catalog2(AbstractCatalog): + + """Catalog2.""" + + provider = Object(object()) + + self.assertTrue(len(Catalog1.providers) == 1) + self.assertIs(Catalog1.provider, Catalog1.providers['provider']) + + self.assertTrue(len(Catalog2.providers) == 1) + self.assertIs(Catalog2.provider, Catalog2.providers['provider']) + + self.assertIsNot(Catalog1.provider, Catalog2.provider) diff --git a/tests/test_decorators.py b/tests/test_decorators.py new file mode 100644 index 00000000..1f3dbbb8 --- /dev/null +++ b/tests/test_decorators.py @@ -0,0 +1,115 @@ +"""Objects decorators unittests.""" + +import unittest2 as unittest + +from objects.decorators import override +from objects.decorators import inject + +from objects.catalog import AbstractCatalog + +from objects.providers import Factory +from objects.providers import Object +from objects.providers import Value + +from objects.injections import KwArg + +from objects.errors import Error + + +class OverrideTests(unittest.TestCase): + + """Override decorator test cases.""" + + class Catalog(AbstractCatalog): + + """Test catalog.""" + + obj = Object(object()) + another_obj = Object(object()) + + def test_overriding(self): + """Test catalog overriding with another catalog.""" + @override(self.Catalog) + class OverridingCatalog(self.Catalog): + + """Overriding catalog.""" + + obj = Value(1) + another_obj = Value(2) + + self.assertEqual(self.Catalog.obj(), 1) + self.assertEqual(self.Catalog.another_obj(), 2) + + +class InjectTests(unittest.TestCase): + + """Inject decorator test cases.""" + + def test_decorated(self): + """Test `inject()` decorated callback.""" + provider1 = Factory(object) + provider2 = Factory(list) + + @inject(KwArg('a', provider1)) + @inject(KwArg('b', provider2)) + def test(a, b): + return a, b + + a1, b1 = test() + a2, b2 = test() + + self.assertIsInstance(a1, object) + self.assertIsInstance(a2, object) + self.assertIsNot(a1, a2) + + self.assertIsInstance(b1, list) + self.assertIsInstance(b2, list) + self.assertIsNot(b1, b2) + + def test_decorated_kwargs_priority(self): + """Test `inject()` decorated callback kwargs priority.""" + provider1 = Factory(object) + provider2 = Factory(list) + object_a = object() + + @inject(KwArg('a', provider1)) + @inject(KwArg('b', provider2)) + def test(a, b): + return a, b + + a1, b1 = test(a=object_a) + a2, b2 = test(a=object_a) + + self.assertIsInstance(a1, object) + self.assertIsInstance(a2, object) + self.assertIs(a1, object_a) + self.assertIs(a2, object_a) + + self.assertIsInstance(b1, list) + self.assertIsInstance(b2, list) + self.assertIsNot(b1, b2) + + def test_decorated_with_args(self): + """Test `inject()` decorated callback with args.""" + provider = Factory(list) + object_a = object() + + @inject(KwArg('b', provider)) + def test(a, b): + return a, b + + a1, b1 = test(object_a) + a2, b2 = test(object_a) + + self.assertIsInstance(a1, object) + self.assertIsInstance(a2, object) + self.assertIs(a1, object_a) + self.assertIs(a2, object_a) + + self.assertIsInstance(b1, list) + self.assertIsInstance(b2, list) + self.assertIsNot(b1, b2) + + def test_decorate_with_not_injection(self): + """Test `inject()` decorator with not an injection instance.""" + self.assertRaises(Error, inject, object) diff --git a/tests/test_injections.py b/tests/test_injections.py index b4d5ff3c..aa682ecd 100644 --- a/tests/test_injections.py +++ b/tests/test_injections.py @@ -6,11 +6,8 @@ from objects.injections import Injection from objects.injections import KwArg from objects.injections import Attribute from objects.injections import Method -from objects.injections import inject -from objects.providers import NewInstance - -from objects.providers import Error +from objects.providers import Factory class InjectionTests(unittest.TestCase): @@ -30,7 +27,7 @@ class InjectionTests(unittest.TestCase): def test_value_with_provider_injectable(self): """Test Injection value property with provider.""" - injection = Injection('some_arg_name', NewInstance(object)) + injection = Injection('some_arg_name', Factory(object)) self.assertIsInstance(injection.value, object) @@ -65,77 +62,3 @@ class MethodTests(unittest.TestCase): injection = Method('some_arg_name', 'some_value') self.assertEqual(injection.name, 'some_arg_name') self.assertEqual(injection.injectable, 'some_value') - - -class InjectTests(unittest.TestCase): - - """Inject decorator test cases.""" - - def test_decorated(self): - """Test `inject()` decorated callback.""" - provider1 = NewInstance(object) - provider2 = NewInstance(list) - - @inject(KwArg('a', provider1)) - @inject(KwArg('b', provider2)) - def test(a, b): - return a, b - - a1, b1 = test() - a2, b2 = test() - - self.assertIsInstance(a1, object) - self.assertIsInstance(a2, object) - self.assertIsNot(a1, a2) - - self.assertIsInstance(b1, list) - self.assertIsInstance(b2, list) - self.assertIsNot(b1, b2) - - def test_decorated_kwargs_priority(self): - """Test `inject()` decorated callback kwargs priority.""" - provider1 = NewInstance(object) - provider2 = NewInstance(list) - object_a = object() - - @inject(KwArg('a', provider1)) - @inject(KwArg('b', provider2)) - def test(a, b): - return a, b - - a1, b1 = test(a=object_a) - a2, b2 = test(a=object_a) - - self.assertIsInstance(a1, object) - self.assertIsInstance(a2, object) - self.assertIs(a1, object_a) - self.assertIs(a2, object_a) - - self.assertIsInstance(b1, list) - self.assertIsInstance(b2, list) - self.assertIsNot(b1, b2) - - def test_decorated_with_args(self): - """Test `inject()` decorated callback with args.""" - provider = NewInstance(list) - object_a = object() - - @inject(KwArg('b', provider)) - def test(a, b): - return a, b - - a1, b1 = test(object_a) - a2, b2 = test(object_a) - - self.assertIsInstance(a1, object) - self.assertIsInstance(a2, object) - self.assertIs(a1, object_a) - self.assertIs(a2, object_a) - - self.assertIsInstance(b1, list) - self.assertIsInstance(b2, list) - self.assertIsNot(b1, b2) - - def test_decorate_with_not_injection(self): - """Test `inject()` decorator with not an injection instance.""" - self.assertRaises(Error, inject, object) diff --git a/tests/test_providers.py b/tests/test_providers.py index 85ac1cba..16c9bf16 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -4,7 +4,7 @@ import unittest2 as unittest from objects.providers import Provider from objects.providers import Delegate -from objects.providers import NewInstance +from objects.providers import Factory from objects.providers import Singleton from objects.providers import ExternalDependency from objects.providers import Class @@ -44,12 +44,12 @@ class ProviderTests(unittest.TestCase): delegate1 = self.provider.delegate() self.assertIsInstance(delegate1, Delegate) - self.assertIs(delegate1.delegated, self.provider) + self.assertIs(delegate1(), self.provider) delegate2 = self.provider.delegate() self.assertIsInstance(delegate2, Delegate) - self.assertIs(delegate2.delegated, self.provider) + self.assertIs(delegate2(), self.provider) self.assertIsNot(delegate1, delegate2) @@ -57,31 +57,12 @@ class ProviderTests(unittest.TestCase): """Test provider overriding.""" overriding_provider = Provider() self.provider.override(overriding_provider) - self.assertTrue(self.provider.overridden) + self.assertTrue(self.provider.is_overridden) def test_override_with_not_provider(self): """Test provider overriding with not provider instance.""" self.assertRaises(Error, self.provider.override, object()) - def test_reset_override(self): - """Test reset of provider's override.""" - overriding_provider = Provider() - self.provider.override(overriding_provider) - - self.assertTrue(self.provider.overridden) - self.assertIs(self.provider.last_overriding, overriding_provider) - - self.provider.reset_override() - - self.assertFalse(self.provider.overridden) - try: - self.provider.last_overriding - except Error: - pass - else: - self.fail('Got en error in {}'.format( - str(self.test_last_overriding_of_not_overridden_provider))) - def test_last_overriding(self): """Test getting last overriding provider.""" overriding_provider1 = Provider() @@ -103,6 +84,45 @@ class ProviderTests(unittest.TestCase): self.fail('Got en error in {}'.format( str(self.test_last_overriding_of_not_overridden_provider))) + def test_reset_last_overriding(self): + """Test reseting of last overriding provider.""" + overriding_provider1 = Provider() + overriding_provider2 = Provider() + + self.provider.override(overriding_provider1) + self.provider.override(overriding_provider2) + + self.assertIs(self.provider.last_overriding, overriding_provider2) + + self.provider.reset_last_overriding() + self.assertIs(self.provider.last_overriding, overriding_provider1) + + self.provider.reset_last_overriding() + self.assertFalse(self.provider.is_overridden) + + def test_reset_last_overriding_of_not_overridden_provider(self): + """Test resetting of last overriding on not overridden provier.""" + self.assertRaises(Error, self.provider.reset_last_overriding) + + def test_reset_override(self): + """Test reset of provider's override.""" + overriding_provider = Provider() + self.provider.override(overriding_provider) + + self.assertTrue(self.provider.is_overridden) + self.assertIs(self.provider.last_overriding, overriding_provider) + + self.provider.reset_override() + + self.assertFalse(self.provider.is_overridden) + try: + self.provider.last_overriding + except Error: + pass + else: + self.fail('Got en error in {}'.format( + str(self.test_last_overriding_of_not_overridden_provider))) + class DelegateTests(unittest.TestCase): @@ -130,13 +150,13 @@ class DelegateTests(unittest.TestCase): self.assertIs(delegated2, self.delegated) -class NewInstanceTests(unittest.TestCase): +class FactoryTests(unittest.TestCase): - """NewInstance test cases.""" + """Factory test cases.""" class Example(object): - """Example class for NewInstance provider tests.""" + """Example class for Factory provider tests.""" def __init__(self, init_arg1=None, init_arg2=None): """Initializer. @@ -164,15 +184,19 @@ class NewInstanceTests(unittest.TestCase): def test_is_provider(self): """Test `is_provider` check.""" - self.assertTrue(is_provider(NewInstance(self.Example))) + self.assertTrue(is_provider(Factory(self.Example))) - def test_init_with_not_class(self): - """Test creation of provider with not a class.""" - self.assertRaises(Error, NewInstance, 123) + def test_init_with_callable(self): + """Test creation of provider with a callable.""" + self.assertTrue(Factory(credits)) + + def test_init_with_not_callable(self): + """Test creation of provider with not a callable.""" + self.assertRaises(Error, Factory, 123) def test_call(self): """Test creation of new instances.""" - provider = NewInstance(self.Example) + provider = Factory(self.Example) instance1 = provider() instance2 = provider() @@ -182,9 +206,9 @@ class NewInstanceTests(unittest.TestCase): def test_call_with_init_args(self): """Test creation of new instances with init args injections.""" - provider = NewInstance(self.Example, - KwArg('init_arg1', 'i1'), - KwArg('init_arg2', 'i2')) + provider = Factory(self.Example, + KwArg('init_arg1', 'i1'), + KwArg('init_arg2', 'i2')) instance1 = provider() instance2 = provider() @@ -201,9 +225,9 @@ class NewInstanceTests(unittest.TestCase): def test_call_with_attributes(self): """Test creation of new instances with attribute injections.""" - provider = NewInstance(self.Example, - Attribute('attribute1', 'a1'), - Attribute('attribute2', 'a2')) + provider = Factory(self.Example, + Attribute('attribute1', 'a1'), + Attribute('attribute2', 'a2')) instance1 = provider() instance2 = provider() @@ -220,9 +244,9 @@ class NewInstanceTests(unittest.TestCase): def test_call_with_methods(self): """Test creation of new instances with method injections.""" - provider = NewInstance(self.Example, - Method('method1', 'm1'), - Method('method2', 'm2')) + provider = Factory(self.Example, + Method('method1', 'm1'), + Method('method2', 'm2')) instance1 = provider() instance2 = provider() @@ -239,7 +263,7 @@ class NewInstanceTests(unittest.TestCase): def test_call_with_context_args(self): """Test creation of new instances with context args.""" - provider = NewInstance(self.Example) + provider = Factory(self.Example) instance = provider(11, 22) self.assertEqual(instance.init_arg1, 11) @@ -247,8 +271,8 @@ class NewInstanceTests(unittest.TestCase): def test_call_with_context_kwargs(self): """Test creation of new instances with context kwargs.""" - provider = NewInstance(self.Example, - KwArg('init_arg1', 1)) + provider = Factory(self.Example, + KwArg('init_arg1', 1)) instance1 = provider(init_arg2=22) self.assertEqual(instance1.init_arg1, 1) @@ -260,9 +284,9 @@ class NewInstanceTests(unittest.TestCase): def test_call_overridden(self): """Test creation of new instances on overridden provider.""" - provider = NewInstance(self.Example) - overriding_provider1 = NewInstance(dict) - overriding_provider2 = NewInstance(list) + provider = Factory(self.Example) + overriding_provider1 = Factory(dict) + overriding_provider2 = Factory(list) provider.override(overriding_provider1) provider.override(overriding_provider2) @@ -323,12 +347,12 @@ class ExternalDependencyTests(unittest.TestCase): def test_call_overridden(self): """Test call of overridden external dependency.""" - self.provider.override(NewInstance(list)) + self.provider.provided_by(Factory(list)) self.assertIsInstance(self.provider(), list) def test_call_overridden_but_not_instance_of(self): """Test call of overridden external dependency, but not instance of.""" - self.provider.override(NewInstance(dict)) + self.provider.provided_by(Factory(dict)) self.assertRaises(Error, self.provider) def test_call_not_overridden(self): diff --git a/tests/test_utils.py b/tests/test_utils.py index f4b038ce..3de7e5e7 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -40,6 +40,26 @@ class IsProviderTests(unittest.TestCase): """Test with object.""" self.assertFalse(is_provider(object())) + def test_with_subclass_instance(self): + """Test with subclass of provider instance.""" + class SomeProvider(Provider): + + """Some provider for test.""" + + self.assertTrue(is_provider(SomeProvider())) + + def test_with_class_with_getattr(self): + """Test with class that has __getattr__() method implementation.""" + class SomeClass(object): + + """Some test class with __getattr__() method implementation.""" + + def __getattr__(self, _): + """Test implementation that just returns False.""" + return False + + self.assertFalse(is_provider(SomeClass())) + class EnsureIsProviderTests(unittest.TestCase): diff --git a/tox.ini b/tox.ini index 2dff08c0..bb950ba9 100644 --- a/tox.ini +++ b/tox.ini @@ -18,6 +18,7 @@ commands= [testenv:coveralls] basepython=python2.7 +passenv=TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH deps= {[testenv]deps} coverage @@ -39,14 +40,14 @@ basepython=python2.7 deps= flake8 commands= - flake8 --max-complexity=8 objects + flake8 --max-complexity=8 objects/ [testenv:pep257] basepython=python2.7 deps= pep257 commands= - pep257 objects + pep257 objects/ [testenv:py26] basepython=python2.6