moving @override and @inject decorators to decorators module

This commit is contained in:
Roman Mogilatov 2015-04-14 23:17:53 +03:00
parent 463f219307
commit b0720cfc2c
18 changed files with 453 additions and 136 deletions

View File

@ -38,6 +38,7 @@ Contents
introduction
installation
providers
entities
advanced_usage
examples

242
docs/providers.rst Normal file
View File

@ -0,0 +1,242 @@
Providers
=========
Providers are strategies of accessing objects.
All providers are callable. They describe how particular objects will be
provided.
Instance providers & Injections
-------------------------------
*Instance* providers are providers that deal with object's creation and
initialization.
There are few *Instance* providers:
- ``NewInstance`` provider creates new instance of specified class on every
call.
- ``Singleton`` provider creates new instance of specified class on first
call and returns same instance on every next call.
.. code-block:: python
"""`NewInstance` and `Singleton` providers example."""
from objects.providers import NewInstance
from objects.providers import Singleton
# NewInstance provider creates 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
assert isinstance(object_1, object) and isinstance(object_2, object)
# Singleton provider creates new instance of specified class on first call
# and returns 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
assert isinstance(object_1, object) and isinstance(object_2, object)
Objects can take dependencies in various forms. Some objects take init
arguments, other are using attributes or methods to be initialized. It affects
how such objects need to be created and initialized, and that is the place
where **Injections** need to be used.
In terms of computer science, **Injection** of dependency is a way how
dependency can be coupled with dependent object.
In terms of **Objects**, **Injection** is an instruction how to provide
dependency for the particular object.
Every Python object could be an injection's value. Special case is an **Objects**
provider as an injection's value. In such case, injection value is a result of
injectable provider call (every time injection is done).
There are several types of injections. Below is a description of how they are
used by instance providers:
- ``KwArg`` - is injected in object's ``__init__()`` method in time of
object's initialization via keyword argument.
- ``Attribute`` - is injected into object's attribute (not class attribute)
after object's initialization.
- ``Method`` - is injected into object method's call after objects
initialization.
Example:
.. code-block:: python
"""`NewInstance` and `Singleton` providers with injections 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').fetchone()[0]
# 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))
# 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 is database()
assert object_a_1.get_one() == object_a_2.get_one() == 1
Static providers
----------------
Static providers are family of providers that returns its values "as is".
There are four of static providers: ``Class``, ``Object``, ``Function`` and
``Value``. All of them has the same behaviour, but usage of anyone is
predicted by readability and providable object's type.
.. 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
cls_provider = Class(object)
assert cls_provider() is object
object_provider = Object(object())
assert isinstance(object_provider(), object)
function_provider = Function(len)
assert function_provider() is len
value_provider = Value(123)
assert value_provider() == 123
Callable provider
-----------------
External dependency provider
----------------------------
Config provider
---------------
Providers delegation
--------------------
Overriding of providers
-----------------------
Any provider can be overridden by another provider.
Example:
.. code-block:: python
"""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

View File

@ -7,7 +7,8 @@ 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

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -4,8 +4,7 @@ from objects.providers import NewInstance
from objects.providers import Singleton
# NewInstance provider will create new instance of specified class
# on every call.
# NewInstance provider creates new instance of specified class on every call.
new_object = NewInstance(object)
object_1 = new_object()
@ -13,8 +12,8 @@ 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.
# Singleton provider creates new instance of specified class on first call
# and returns same instance on every next call.
single_object = Singleton(object)
single_object_1 = single_object()

View File

@ -0,0 +1,24 @@
"""`NewInstance` and `Singleton` providers example."""
from objects.providers import NewInstance
from objects.providers import Singleton
# NewInstance provider creates 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
assert isinstance(object_1, object) and isinstance(object_2, object)
# Singleton provider creates new instance of specified class on first call
# and returns 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
assert isinstance(object_1, object) and isinstance(object_2, object)

View File

@ -1,4 +1,4 @@
"""`KwArg` and `Attribute` injections example."""
"""`NewInstance` and `Singleton` providers with injections example."""
import sqlite3
@ -41,5 +41,5 @@ 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.database is object_a_2.database is database()
assert object_a_1.get_one() == object_a_2.get_one() == 1

View File

@ -0,0 +1,19 @@
"""Static providers example."""
from objects.providers import Class
from objects.providers import Object
from objects.providers import Function
from objects.providers import Value
cls_provider = Class(object)
assert cls_provider() is object
object_provider = Object(object())
assert isinstance(object_provider(), object)
function_provider = Function(len)
assert function_provider() is len
value_provider = Value(123)
assert value_provider() == 123

View File

@ -4,7 +4,6 @@ Dependency management tool for Python projects.
"""
from .catalog import AbstractCatalog
from .catalog import override
from .providers import Provider
from .providers import Delegate
@ -22,11 +21,13 @@ 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',
@ -46,5 +47,9 @@ __all__ = ('AbstractCatalog',
'Attribute',
'Method',
# Decorators
'override',
'inject',
# Errors
'Error')

View File

@ -47,12 +47,3 @@ class AbstractCatalog(object):
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

34
objects/decorators.py Normal file
View File

@ -0,0 +1,34 @@
"""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):
"""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

View File

@ -1,9 +1,6 @@
"""Injections module."""
from six import wraps
from .utils import is_provider
from .utils import ensure_is_injection
class Injection(object):
@ -45,23 +42,3 @@ 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

View File

@ -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
@ -50,16 +49,3 @@ class CatalogTests(unittest.TestCase):
"""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)
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)

114
tests/test_decorators.py Normal file
View File

@ -0,0 +1,114 @@
"""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 NewInstance
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 = 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)

View File

@ -6,12 +6,9 @@ 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
class InjectionTests(unittest.TestCase):
@ -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)