0.0.4 version

This commit is contained in:
Roman Mogilatov 2015-01-11 18:10:11 +02:00
parent 028e5e9ef7
commit 143ba92b0a
9 changed files with 263 additions and 195 deletions

129
README.md
View File

@ -48,36 +48,75 @@ class AppCatalog(Catalog):
""" :type: (objects.Provider) -> ObjectB """ """ :type: (objects.Provider) -> ObjectB """
# Catalog injection into consumer class. # Catalog static provides.
a1, a2 = AppCatalog.object_a(), AppCatalog.object_a()
b1, b2 = AppCatalog.object_b(), AppCatalog.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 AppCatalog.database()
# Dependency injection (The Python Way) into class.
class Consumer(object): class Consumer(object):
catalog = AppCatalog(AppCatalog.object_a,
dependencies = AppCatalog(AppCatalog.object_a,
AppCatalog.object_b) AppCatalog.object_b)
def return_a_b(self): def test(self):
return (self.catalog.object_a(), a1 = self.dependencies.object_a()
self.catalog.object_b()) a2 = self.dependencies.object_a()
a1, b1 = Consumer().return_a_b() b1 = self.dependencies.object_b()
b2 = self.dependencies.object_b()
# Catalog static provides.
a2 = AppCatalog.object_a()
b2 = AppCatalog.object_b()
# Some asserts. # Some asserts.
assert a1 is not a2 assert a1 is not a2
assert b1 is not b2 assert b1 is not b2
assert a1.db is a2.db is b1.db is b2.db assert a1.db is a2.db is b1.db is b2.db
try:
self.dependencies.database()
except AttributeError:
pass
else:
raise Exception('Database is not listed as a dependency')
Consumer().test()
# Dependency injection (The Python Way) into a callback.
def consumer_callback(dependencies=AppCatalog(AppCatalog.object_a,
AppCatalog.object_b)):
a1 = dependencies.object_a()
a2 = dependencies.object_a()
b1 = dependencies.object_b()
b2 = dependencies.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
try:
dependencies.database()
except AttributeError:
pass
else:
raise Exception('Database is not listed as a dependency')
``` ```
Example of injections using objects.catalog: Example of overriding object providers:
```python ```python
""" """
Concept example of objects injections. Concept example of objects overrides.
""" """
from objects import Catalog, Singleton, NewInstance, InitArg, Attribute, inject
from objects import Catalog, Singleton, NewInstance, InitArg, Attribute, overrides
import sqlite3 import sqlite3
@ -87,6 +126,10 @@ class ObjectA(object):
self.db = db self.db = db
class ObjectAMock(ObjectA):
pass
# Catalog of objects providers. # Catalog of objects providers.
class AppCatalog(Catalog): class AppCatalog(Catalog):
""" """
@ -103,57 +146,25 @@ class AppCatalog(Catalog):
""" :type: (objects.Provider) -> ObjectA """ """ :type: (objects.Provider) -> ObjectA """
# Class attributes injections. # Overriding AppCatalog by SandboxCatalog with some mocks.
@inject(Attribute('a', AppCatalog.object_a)) @overrides(AppCatalog)
@inject(Attribute('database', AppCatalog.database)) class SandboxCatalog(AppCatalog):
class Consumer(object):
""" """
Some consumer class with database dependency via attribute. Sandbox objects catalog with some mocks.
""" """
a = None object_a = NewInstance(ObjectAMock,
InitArg('db', AppCatalog.database))
""" :type: (objects.Provider) -> ObjectA """ """ :type: (objects.Provider) -> ObjectA """
database = None
""" :type: (objects.Provider) -> sqlite3.Connection """
def tests(self): # Catalog static provides.
a1, a2 = self.a(), self.a() a1 = AppCatalog.object_a()
a2 = AppCatalog.object_a()
# Some asserts.
assert isinstance(a1, ObjectAMock)
assert isinstance(a2, ObjectAMock)
assert a1 is not a2 assert a1 is not a2
assert a1.db is a2.db is self.database() assert a1.db is a2.db is AppCatalog.database()
consumer = Consumer()
consumer.tests()
# Class __init__ injections.
@inject(InitArg('a1', AppCatalog.object_a))
@inject(InitArg('a2', AppCatalog.object_a))
@inject(InitArg('database', AppCatalog.database))
class ConsumerWithInitArg(object):
"""
Some consumer class with database dependency via init arg.
"""
def __init__(self, a1, a2, database):
"""
Initializer.
:param a1: ObjectA
:param a2: ObjectA
:param database: sqlite3.Connection
"""
self.a1 = a1
self.a2 = a2
self.database = database
def tests(self):
assert self.a1 is not self.a2
assert self.a1.db is self.a2.db is self.database
consumer = ConsumerWithInitArg()
consumer.tests()
``` ```

View File

@ -1 +1 @@
0.0.3 0.0.4

View File

