diff --git a/README.md b/README.md index 0036c2ab..ce3a2617 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,16 @@ Python catalogs of objects providers Example: ```python -"""Concept example of Objects.""" +"""Concept example of `Objects`.""" -from objects import AbstractCatalog +from objects.catalog import AbstractCatalog from objects.providers import Singleton from objects.providers import NewInstance -from objects.injections import InitArg +from objects.injections import KwArg from objects.injections import Attribute +from objects.injections import inject import sqlite3 @@ -49,17 +50,17 @@ class Catalog(AbstractCatalog): """Catalog of objects providers.""" database = Singleton(sqlite3.Connection, - InitArg('database', ':memory:'), + KwArg('database', ':memory:'), Attribute('row_factory', sqlite3.Row)) """:type: (objects.Provider) -> sqlite3.Connection""" object_a = NewInstance(ObjectA, - InitArg('db', database)) + KwArg('db', database)) """:type: (objects.Provider) -> ObjectA""" object_b = NewInstance(ObjectB, - InitArg('a', object_a), - InitArg('db', database)) + KwArg('a', object_a), + KwArg('db', database)) """:type: (objects.Provider) -> ObjectB""" @@ -67,8 +68,18 @@ class Catalog(AbstractCatalog): 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() + + +# Example of inline injections. +@inject(KwArg('a', Catalog.object_a)) +@inject(KwArg('b', Catalog.object_b)) +@inject(KwArg('database', Catalog.database)) +def example(a, b, database): + assert a.db is b.db is database is Catalog.database() + + +example() ``` diff --git a/examples/callable_provider.py b/examples/callable_provider.py index 098b3ef0..feef362c 100644 --- a/examples/callable_provider.py +++ b/examples/callable_provider.py @@ -1,13 +1,12 @@ """Callable provider examples.""" -from objects import AbstractCatalog +from objects.catalog import AbstractCatalog from objects.providers import Singleton from objects.providers import Callable -from objects.injections import InitArg +from objects.injections import KwArg from objects.injections import Attribute -from objects.injections import Injection import sqlite3 @@ -22,12 +21,12 @@ class Catalog(AbstractCatalog): """Catalog of objects providers.""" database = Singleton(sqlite3.Connection, - InitArg('database', ':memory:'), + KwArg('database', ':memory:'), Attribute('row_factory', sqlite3.Row)) """:type: (objects.Provider) -> sqlite3.Connection""" consuming_function = Callable(consuming_function, - Injection('db', database)) + KwArg('db', database)) """:type: (objects.Provider) -> consuming_function""" diff --git a/examples/concept.py b/examples/concept.py index cb9353fb..6d934b48 100644 --- a/examples/concept.py +++ b/examples/concept.py @@ -1,12 +1,13 @@ """Concept example of `Objects`.""" -from objects import AbstractCatalog +from objects.catalog import AbstractCatalog from objects.providers import Singleton from objects.providers import NewInstance -from objects.injections import InitArg +from objects.injections import KwArg from objects.injections import Attribute +from objects.injections import inject import sqlite3 @@ -35,17 +36,17 @@ class Catalog(AbstractCatalog): """Catalog of objects providers.""" database = Singleton(sqlite3.Connection, - InitArg('database', ':memory:'), + KwArg('database', ':memory:'), Attribute('row_factory', sqlite3.Row)) """:type: (objects.Provider) -> sqlite3.Connection""" object_a = NewInstance(ObjectA, - InitArg('db', database)) + KwArg('db', database)) """:type: (objects.Provider) -> ObjectA""" object_b = NewInstance(ObjectB, - InitArg('a', object_a), - InitArg('db', database)) + KwArg('a', object_a), + KwArg('db', database)) """:type: (objects.Provider) -> ObjectB""" @@ -53,7 +54,17 @@ class Catalog(AbstractCatalog): 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() + + +# Example of inline injections. +@inject(KwArg('a', Catalog.object_a)) +@inject(KwArg('b', Catalog.object_b)) +@inject(KwArg('database', Catalog.database)) +def example(a, b, database): + assert a.db is b.db is database is Catalog.database() + + +example() diff --git a/examples/config_provider.py b/examples/config_provider.py index 1d94be9f..4bfabbd9 100644 --- a/examples/config_provider.py +++ b/examples/config_provider.py @@ -1,11 +1,11 @@ """Config provider examples.""" -from objects import AbstractCatalog +from objects.catalog import AbstractCatalog from objects.providers import Config from objects.providers import NewInstance -from objects.injections import InitArg +from objects.injections import KwArg class ObjectA(object): @@ -27,9 +27,9 @@ class Catalog(AbstractCatalog): """:type: (objects.Config)""" object_a = NewInstance(ObjectA, - InitArg('fee', config.FEE), - InitArg('price', config.PRICE), - InitArg('timezone', config.GLOBAL.TIMEZONE)) + KwArg('fee', config.FEE), + KwArg('price', config.PRICE), + KwArg('timezone', config.GLOBAL.TIMEZONE)) """:type: (objects.Provider) -> ObjectA""" diff --git a/examples/delegate.py b/examples/delegate.py index a2b49a09..df4a9068 100644 --- a/examples/delegate.py +++ b/examples/delegate.py @@ -1,11 +1,11 @@ """Provider delegation example.""" -from objects import AbstractCatalog +from objects.catalog import AbstractCatalog from objects.providers import Singleton from objects.providers import NewInstance -from objects.injections import InitArg +from objects.injections import KwArg from objects.injections import Attribute import sqlite3 @@ -34,16 +34,16 @@ class Catalog(AbstractCatalog): """Catalog of objects providers.""" database = Singleton(sqlite3.Connection, - InitArg('database', ':memory:'), + KwArg('database', ':memory:'), Attribute('row_factory', sqlite3.Row)) """:type: (objects.Provider) -> sqlite3.Connection""" object_a = NewInstance(ObjectA, - InitArg('db', database)) + KwArg('db', database)) """:type: (objects.Provider) -> ObjectA""" object_b = Singleton(ObjectB, - InitArg('a_provider', object_a.delegate())) + KwArg('a_provider', object_a.delegate())) """:type: (objects.Provider) -> ObjectB""" diff --git a/examples/external_dependency.py b/examples/external_dependency.py index a288d930..5c921f0f 100644 --- a/examples/external_dependency.py +++ b/examples/external_dependency.py @@ -1,12 +1,12 @@ """External dependency example.""" -from objects import AbstractCatalog +from objects.catalog import AbstractCatalog from objects.providers import Singleton from objects.providers import NewInstance from objects.providers import ExternalDependency -from objects.injections import InitArg +from objects.injections import KwArg from objects.injections import Attribute import sqlite3 @@ -39,18 +39,18 @@ class Catalog(AbstractCatalog): """:type: (objects.Provider) -> sqlite3.Connection""" object_a = NewInstance(ObjectA, - InitArg('db', database)) + KwArg('db', database)) """:type: (objects.Provider) -> ObjectA""" object_b = NewInstance(ObjectB, - InitArg('a', object_a), - InitArg('db', database)) + KwArg('a', object_a), + KwArg('db', database)) """:type: (objects.Provider) -> ObjectB""" # Satisfaction of external dependency. Catalog.database.override(Singleton(sqlite3.Connection, - InitArg('database', ':memory:'), + KwArg('database', ':memory:'), Attribute('row_factory', sqlite3.Row))) # Catalog static provides. diff --git a/examples/overrides.py b/examples/override.py similarity index 79% rename from examples/overrides.py rename to examples/override.py index c4df26eb..6cffbab8 100644 --- a/examples/overrides.py +++ b/examples/override.py @@ -1,12 +1,12 @@ """Override example.""" -from objects import AbstractCatalog -from objects import overrides +from objects.catalog import AbstractCatalog +from objects.catalog import override from objects.providers import Singleton from objects.providers import NewInstance -from objects.injections import InitArg +from objects.injections import KwArg from objects.injections import Attribute import sqlite3 @@ -31,22 +31,22 @@ class Catalog(AbstractCatalog): """Catalog of objects providers.""" database = Singleton(sqlite3.Connection, - InitArg('database', ':memory:'), + KwArg('database', ':memory:'), Attribute('row_factory', sqlite3.Row)) """:type: (objects.Provider) -> sqlite3.Connection""" object_a = NewInstance(ObjectA, - InitArg('db', database)) + KwArg('db', database)) """:type: (objects.Provider) -> ObjectA""" -@overrides(Catalog) +@override(Catalog) class SandboxCatalog(Catalog): """Sandbox objects catalog with some mocks that overrides Catalog.""" object_a = NewInstance(ObjectAMock, - InitArg('db', Catalog.database)) + KwArg('db', Catalog.database)) """:type: (objects.Provider) -> ObjectA""" diff --git a/examples/usage.py b/examples/usage.py index 7f57b3bb..afab5a6e 100644 --- a/examples/usage.py +++ b/examples/usage.py @@ -1,11 +1,11 @@ """Concept example of objects catalogs.""" -from objects import AbstractCatalog +from objects.catalog import AbstractCatalog from objects.providers import Singleton from objects.providers import NewInstance -from objects.injections import InitArg +from objects.injections import KwArg from objects.injections import Attribute from objects.errors import Error @@ -37,17 +37,17 @@ class Catalog(AbstractCatalog): """Catalog of objects providers.""" database = Singleton(sqlite3.Connection, - InitArg('database', ':memory:'), + KwArg('database', ':memory:'), Attribute('row_factory', sqlite3.Row)) """:type: (objects.Provider) -> sqlite3.Connection""" object_a = NewInstance(ObjectA, - InitArg('db', database)) + KwArg('db', database)) """:type: (objects.Provider) -> ObjectA""" object_b = NewInstance(ObjectB, - InitArg('a', object_a), - InitArg('db', database)) + KwArg('a', object_a), + KwArg('db', database)) """:type: (objects.Provider) -> ObjectB""" diff --git a/objects/__init__.py b/objects/__init__.py index f00f678c..1486e968 100644 --- a/objects/__init__.py +++ b/objects/__init__.py @@ -1,6 +1,7 @@ """Objects.""" -from .catalog import AbstractCatalog, overrides +from .catalog import AbstractCatalog +from .catalog import override from .providers import Provider from .providers import Delegate @@ -14,7 +15,7 @@ from .providers import Value from .providers import Callable from .providers import Config -from .injections import InitArg +from .injections import KwArg from .injections import Attribute from .injections import Method @@ -22,7 +23,7 @@ from .errors import Error __all__ = ('AbstractCatalog', - 'overrides', + 'override', # Providers 'Provider', @@ -38,7 +39,7 @@ __all__ = ('AbstractCatalog', 'Config', # Injections - 'InitArg', + 'KwArg', 'Attribute', 'Method', diff --git a/objects/catalog.py b/objects/catalog.py index ce8084c1..81d12bcf 100644 --- a/objects/catalog.py +++ b/objects/catalog.py @@ -49,7 +49,7 @@ class AbstractCatalog(object): overridden_provider.override(provider) -def overrides(catalog): +def override(catalog): """Catalog overriding decorator.""" def decorator(overriding_catalog): """Overriding decorator.""" diff --git a/objects/injections.py b/objects/injections.py index 63bdbf8d..4dd57ee9 100644 --- a/objects/injections.py +++ b/objects/injections.py @@ -1,6 +1,7 @@ """Injections module.""" from .utils import is_provider +from .utils import ensure_is_injection class Injection(object): @@ -23,12 +24,12 @@ class Injection(object): return self.injectable -class InitArg(Injection): +class KwArg(Injection): - """Init argument injection.""" + """Keyword argument injection.""" - __IS_OBJECTS_INIT_ARG_INJECTION__ = True - __slots__ = ('__IS_OBJECTS_INIT_ARG_INJECTION__',) + __IS_OBJECTS_KWARG_INJECTION__ = True + __slots__ = ('__IS_OBJECTS_KWARG_INJECTION__',) class Attribute(Injection): @@ -45,3 +46,22 @@ class Method(Injection): __IS_OBJECTS_METHOD_INJECTION__ = True __slots__ = ('__IS_OBJECTS_METHOD_INJECTION__',) + + +def inject(injection): + """Inject decorator. + + :type injection: Injection + :return: (callable) -> (callable) + """ + injection = ensure_is_injection(injection) + + def decorator(callback): + """Decorator.""" + def decorated(*args, **kwargs): + """Decorated.""" + if injection.name not in kwargs: + kwargs[injection.name] = injection.value + return callback(*args, **kwargs) + return decorated + return decorator diff --git a/objects/providers.py b/objects/providers.py index c478120e..c2c683f1 100644 --- a/objects/providers.py +++ b/objects/providers.py @@ -3,8 +3,7 @@ from inspect import isclass from .utils import ensure_is_provider -from .utils import is_injection -from .utils import is_init_arg_injection +from .utils import is_kwarg_injection from .utils import is_attribute_injection from .utils import is_method_injection @@ -77,7 +76,7 @@ class NewInstance(Provider): New instance providers will create and return new instance on every call. """ - __slots__ = ('provides', 'init_args', 'attributes', 'methods') + __slots__ = ('provides', 'kwargs', 'attributes', 'methods') def __init__(self, provides, *injections): """Initializer.""" @@ -85,9 +84,9 @@ class NewInstance(Provider): raise Error('NewInstance provider expects to get class, ' + 'got {0} instead'.format(str(provides))) self.provides = provides - self.init_args = tuple((injection - for injection in injections - if is_init_arg_injection(injection))) + 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))) @@ -102,7 +101,7 @@ class NewInstance(Provider): return self.last_overriding(*args, **kwargs) init_kwargs = dict(((injection.name, injection.value) - for injection in self.init_args)) + for injection in self.kwargs)) init_kwargs.update(kwargs) instance = self.provides(*args, **init_kwargs) @@ -228,7 +227,7 @@ class Callable(Provider): self.callback = callback self.injections = tuple((injection for injection in injections - if is_injection(injection))) + if is_kwarg_injection(injection))) super(Callable, self).__init__() def __call__(self, *args, **kwargs): diff --git a/objects/utils.py b/objects/utils.py index 577bfb91..89319021 100644 --- a/objects/utils.py +++ b/objects/utils.py @@ -25,10 +25,18 @@ def is_injection(instance): hasattr(instance, '__IS_OBJECTS_INJECTION__')) -def is_init_arg_injection(instance): - """Check if instance is init arg injection instance.""" +def ensure_is_injection(instance): + """Check if instance is injection instance, otherwise raise and error.""" + if not is_injection(instance): + raise Error('Expected injection instance, ' + 'got {0}'.format(str(instance))) + return 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_INIT_ARG_INJECTION__')) + hasattr(instance, '__IS_OBJECTS_KWARG_INJECTION__')) def is_attribute_injection(instance): diff --git a/tests/test_catalog.py b/tests/test_catalog.py index bc055e4d..065e56f5 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -3,7 +3,7 @@ import unittest2 as unittest from objects.catalog import AbstractCatalog -from objects.catalog import overrides +from objects.catalog import override from objects.providers import Object from objects.providers import Value @@ -53,7 +53,7 @@ class CatalogTests(unittest.TestCase): def test_overriding(self): """Test catalog overriding with another catalog.""" - @overrides(self.Catalog) + @override(self.Catalog) class OverridingCatalog(self.Catalog): """Overriding catalog.""" diff --git a/tests/test_injections.py b/tests/test_injections.py index 6cb2763c..b29b1cd0 100644 --- a/tests/test_injections.py +++ b/tests/test_injections.py @@ -3,7 +3,7 @@ import unittest2 as unittest from objects.injections import Injection -from objects.injections import InitArg +from objects.injections import KwArg from objects.injections import Attribute from objects.injections import Method @@ -36,8 +36,8 @@ class InitArgTests(unittest.TestCase): """Init arg injection test cases.""" def test_init(self): - """Test InitArg creation and initialization.""" - injection = InitArg('some_arg_name', 'some_value') + """Test KwArg creation and initialization.""" + injection = KwArg('some_arg_name', 'some_value') self.assertEqual(injection.name, 'some_arg_name') self.assertEqual(injection.injectable, 'some_value') diff --git a/tests/test_providers.py b/tests/test_providers.py index cdef019c..1973d8b2 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -14,8 +14,7 @@ from objects.providers import Value from objects.providers import Callable from objects.providers import Config -from objects.injections import Injection -from objects.injections import InitArg +from objects.injections import KwArg from objects.injections import Attribute from objects.injections import Method @@ -184,8 +183,8 @@ class NewInstanceTests(unittest.TestCase): def test_call_with_init_args(self): """Test creation of new instances with init args injections.""" provider = NewInstance(self.Example, - InitArg('init_arg1', 'i1'), - InitArg('init_arg2', 'i2')) + KwArg('init_arg1', 'i1'), + KwArg('init_arg2', 'i2')) instance1 = provider() instance2 = provider() @@ -249,7 +248,7 @@ class NewInstanceTests(unittest.TestCase): def test_call_with_context_kwargs(self): """Test creation of new instances with context kwargs.""" provider = NewInstance(self.Example, - InitArg('init_arg1', 1)) + KwArg('init_arg1', 1)) instance1 = provider(init_arg2=22) self.assertEqual(instance1.init_arg1, 1) @@ -399,9 +398,9 @@ class CallableTests(unittest.TestCase): def setUp(self): """Set test cases environment up.""" self.provider = Callable(self.example, - Injection('arg1', 'a1'), - Injection('arg2', 'a2'), - Injection('arg3', 'a3')) + KwArg('arg1', 'a1'), + KwArg('arg2', 'a2'), + KwArg('arg3', 'a3')) def test_init_with_not_callable(self): """Test creation of provider with not callable.""" @@ -418,7 +417,7 @@ class CallableTests(unittest.TestCase): def test_call_with_args(self): """Test provider call with kwargs priority.""" provider = Callable(self.example, - Injection('arg3', 'a3')) + KwArg('arg3', 'a3')) self.assertEqual(provider(1, 2), (1, 2, 'a3')) def test_call_with_kwargs_priority(self): diff --git a/tests/test_utils.py b/tests/test_utils.py index d4f15ac6..f4b038ce 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,14 +5,15 @@ import unittest2 as unittest from objects.utils import is_provider from objects.utils import ensure_is_provider from objects.utils import is_injection -from objects.utils import is_init_arg_injection +from objects.utils import ensure_is_injection +from objects.utils import is_kwarg_injection from objects.utils import is_attribute_injection from objects.utils import is_method_injection from objects.providers import Provider from objects.injections import Injection -from objects.injections import InitArg +from objects.injections import KwArg from objects.injections import Attribute from objects.injections import Method @@ -72,7 +73,7 @@ class IsInjectionTests(unittest.TestCase): def test_with_subclass_instances(self): """Test with subclass instances.""" - self.assertTrue(is_injection(InitArg('name', 'value'))) + self.assertTrue(is_injection(KwArg('name', 'value'))) self.assertTrue(is_injection(Attribute('name', 'value'))) self.assertTrue(is_injection(Method('name', 'value'))) @@ -89,29 +90,51 @@ class IsInjectionTests(unittest.TestCase): self.assertFalse(is_injection(object())) -class IsInitArgInjectionTests(unittest.TestCase): +class EnsureIsInjectionTests(unittest.TestCase): - """`is_init_arg_injection()` test cases.""" + """`ensure_is_injection` test cases.""" def test_with_instance(self): """Test with instance.""" - self.assertTrue(is_init_arg_injection(InitArg('name', 'value'))) + injection = Injection('name', 'value') + self.assertIs(ensure_is_injection(injection), injection) def test_with_class(self): """Test with class.""" - self.assertFalse(is_init_arg_injection(InitArg)) - - def test_with_parent_class(self): - """Test with parent class.""" - self.assertFalse(is_init_arg_injection(Injection)) + self.assertRaises(Error, ensure_is_injection, Injection) def test_with_string(self): """Test with string.""" - self.assertFalse(is_init_arg_injection('some_string')) + self.assertRaises(Error, ensure_is_injection, 'some_string') def test_with_object(self): """Test with object.""" - self.assertFalse(is_init_arg_injection(object())) + self.assertRaises(Error, ensure_is_injection, object()) + + +class IsKwArgInjectionTests(unittest.TestCase): + + """`is_kwarg_injection()` test cases.""" + + def test_with_instance(self): + """Test with instance.""" + self.assertTrue(is_kwarg_injection(KwArg('name', 'value'))) + + def test_with_class(self): + """Test with class.""" + self.assertFalse(is_kwarg_injection(KwArg)) + + def test_with_parent_class(self): + """Test with parent class.""" + self.assertFalse(is_kwarg_injection(Injection)) + + def test_with_string(self): + """Test with string.""" + self.assertFalse(is_kwarg_injection('some_string')) + + def test_with_object(self): + """Test with object.""" + self.assertFalse(is_kwarg_injection(object())) class IsAttributeInjectionTests(unittest.TestCase):