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 new file mode 100644 index 00000000..45dd602c --- /dev/null +++ b/examples/scoped_provider.py @@ -0,0 +1,71 @@ +""" +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/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): """