@ -39,23 +39,61 @@ class AppCatalog(Catalog):
""" :type: (objects.Provider) -> ObjectB """ """ :type: (objects.Provider) -> ObjectB """
# Catalog injection into consumer class. # Catalog static provides.
a1, a2 = AppCatalog.object_a(), AppCatalog.object_a()
b1, b2 = AppCatalog.object_b(), AppCatalog.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 AppCatalog.database()
# Dependency injection (The Python Way) into class.
class Consumer(object): class Consumer(object):
catalog = AppCatalog(AppCatalog.object_a,
dependencies = AppCatalog(AppCatalog.object_a,
AppCatalog.object_b) AppCatalog.object_b)
def return_a_b(self): def test(self):
return (self.catalog.object_a(), a1 = self.dependencies.object_a()
self.catalog.object_b()) a2 = self.dependencies.object_a()
a1, b1 = Consumer().return_a_b() b1 = self.dependencies.object_b()
b2 = self.dependencies.object_b()
# Catalog static provides.
a2 = AppCatalog.object_a()
b2 = AppCatalog.object_b()
# Some asserts. # Some asserts.
assert a1 is not a2 assert a1 is not a2
assert b1 is not b2 assert b1 is not b2
assert a1.db is a2.db is b1.db is b2.db assert a1.db is a2.db is b1.db is b2.db
try:
self.dependencies.database()
except AttributeError:
pass
else:
raise Exception('Database is not listed as a dependency')
Consumer().test()
# Dependency injection (The Python Way) into a callback.
def consumer_callback(dependencies=AppCatalog(AppCatalog.object_a,
AppCatalog.object_b)):
a1 = dependencies.object_a()
a2 = dependencies.object_a()
b1 = dependencies.object_b()
b2 = dependencies.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
try:
dependencies.database()
except AttributeError:
pass
else:
raise Exception('Database is not listed as a dependency')

View File

@ -1,83 +0,0 @@
"""
Concept example of objects injections.
"""
from objects import Catalog, Singleton, NewInstance, InitArg, Attribute, inject
import sqlite3
# Some example class.
class ObjectA(object):
def __init__(self, db):
self.db = db
# Catalog of objects providers.
class AppCatalog(Catalog):
"""
Objects catalog.
"""
database = Singleton(sqlite3.Connection,
InitArg('database', ':memory:'),
Attribute('row_factory', sqlite3.Row))
""" :type: (objects.Provider) -> sqlite3.Connection """
object_a = NewInstance(ObjectA,
InitArg('db', database))
""" :type: (objects.Provider) -> ObjectA """
# Class attributes injections.
@inject(Attribute('a', AppCatalog.object_a))
@inject(Attribute('database', AppCatalog.database))
class Consumer(object):
"""
Some consumer class with database dependency via attribute.
"""
a = None
""" :type: (objects.Provider) -> ObjectA """
database = None
""" :type: (objects.Provider) -> sqlite3.Connection """
def tests(self):
a1, a2 = self.a(), self.a()
assert a1 is not a2
assert a1.db is a2.db is self.database()
consumer = Consumer()
consumer.tests()
# Class __init__ injections.
@inject(InitArg('a1', AppCatalog.object_a))
@inject(InitArg('a2', AppCatalog.object_a))
@inject(InitArg('database', AppCatalog.database))
class ConsumerWithInitArg(object):
"""
Some consumer class with database dependency via init arg.
"""
def __init__(self, a1, a2, database):
"""
Initializer.
:param a1: ObjectA
:param a2: ObjectA
:param database: sqlite3.Connection
"""
self.a1 = a1
self.a2 = a2
self.database = database
def tests(self):
assert self.a1 is not self.a2
assert self.a1.db is self.a2.db is self.database
consumer = ConsumerWithInitArg()
consumer.tests()

56
examples/overrides.py Normal file
View File

@ -0,0 +1,56 @@
"""
Concept example of objects overrides.
"""
from objects import Catalog, Singleton, NewInstance, InitArg, Attribute, overrides
import sqlite3
# Some example class.
class ObjectA(object):
def __init__(self, db):
self.db = db
class ObjectAMock(ObjectA):
pass
# Catalog of objects providers.
class AppCatalog(Catalog):
"""
Objects catalog.
"""
database = Singleton(sqlite3.Connection,
InitArg('database', ':memory:'),
Attribute('row_factory', sqlite3.Row))
""" :type: (objects.Provider) -> sqlite3.Connection """
object_a = NewInstance(ObjectA,
InitArg('db', database))
""" :type: (objects.Provider) -> ObjectA """
# Overriding AppCatalog by SandboxCatalog with some mocks.
@overrides(AppCatalog)
class SandboxCatalog(AppCatalog):
"""
Sandbox objects catalog with some mocks.
"""
object_a = NewInstance(ObjectAMock,
InitArg('db', AppCatalog.database))
""" :type: (objects.Provider) -> ObjectA """
# Catalog static provides.
a1 = AppCatalog.object_a()
a2 = AppCatalog.object_a()
# Some asserts.
assert isinstance(a1, ObjectAMock)
assert isinstance(a2, ObjectAMock)
assert a1 is not a2
assert a1.db is a2.db is AppCatalog.database()

View File

