diff --git a/README.md b/README.md index ad2205e0..43563f30 100644 --- a/README.md +++ b/README.md @@ -48,36 +48,75 @@ class AppCatalog(Catalog): """ :type: (objects.Provider) -> ObjectB """ -# Catalog injection into consumer class. -class Consumer(object): - catalog = AppCatalog(AppCatalog.object_a, - AppCatalog.object_b) - - def return_a_b(self): - return (self.catalog.object_a(), - self.catalog.object_b()) - -a1, b1 = Consumer().return_a_b() - - # Catalog static provides. -a2 = AppCatalog.object_a() -b2 = AppCatalog.object_b() +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 +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): + + dependencies = AppCatalog(AppCatalog.object_a, + AppCatalog.object_b) + + def test(self): + a1 = self.dependencies.object_a() + a2 = self.dependencies.object_a() + + b1 = self.dependencies.object_b() + b2 = self.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: + 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 """ -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 @@ -87,6 +126,10 @@ class ObjectA(object): self.db = db +class ObjectAMock(ObjectA): + pass + + # Catalog of objects providers. class AppCatalog(Catalog): """ @@ -103,57 +146,25 @@ class AppCatalog(Catalog): """ :type: (objects.Provider) -> ObjectA """ -# Class attributes injections. -@inject(Attribute('a', AppCatalog.object_a)) -@inject(Attribute('database', AppCatalog.database)) -class Consumer(object): +# Overriding AppCatalog by SandboxCatalog with some mocks. +@overrides(AppCatalog) +class SandboxCatalog(AppCatalog): """ - 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 """ - database = None - """ :type: (objects.Provider) -> sqlite3.Connection """ - def tests(self): - a1, a2 = self.a(), self.a() +# Catalog static provides. +a1 = AppCatalog.object_a() +a2 = AppCatalog.object_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() +# Some asserts. +assert isinstance(a1, ObjectAMock) +assert isinstance(a2, ObjectAMock) +assert a1 is not a2 +assert a1.db is a2.db is AppCatalog.database() ``` diff --git a/VERSION b/VERSION index bcab45af..81340c7e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.3 +0.0.4 diff --git a/examples/concept.py b/examples/concept.py index ad41daca..8cff5230 100644 --- a/examples/concept.py +++ b/examples/concept.py @@ -39,23 +39,61 @@ class AppCatalog(Catalog): """ :type: (objects.Provider) -> ObjectB """ -# Catalog injection into consumer class. -class Consumer(object): - catalog = AppCatalog(AppCatalog.object_a, - AppCatalog.object_b) - - def return_a_b(self): - return (self.catalog.object_a(), - self.catalog.object_b()) - -a1, b1 = Consumer().return_a_b() - - # Catalog static provides. -a2 = AppCatalog.object_a() -b2 = AppCatalog.object_b() +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 +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): + + dependencies = AppCatalog(AppCatalog.object_a, + AppCatalog.object_b) + + def test(self): + a1 = self.dependencies.object_a() + a2 = self.dependencies.object_a() + + b1 = self.dependencies.object_b() + b2 = self.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: + 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') diff --git a/examples/injections.py b/examples/injections.py deleted file mode 100644 index 8ee78af6..00000000 --- a/examples/injections.py +++ /dev/null @@ -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() diff --git a/examples/overrides.py b/examples/overrides.py new file mode 100644 index 00000000..d884e1ea --- /dev/null +++ b/examples/overrides.py @@ -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() diff --git a/objects/__init__.py b/objects/__init__.py index 5839e635..7618ee0a 100644 --- a/objects/__init__.py +++ b/objects/__init__.py @@ -2,17 +2,17 @@ `Objects` library. """ -from .catalog import Catalog +from .catalog import Catalog, overrides from .providers import (Provider, NewInstance, Singleton, Class, Object, Function, Value) -from .injections import InitArg, Attribute, Method, inject +from .injections import InitArg, Attribute, Method, uses -__all__ = ['Catalog', +__all__ = ['Catalog', 'overrides', # Providers 'Provider', 'NewInstance', 'Singleton', 'Class', 'Object', 'Function', 'Value', # Injections - 'InitArg', 'Attribute', 'Method', 'inject'] + 'InitArg', 'Attribute', 'Method', 'uses'] diff --git a/objects/catalog.py b/objects/catalog.py index 23b0520d..d008fa3f 100644 --- a/objects/catalog.py +++ b/objects/catalog.py @@ -14,19 +14,58 @@ class Catalog(object): """ 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: """ - used_providers = set(used_providers) - for attribute_name in set(dir(self.__class__)) - set(dir(Catalog)): - provider = getattr(self, attribute_name) + attribute = super(Catalog, self).__getattribute__(item) + if item in ('__used_providers__',): + 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): continue - if provider not in used_providers: - setattr(self, attribute_name, None) + providers.add((attr_name, provider)) + 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 diff --git a/objects/injections.py b/objects/injections.py index a6daf8c4..14d51479 100644 --- a/objects/injections.py +++ b/objects/injections.py @@ -46,25 +46,12 @@ class Method(Injection): """ -def inject(injection): +def uses(provider): """ - Injection decorator. + Providers usage decorator. """ - def decorator(callback_or_cls): - if isclass(callback_or_cls): - cls = callback_or_cls - if isinstance(injection, Attribute): - setattr(cls, injection.name, injection.injectable) - elif isinstance(injection, InitArg): - cls.__init__ = decorator(cls.__init__) - 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 + def decorator(cls): + catalog = getattr(cls, 'catalog') + # catalog.__add__provider__(provider) + return cls return decorator diff --git a/objects/providers.py b/objects/providers.py index 287cd96c..af200116 100644 --- a/objects/providers.py +++ b/objects/providers.py @@ -11,6 +11,13 @@ class Provider(object): """ __is_objects_provider__ = True + __overridden_by__ = list() + + def __init__(self): + """ + Initializer. + """ + self.__overridden_by__ = list() def __call__(self, *args, **kwargs): """ @@ -18,6 +25,12 @@ class Provider(object): """ raise NotImplementedError() + def __override__(self, provider): + """ + Overrides provider with another provider. + """ + self.__overridden_by__.append(provider) + def prepare_injections(injections): """ @@ -48,11 +61,15 @@ class NewInstance(Provider): self.init_injections = fetch_injections(injections, InitArg) self.attribute_injections = fetch_injections(injections, Attribute) self.method_injections = fetch_injections(injections, Method) + super(NewInstance, self).__init__() def __call__(self, *args, **kwargs): """ Returns provided instance. """ + if self.__overridden_by__: + return self.__overridden_by__[-1].__call__(*args, **kwargs) + init_injections = prepare_injections(self.init_injections) init_injections = dict(init_injections) init_injections.update(kwargs) @@ -102,11 +119,14 @@ class _StaticProvider(Provider): Initializer. """ self.provides = provides + super(_StaticProvider, self).__init__() def __call__(self): """ Returns provided instance. """ + if self.__overridden_by__: + return self.__overridden_by__[-1].__call__() return self.provides