From 1cdf393f56d3f535ece162728d31394bbe98fee5 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 28 Jan 2015 00:21:31 +0200 Subject: [PATCH 1/7] adding scoped provider with examples --- examples/scoped_provider.py | 67 +++++++++++++++++++++++++++++++++++++ objects/providers.py | 54 ++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 examples/scoped_provider.py diff --git a/examples/scoped_provider.py b/examples/scoped_provider.py new file mode 100644 index 00000000..1a007bd0 --- /dev/null +++ b/examples/scoped_provider.py @@ -0,0 +1,67 @@ +""" +Scoped provider examples. +""" + +from objects import AbstractCatalog +from objects.providers import Singleton, Scoped +from objects.injections import InitArg, Attribute +import sqlite3 + + +class ObjectA(object): + def __init__(self, db): + self.db = db + + +# Catalog of objects providers. +class Catalog(AbstractCatalog): + """ + Objects catalog. + """ + + database = Singleton(sqlite3.Connection, + InitArg('database', ':memory:'), + Attribute('row_factory', sqlite3.Row)) + """ :type: (objects.Provider) -> sqlite3.Connection """ + + object_a = Scoped(ObjectA, + InitArg('db', database)) + """ :type: (objects.Provider) -> ObjectA """ + + +with Catalog.object_a as object_a_provider: + object_a1 = object_a_provider() + object_a2 = object_a_provider() + + assert object_a1 is object_a2 + assert object_a1.db is object_a2.db + +with Catalog.object_a as object_a_provider: + object_a3 = object_a_provider() + object_a4 = object_a_provider() + + assert object_a3 is object_a4 + assert object_a3.db is object_a4.db + + assert (object_a1 is not object_a3) and \ + (object_a1 is not object_a4) + assert (object_a2 is not object_a3) and \ + (object_a2 is not object_a4) + + +Catalog.object_a.in_scope() + +object_a5 = Catalog.object_a() +object_a6 = Catalog.object_a() + +assert object_a5 is object_a6 +assert object_a5.db is object_a6.db + +assert (object_a1 is not object_a3) and \ + (object_a1 is not object_a4) and \ + (object_a1 is not object_a5) and \ + (object_a1 is not object_a6) +assert (object_a2 is not object_a3) and \ + (object_a2 is not object_a4) and \ + (object_a2 is not object_a5) and \ + (object_a2 is not object_a6) diff --git a/objects/providers.py b/objects/providers.py index 2c5ef271..2d0fbdd1 100644 --- a/objects/providers.py +++ b/objects/providers.py @@ -108,6 +108,60 @@ class Singleton(NewInstance): self.instance = super(Singleton, self).__call__(*args, **kwargs) return self.instance + def _reset_instance(self): + """ + Resets instance. + """ + self.instance = None + + +class Scoped(Singleton): + """ + Scoped provider will create instance once for every scope and return it on every call. + """ + + def __init__(self, *args, **kwargs): + """ + Initializer. + """ + self.is_in_scope = None + super(Scoped, self).__init__(*args, **kwargs) + + def in_scope(self): + """ + Sets provider in "in scope" state. + """ + self.is_in_scope = True + self._reset_instance() + + def out_of_scope(self): + """ + Sets provider in "out of scope" state. + """ + self.is_in_scope = False + self._reset_instance() + + def __call__(self, *args, **kwargs): + """ + Returns provided instance. + """ + if not self.is_in_scope: + raise RuntimeError('Trying to provide {} while provider is not in scope'.format(self.provides)) + return super(Scoped, self).__call__(*args, **kwargs) + + def __enter__(self): + """ + With __enter__() implementation. Makes provider to be in scope. + """ + self.in_scope() + return self + + def __exit__(self, *_): + """ + With __exit__() implementation. Makes provider to be out of scope. + """ + self.out_of_scope() + class ExternalDependency(Provider): """ From 113f770634fb847a4ced0084daad46ead3cb26d0 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 28 Jan 2015 00:26:38 +0200 Subject: [PATCH 2/7] updating readme and examples --- README.md | 79 +++++++++++++++++++++++++++++++++ examples/concept.py | 1 + examples/external_dependency.py | 1 + examples/overrides.py | 1 + examples/scoped_provider.py | 4 ++ 5 files changed, 86 insertions(+) diff --git a/README.md b/README.md index fd8fdf1f..293e649e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Concept example of objects catalogs. from objects import AbstractCatalog from objects.providers import Singleton, NewInstance from objects.injections import InitArg, Attribute + import sqlite3 @@ -121,6 +122,7 @@ Concept example of objects overrides. from objects import AbstractCatalog, overrides from objects.providers import Singleton, NewInstance from objects.injections import InitArg, Attribute + import sqlite3 @@ -183,6 +185,7 @@ Concept example of objects catalogs. from objects import AbstractCatalog from objects.providers import Singleton, NewInstance, ExternalDependency from objects.injections import InitArg, Attribute + import sqlite3 @@ -231,3 +234,79 @@ 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 objects catalog with scoped provider: + +```python +""" +Scoped provider examples. +""" + +from objects import AbstractCatalog +from objects.providers import Singleton, Scoped +from objects.injections import InitArg, Attribute + +import sqlite3 + + +class ObjectA(object): + def __init__(self, db): + self.db = db + + +# Catalog of objects providers. +class Catalog(AbstractCatalog): + """ + Objects catalog. + """ + + database = Singleton(sqlite3.Connection, + InitArg('database', ':memory:'), + Attribute('row_factory', sqlite3.Row)) + """ :type: (objects.Provider) -> sqlite3.Connection """ + + object_a = Scoped(ObjectA, + InitArg('db', database)) + """ :type: (objects.Provider) -> ObjectA """ + + +# Making scope using `with` statement. +with Catalog.object_a as object_a_provider: + object_a1 = object_a_provider() + object_a2 = object_a_provider() + + assert object_a1 is object_a2 + assert object_a1.db is object_a2.db + +# Making another one scope using `with` statement. +with Catalog.object_a as object_a_provider: + object_a3 = object_a_provider() + object_a4 = object_a_provider() + + assert object_a3 is object_a4 + assert object_a3.db is object_a4.db + + assert (object_a1 is not object_a3) and \ + (object_a1 is not object_a4) + assert (object_a2 is not object_a3) and \ + (object_a2 is not object_a4) + + +# Making one more scope using provider methods. +Catalog.object_a.in_scope() + +object_a5 = Catalog.object_a() +object_a6 = Catalog.object_a() + +assert object_a5 is object_a6 +assert object_a5.db is object_a6.db + +assert (object_a1 is not object_a3) and \ + (object_a1 is not object_a4) and \ + (object_a1 is not object_a5) and \ + (object_a1 is not object_a6) +assert (object_a2 is not object_a3) and \ + (object_a2 is not object_a4) and \ + (object_a2 is not object_a5) and \ + (object_a2 is not object_a6) +``` diff --git a/examples/concept.py b/examples/concept.py index 34714fd1..f44eb8fe 100644 --- a/examples/concept.py +++ b/examples/concept.py @@ -5,6 +5,7 @@ Concept example of objects catalogs. from objects import AbstractCatalog from objects.providers import Singleton, NewInstance from objects.injections import InitArg, Attribute + import sqlite3 diff --git a/examples/external_dependency.py b/examples/external_dependency.py index a19e0c95..b5ae4cc4 100644 --- a/examples/external_dependency.py +++ b/examples/external_dependency.py @@ -5,6 +5,7 @@ Concept example of objects catalogs. from objects import AbstractCatalog from objects.providers import Singleton, NewInstance, ExternalDependency from objects.injections import InitArg, Attribute + import sqlite3 diff --git a/examples/overrides.py b/examples/overrides.py index 6a6d5ebb..242ec45a 100644 --- a/examples/overrides.py +++ b/examples/overrides.py @@ -6,6 +6,7 @@ Concept example of objects overrides. from objects import AbstractCatalog, overrides from objects.providers import Singleton, NewInstance from objects.injections import InitArg, Attribute + import sqlite3 diff --git a/examples/scoped_provider.py b/examples/scoped_provider.py index 1a007bd0..45dd602c 100644 --- a/examples/scoped_provider.py +++ b/examples/scoped_provider.py @@ -5,6 +5,7 @@ Scoped provider examples. from objects import AbstractCatalog from objects.providers import Singleton, Scoped from objects.injections import InitArg, Attribute + import sqlite3 @@ -29,6 +30,7 @@ class Catalog(AbstractCatalog): """ :type: (objects.Provider) -> ObjectA """ +# Making scope using `with` statement. with Catalog.object_a as object_a_provider: object_a1 = object_a_provider() object_a2 = object_a_provider() @@ -36,6 +38,7 @@ with Catalog.object_a as object_a_provider: assert object_a1 is object_a2 assert object_a1.db is object_a2.db +# Making another one scope using `with` statement. with Catalog.object_a as object_a_provider: object_a3 = object_a_provider() object_a4 = object_a_provider() @@ -49,6 +52,7 @@ with Catalog.object_a as object_a_provider: (object_a2 is not object_a4) +# Making one more scope using provider methods. Catalog.object_a.in_scope() object_a5 = Catalog.object_a() From 3786b052a908b0520edf09f1beefc8e9a5e7e164 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 28 Jan 2015 00:48:33 +0200 Subject: [PATCH 3/7] adding callable provider with examples --- README.md | 47 +++++++++++++++++++++++++++++++++++ examples/callable_provider.py | 42 +++++++++++++++++++++++++++++++ objects/providers.py | 30 +++++++++++++++++++++- 3 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 examples/callable_provider.py diff --git a/README.md b/README.md index 293e649e..7e604ec1 100644 --- a/README.md +++ b/README.md @@ -310,3 +310,50 @@ assert (object_a2 is not object_a3) and \ (object_a2 is not object_a5) and \ (object_a2 is not object_a6) ``` + +Example of objects catalog with callable provider: + +```python +""" +Callable provider examples. +""" + +from objects import AbstractCatalog +from objects.providers import Singleton, Callable +from objects.injections import Injection, InitArg, Attribute + +import sqlite3 + + +# Some example function. +def consuming_function(arg, db): + return arg, db + + +# Catalog of objects providers. +class Catalog(AbstractCatalog): + """ + Objects catalog. + """ + + database = Singleton(sqlite3.Connection, + InitArg('database', ':memory:'), + Attribute('row_factory', sqlite3.Row)) + """ :type: (objects.Provider) -> sqlite3.Connection """ + + consuming_function = Callable(consuming_function, + Injection('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/callable_provider.py b/examples/callable_provider.py new file mode 100644 index 00000000..3e037c81 --- /dev/null +++ b/examples/callable_provider.py @@ -0,0 +1,42 @@ +""" +Callable provider examples. +""" + +from objects import AbstractCatalog +from objects.providers import Singleton, Callable +from objects.injections import Injection, InitArg, Attribute + +import sqlite3 + + +# Some example function. +def consuming_function(arg, db): + return arg, db + + +# Catalog of objects providers. +class Catalog(AbstractCatalog): + """ + Objects catalog. + """ + + database = Singleton(sqlite3.Connection, + InitArg('database', ':memory:'), + Attribute('row_factory', sqlite3.Row)) + """ :type: (objects.Provider) -> sqlite3.Connection """ + + consuming_function = Callable(consuming_function, + Injection('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/objects/providers.py b/objects/providers.py index 2d0fbdd1..9b0a63dc 100644 --- a/objects/providers.py +++ b/objects/providers.py @@ -3,7 +3,7 @@ Standard providers. """ from collections import Iterable -from .injections import InitArg, Attribute, Method +from .injections import Injection, InitArg, Attribute, Method class Provider(object): @@ -247,3 +247,31 @@ class Value(_StaticProvider): """ Value provider provides value. """ + + +class Callable(Provider): + """ + Callable providers will provides callable calls with some predefined + dependencies injections. + """ + + def __init__(self, calls, *injections): + """ + Initializer. + """ + self.calls = calls + self.injections = fetch_injections(injections, Injection) + super(Callable, self).__init__() + + def __call__(self, *args, **kwargs): + """ + Returns provided instance. + """ + if self.__overridden_by__: + return self.__overridden_by__[-1].__call__(*args, **kwargs) + + injections = prepare_injections(self.injections) + injections = dict(injections) + injections.update(kwargs) + + return self.calls(*args, **injections) From 158b6fcd9e69aeb152bbfed270b176193f499e62 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 28 Jan 2015 13:08:54 +0200 Subject: [PATCH 4/7] Adding config provider --- README.md | 72 +++++++++++++++++++++++++++++++++++++ examples/config_provider.py | 66 ++++++++++++++++++++++++++++++++++ examples/overrides.py | 1 + objects/providers.py | 67 +++++++++++++++++++++++++++++++++- 4 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 examples/config_provider.py diff --git a/README.md b/README.md index 7e604ec1..78fb8a21 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ class ObjectA(object): self.db = db +# Mock of example class. class ObjectAMock(ObjectA): pass @@ -357,3 +358,74 @@ assert arg1 == 1 assert arg2 == 2 assert arg3 == 3 ``` + +Example of objects catalog with config provider: + +```python +""" +Config provider examples. +""" + +from objects import AbstractCatalog +from objects.providers import Config, NewInstance +from objects.injections import InitArg + + +# Some example class. +class ObjectA(object): + def __init__(self, setting_one, setting_two, setting_three): + self.setting_one = setting_one + self.setting_two = setting_two + self.setting_three = setting_three + + +# Catalog of objects providers. +class Catalog(AbstractCatalog): + """ + Objects catalog. + """ + + config = Config() + """ :type: (objects.Config) """ + + object_a = NewInstance(ObjectA, + InitArg('setting_one', config.SETTING_ONE), + InitArg('setting_two', config.SETTING_TWO), + InitArg('setting_three', config.GLOBAL.SETTING_THREE)) + """ :type: (objects.Provider) -> ObjectA """ + + +# Setting config value and making some tests. +Catalog.config.update_from({ + 'SETTING_ONE': 1, + 'SETTING_TWO': 2, + 'GLOBAL': { + 'SETTING_THREE': 3 + } +}) + +object_a1 = Catalog.object_a() + +assert object_a1.setting_one == 1 +assert object_a1.setting_two == 2 +assert object_a1.setting_three == 3 + +# Changing config value one more time and making some tests. +Catalog.config.update_from({ + 'SETTING_ONE': 11, + 'SETTING_TWO': 22, + 'GLOBAL': { + 'SETTING_THREE': 33 + } +}) + +object_a2 = Catalog.object_a() + +assert object_a2.setting_one == 11 +assert object_a2.setting_two == 22 +assert object_a2.setting_three == 33 + +assert object_a1.setting_one == 1 +assert object_a1.setting_two == 2 +assert object_a1.setting_three == 3 +``` diff --git a/examples/config_provider.py b/examples/config_provider.py new file mode 100644 index 00000000..599293b6 --- /dev/null +++ b/examples/config_provider.py @@ -0,0 +1,66 @@ +""" +Config provider examples. +""" + +from objects import AbstractCatalog +from objects.providers import Config, NewInstance +from objects.injections import InitArg + + +# Some example class. +class ObjectA(object): + def __init__(self, setting_one, setting_two, setting_three): + self.setting_one = setting_one + self.setting_two = setting_two + self.setting_three = setting_three + + +# Catalog of objects providers. +class Catalog(AbstractCatalog): + """ + Objects catalog. + """ + + config = Config() + """ :type: (objects.Config) """ + + object_a = NewInstance(ObjectA, + InitArg('setting_one', config.SETTING_ONE), + InitArg('setting_two', config.SETTING_TWO), + InitArg('setting_three', config.GLOBAL.SETTING_THREE)) + """ :type: (objects.Provider) -> ObjectA """ + + +# Setting config value and making some tests. +Catalog.config.update_from({ + 'SETTING_ONE': 1, + 'SETTING_TWO': 2, + 'GLOBAL': { + 'SETTING_THREE': 3 + } +}) + +object_a1 = Catalog.object_a() + +assert object_a1.setting_one == 1 +assert object_a1.setting_two == 2 +assert object_a1.setting_three == 3 + +# Changing config value one more time and making some tests. +Catalog.config.update_from({ + 'SETTING_ONE': 11, + 'SETTING_TWO': 22, + 'GLOBAL': { + 'SETTING_THREE': 33 + } +}) + +object_a2 = Catalog.object_a() + +assert object_a2.setting_one == 11 +assert object_a2.setting_two == 22 +assert object_a2.setting_three == 33 + +assert object_a1.setting_one == 1 +assert object_a1.setting_two == 2 +assert object_a1.setting_three == 3 diff --git a/examples/overrides.py b/examples/overrides.py index 242ec45a..4b4dc4db 100644 --- a/examples/overrides.py +++ b/examples/overrides.py @@ -16,6 +16,7 @@ class ObjectA(object): self.db = db +# Mock of example class. class ObjectAMock(ObjectA): pass diff --git a/objects/providers.py b/objects/providers.py index 9b0a63dc..c94361e3 100644 --- a/objects/providers.py +++ b/objects/providers.py @@ -251,7 +251,7 @@ class Value(_StaticProvider): class Callable(Provider): """ - Callable providers will provides callable calls with some predefined + Callable provider will provide callable calls with some predefined dependencies injections. """ @@ -275,3 +275,68 @@ class Callable(Provider): injections.update(kwargs) return self.calls(*args, **injections) + + +class _DeferredConfig(Provider): + """ + Deferred config providers provide an value from the root config object. + """ + def __init__(self, paths, root_config): + """ + Initializer. + """ + self.paths = paths + self.root_config = root_config + super(_DeferredConfig, self).__init__() + + def __getattr__(self, item): + """ + Returns instance of deferred config. + """ + return _DeferredConfig(paths=self.paths + (item,), + root_config=self.root_config) + + def __call__(self, *args, **kwargs): + """ + Returns provided instance. + """ + return self.root_config(self.paths) + + +class Config(Provider): + """ + Config provider provides dict values. Also config provider creates + deferred config objects for all undefined attribute calls. + """ + + def __init__(self, value=None): + """ + Initializer. + """ + if not value: + value = dict() + self.value = value + super(Config, self).__init__() + + def update_from(self, value): + """ + Updates current value from another one. + """ + self.value.update(value) + + def __getattr__(self, item): + """ + Returns instance of deferred config. + """ + return _DeferredConfig(paths=(item,), + root_config=self) + + def __call__(self, paths=None): + """ + Returns provided instance. + """ + value = self.value + if paths: + for path in paths: + value = value[path] + return value From 8233312c1524d3e66868dd1010f9b229151d8e2a Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 28 Jan 2015 14:25:27 +0200 Subject: [PATCH 5/7] codestyle fixes --- objects/providers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/objects/providers.py b/objects/providers.py index c94361e3..abbf75da 100644 --- a/objects/providers.py +++ b/objects/providers.py @@ -281,6 +281,7 @@ class _DeferredConfig(Provider): """ Deferred config providers provide an value from the root config object. """ + def __init__(self, paths, root_config): """ Initializer. From 7f88e4ffdde0595bc64ace916bfb8cc42e24cf28 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 28 Jan 2015 14:50:48 +0200 Subject: [PATCH 6/7] Adding possibility to get all providers by type from catalog --- objects/catalog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/objects/catalog.py b/objects/catalog.py index bd265052..56826245 100644 --- a/objects/catalog.py +++ b/objects/catalog.py @@ -33,14 +33,14 @@ class AbstractCatalog(object): return attribute @classmethod - def __all_providers__(cls): + def __all_providers__(cls, provider_type=Provider): """ Returns 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): + if not isinstance(provider, provider_type): continue providers.add((attr_name, provider)) return providers From 3c19399a2a3ad4cde96f75b0d7fb6445abd8c0e7 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 28 Jan 2015 14:57:03 +0200 Subject: [PATCH 7/7] 0.3 release --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 0c62199f..0d91a54c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.1 +0.3.0