diff --git a/docs/_providers.rst b/docs/_providers.rst index f70a580f..7187b56c 100644 --- a/docs/_providers.rst +++ b/docs/_providers.rst @@ -2,86 +2,6 @@ Providers ========= -External dependency providers ------------------------------ - -``ExternalDependency`` provider can be useful for development of -self-sufficient libraries / modules / applications, that has required external -dependencies. - -For example, you have created self-sufficient library / module / application, -that has dependency on *database connection*. - -Second step you want to do is to make this software component to be easy -reusable by wide amount of developers and to be easily integrated into many -applications. - -It may be good idea, to move all external dependencies (like -*database connection*) to the top level and make them to be injected on your -software component's initialization. It will make third party developers feel -themselves free about integration of yours component in their applications, -because of they would be able to find right place / right way for doing this -in their application's architectures. - -On the other side, -you can be sure, that your external dependency will be satisfied by appropriate -instance. - -Example: - -.. code-block:: python - - """External dependency providers example.""" - - import sqlite3 - - from objects.providers import Singleton - from objects.providers import Factory - from objects.providers import ExternalDependency - - from objects.injections import KwArg - from objects.injections import Attribute - - - class ObjectA(object): - - """ObjectA has dependency on database.""" - - def __init__(self, database): - """Initializer. - - Database dependency need to be injected via init arg.""" - self.database = database - - def get_one(self): - """Select one from database and return it.""" - return self.database.execute('SELECT 1').fetchone()[0] - - - # Database and `ObjectA` providers. - database = ExternalDependency(instance_of=sqlite3.Connection) - - object_a_factory = Factory(ObjectA, - KwArg('database', database)) - - # Satisfaction of external dependency. - database.override(Singleton(sqlite3.Connection, - KwArg('database', ':memory:'), - KwArg('timeout', 30), - KwArg('detect_types', True), - KwArg('isolation_level', 'EXCLUSIVE'), - Attribute('row_factory', sqlite3.Row))) - - # Creating several `ObjectA` instances. - object_a_1 = object_a_factory() - object_a_2 = object_a_factory() - - # Making some asserts. - assert object_a_1 is not object_a_2 - assert object_a_1.database is object_a_2.database is database() - - - Config providers ---------------- diff --git a/docs/images/external_dependency.png b/docs/images/external_dependency.png new file mode 100644 index 00000000..831f2699 Binary files /dev/null and b/docs/images/external_dependency.png differ diff --git a/docs/providers/external_dependency.rst b/docs/providers/external_dependency.rst new file mode 100644 index 00000000..ebbdd800 --- /dev/null +++ b/docs/providers/external_dependency.rst @@ -0,0 +1,120 @@ +External dependency providers +----------------------------- + +``ExternalDependency`` provider can be useful for development of +self-sufficient libraries / modules / applications that has required external +dependencies. + +For example, you have created self-sufficient library / module / application, +that has dependency on *database connection*. + +Second step you want to do is to make this software component to be easy +reusable by wide amount of developers and to be easily integrated into many +applications. + +It may be good idea, to move all external dependencies (like +*database connection*) to the top level and make them to be injected on your +software component's initialization. It will make third party developers feel +themselves free about integration of yours component in their applications, +because they would be able to find right place / right way for doing this +in their application's architectures. + +At the same time, you can be sure, that your external dependency will be +satisfied by appropriate instance. + + +Example: + + +.. note:: + + Class ``UserService`` is a part of some library. ``UserService`` has + dependency on database connection, which can be satisfied with any + DBAPI 2.0 database connection. Being a self-sufficient library, + ``UserService`` doesn't hardcode any kind of database management logic. + Instead of this, ``UserService`` provides external dependency, that has to + be satisfied out of library's scope. + +.. image:: /images/external_dependency.png + +.. code-block:: python + + """`ExternalDependency` providers example.""" + + from objects.providers import ExternalDependency + from objects.providers import Factory + from objects.providers import Singleton + + from objects.injections import KwArg + from objects.injections import Attribute + + # Importing SQLITE3 and contextlib.closing for working with cursors: + import sqlite3 + from contextlib import closing + + + # Definition of example UserService: + class UserService(object): + + """Example class UserService. + + UserService has dependency on DBAPI 2.0 database connection.""" + + def __init__(self, database): + """Initializer. + + Database dependency need to be injected via init arg.""" + self.database = database + + def init_database(self): + """Initialize database, if it has not been initialized yet.""" + with closing(self.database.cursor()) as cursor: + cursor.execute(""" + CREATE TABLE IF NOT EXISTS users( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(32) + ) + """) + + def create(self, name): + """Create user with provided name and return his id.""" + with closing(self.database.cursor()) as cursor: + cursor.execute('INSERT INTO users(name) VALUES (?)', (name,)) + return cursor.lastrowid + + def get_by_id(self, id): + """Return user info by user id.""" + with closing(self.database.cursor()) as cursor: + cursor.execute('SELECT id, name FROM users WHERE id=?', (id,)) + return cursor.fetchone() + + + # Database and UserService providers: + database = ExternalDependency(instance_of=sqlite3.Connection) + users_service_factory = Factory(UserService, + KwArg('database', database)) + + # Out of library's scope. + # + # Setting database provider: + database.provided_by(Singleton(sqlite3.Connection, + KwArg('database', ':memory:'), + KwArg('timeout', 30), + KwArg('detect_types', True), + KwArg('isolation_level', 'EXCLUSIVE'), + Attribute('row_factory', sqlite3.Row))) + + # Creating UserService instance: + users_service = users_service_factory() + + # Initializing UserService database: + users_service.init_database() + + # Creating test user and retrieving full information about him: + test_user_id = users_service.create(name='test_user') + test_user = users_service.get_by_id(test_user_id) + + # Making some asserts: + assert test_user['id'] == 1 + assert test_user['name'] == 'test_user' + diff --git a/docs/providers/index.rst b/docs/providers/index.rst index 7bb0970f..8815ba0e 100644 --- a/docs/providers/index.rst +++ b/docs/providers/index.rst @@ -12,3 +12,4 @@ All providers are callable. They describe how particular objects are provided. singleton static callable + external_dependency diff --git a/examples/providers/external_dependency.py b/examples/providers/external_dependency.py new file mode 100644 index 00000000..c4b3ef04 --- /dev/null +++ b/examples/providers/external_dependency.py @@ -0,0 +1,78 @@ +"""`ExternalDependency` providers example.""" + +from objects.providers import ExternalDependency +from objects.providers import Factory +from objects.providers import Singleton + +from objects.injections import KwArg +from objects.injections import Attribute + +# Importing SQLITE3 and contextlib.closing for working with cursors: +import sqlite3 +from contextlib import closing + + +# Definition of example UserService: +class UserService(object): + + """Example class UserService. + + UserService has dependency on DBAPI 2.0 database connection.""" + + def __init__(self, database): + """Initializer. + + Database dependency need to be injected via init arg.""" + self.database = database + + def init_database(self): + """Initialize database, if it has not been initialized yet.""" + with closing(self.database.cursor()) as cursor: + cursor.execute(""" + CREATE TABLE IF NOT EXISTS users( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR(32) + ) + """) + + def create(self, name): + """Create user with provided name and return his id.""" + with closing(self.database.cursor()) as cursor: + cursor.execute('INSERT INTO users(name) VALUES (?)', (name,)) + return cursor.lastrowid + + def get_by_id(self, id): + """Return user info by user id.""" + with closing(self.database.cursor()) as cursor: + cursor.execute('SELECT id, name FROM users WHERE id=?', (id,)) + return cursor.fetchone() + + +# Database and UserService providers: +database = ExternalDependency(instance_of=sqlite3.Connection) +users_service_factory = Factory(UserService, + KwArg('database', database)) + +# Out of library's scope. +# +# Setting database provider: +database.provided_by(Singleton(sqlite3.Connection, + KwArg('database', ':memory:'), + KwArg('timeout', 30), + KwArg('detect_types', True), + KwArg('isolation_level', 'EXCLUSIVE'), + Attribute('row_factory', sqlite3.Row))) + +# Creating UserService instance: +users_service = users_service_factory() + +# Initializing UserService database: +users_service.init_database() + +# Creating test user and retrieving full information about him: +test_user_id = users_service.create(name='test_user') +test_user = users_service.get_by_id(test_user_id) + +# Making some asserts: +assert test_user['id'] == 1 +assert test_user['name'] == 'test_user' diff --git a/objects/providers.py b/objects/providers.py index a8fd9ea9..b7b4229f 100644 --- a/objects/providers.py +++ b/objects/providers.py @@ -189,6 +189,10 @@ class ExternalDependency(Provider): return instance + def provided_by(self, provider): + """Set external dependency provider.""" + return self.override(provider) + class _StaticProvider(Provider): diff --git a/tests/test_providers.py b/tests/test_providers.py index 0dc37ee7..26bddfa4 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -323,12 +323,12 @@ class ExternalDependencyTests(unittest.TestCase): def test_call_overridden(self): """Test call of overridden external dependency.""" - self.provider.override(Factory(list)) + self.provider.provided_by(Factory(list)) self.assertIsInstance(self.provider(), list) def test_call_overridden_but_not_instance_of(self): """Test call of overridden external dependency, but not instance of.""" - self.provider.override(Factory(dict)) + self.provider.provided_by(Factory(dict)) self.assertRaises(Error, self.provider) def test_call_not_overridden(self):