@ -2,17 +2,17 @@
`Objects` library. `Objects` library.
""" """
from .catalog import Catalog from .catalog import Catalog, overrides
from .providers import (Provider, NewInstance, Singleton, Class, Object, from .providers import (Provider, NewInstance, Singleton, Class, Object,
Function, Value) Function, Value)
from .injections import InitArg, Attribute, Method, inject from .injections import InitArg, Attribute, Method, uses
__all__ = ['Catalog', __all__ = ['Catalog', 'overrides',
# Providers # Providers
'Provider', 'NewInstance', 'Singleton', 'Class', 'Provider', 'NewInstance', 'Singleton', 'Class',
'Object', 'Function', 'Value', 'Object', 'Function', 'Value',
# Injections # Injections
'InitArg', 'Attribute', 'Method', 'inject'] 'InitArg', 'Attribute', 'Method', 'uses']

View File

@ -14,19 +14,58 @@ class Catalog(object):
""" """
Initializer. Initializer.
""" """
self._clean_unused_providers(used_providers) self.__used_providers__ = set(used_providers)
def _clean_unused_providers(self, used_providers): def __getattribute__(self, item):
""" """
Sets every catalog's provider in None except of `used_providers` list. Returns providers.
:param list|tuple|set used_providers: :param item:
:return: :return:
""" """
used_providers = set(used_providers) attribute = super(Catalog, self).__getattribute__(item)
for attribute_name in set(dir(self.__class__)) - set(dir(Catalog)): if item in ('__used_providers__',):
provider = getattr(self, attribute_name) return attribute
if attribute not in self.__used_providers__:
raise AttributeError('Provider \'{}\' is not listed in '
'dependencies'.format(item))
return attribute
@classmethod
def __all_providers__(cls):
"""
Returns set of all class providers.
"""
providers = set()
for attr_name in set(dir(cls)) - set(dir(Catalog)):
provider = getattr(cls, attr_name)
if not isinstance(provider, Provider): if not isinstance(provider, Provider):
continue continue
if provider not in used_providers: providers.add((attr_name, provider))
setattr(self, attribute_name, None) return providers
@classmethod
def __override___(cls, overriding):
"""
Overrides current catalog providers by overriding catalog providers.
:param overriding: Catalog
"""
overriden = overriding.__all_providers__() - cls.__all_providers__()
for name, provider in overriden:
overridden_provider = getattr(cls, name)
overridden_provider.__override__(provider)
def overrides(catalog):
"""
Catalog overriding decorator.
:param catalog:
:return:
"""
def decorator(overriding_catalog):
catalog.__override___(overriding_catalog)
return overriding_catalog
return decorator

View File

@ -46,25 +46,12 @@ class Method(Injection):
""" """
def inject(injection): def uses(provider):
""" """
Injection decorator. Providers usage decorator.
""" """
def decorator(callback_or_cls): def decorator(cls):
if isclass(callback_or_cls): catalog = getattr(cls, 'catalog')
cls = callback_or_cls # catalog.__add__provider__(provider)
if isinstance(injection, Attribute):
setattr(cls, injection.name, injection.injectable)
elif isinstance(injection, InitArg):
cls.__init__ = decorator(cls.__init__)
return cls return cls
else:
callback = callback_or_cls
@wraps(callback)
def wrapped(*args, **kwargs):
if injection.name not in kwargs:
kwargs[injection.name] = injection.value
return callback(*args, **kwargs)
return wrapped
return decorator return decorator

View File

@ -11,6 +11,13 @@ class Provider(object):
""" """
__is_objects_provider__ = True __is_objects_provider__ = True
__overridden_by__ = list()
def __init__(self):
"""
Initializer.
"""
self.__overridden_by__ = list()
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
""" """
@ -18,6 +25,12 @@ class Provider(object):
""" """
raise NotImplementedError() raise NotImplementedError()
def __override__(self, provider):
"""
Overrides provider with another provider.
"""
self.__overridden_by__.append(provider)
def prepare_injections(injections): def prepare_injections(injections):
""" """
@ -48,11 +61,15 @@ class NewInstance(Provider):
self.init_injections = fetch_injections(injections, InitArg) self.init_injections = fetch_injections(injections, InitArg)
self.attribute_injections = fetch_injections(injections, Attribute) self.attribute_injections = fetch_injections(injections, Attribute)
self.method_injections = fetch_injections(injections, Method) self.method_injections = fetch_injections(injections, Method)
super(NewInstance, self).__init__()
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
""" """
Returns provided instance. Returns provided instance.
""" """
if self.__overridden_by__:
return self.__overridden_by__[-1].__call__(*args, **kwargs)
init_injections = prepare_injections(self.init_injections) init_injections = prepare_injections(self.init_injections)
init_injections = dict(init_injections) init_injections = dict(init_injections)
init_injections.update(kwargs) init_injections.update(kwargs)
@ -102,11 +119,14 @@ class _StaticProvider(Provider):
Initializer. Initializer.
""" """
self.provides = provides self.provides = provides
super(_StaticProvider, self).__init__()
def __call__(self): def __call__(self):
""" """
Returns provided instance. Returns provided instance.
""" """
if self.__overridden_by__:
return self.__overridden_by__[-1].__call__()
return self.provides return self.provides