diff --git a/docs/images/providers/dependency.png b/docs/images/providers/dependency.png deleted file mode 100644 index dfd2f149..00000000 Binary files a/docs/images/providers/dependency.png and /dev/null differ diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 134b6e24..83459780 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -10,7 +10,7 @@ follows `Semantic versioning`_ Development version ------------------- - Update documentation and rework examples for: ``Singleton``, ``Callable``, ``Coroutine``, - ``Object``, ``List``, ``Configuration``, ``Selector`` providers. + ``Object``, ``List``, ``Configuration``, ``Selector``, and ``Dependency`` providers. - Fix mypy stub of the ``DeclarativeContainer`` to specify the ``__init__`` interface. 3.34.0 diff --git a/docs/providers/dependency.rst b/docs/providers/dependency.rst index 4bd9bf2f..9ce3977b 100644 --- a/docs/providers/dependency.rst +++ b/docs/providers/dependency.rst @@ -1,43 +1,21 @@ -Dependency providers --------------------- +Dependency provider +------------------- .. currentmodule:: dependency_injector.providers -:py:class:`Dependency` provider can be useful for development of -self-sufficient libraries / modules / applications that have required external -dependencies. +:py:class:`Dependency` provider is a placeholder for the dependency of the specified type. -For example, you have created self-sufficient library / module / application, -that has dependency on *database connection*. +The first argument of the ``Dependency`` provider specifies a type of the dependency. It is +called ``instance_of``. ``Dependency`` provider controls the type of the returned object to be an +instance of the ``instance_of`` type. -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 your 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 with appropriate instance. - -Example: - -.. note:: - - Class ``UsersService`` is a part of some library. ``UsersService`` has - dependency on database connection, which can be satisfied with any - DBAPI 2.0 database connection. Being a self-sufficient library, - ``UsersService`` doesn't hardcode any kind of database management logic. - Instead of this, ``UsersService`` has external dependency, that has to - be satisfied by client's code, out of library's scope. - -.. image:: /images/providers/dependency.png +The ``Dependency`` provider must be overridden before usage. It can be overridden by any type of +the provider. The only rule is that overriding provider must return an instance of ``instance_of`` +dependency type. .. literalinclude:: ../../examples/providers/dependency.py :language: python + :lines: 3- + :emphasize-lines: 26 .. disqus:: diff --git a/examples/providers/dependency.py b/examples/providers/dependency.py index a96aa58d..a1114cba 100644 --- a/examples/providers/dependency.py +++ b/examples/providers/dependency.py @@ -1,73 +1,50 @@ -"""`Dependency` providers example.""" +"""`Dependency` provider example.""" -import sqlite3 -import contextlib +import abc +import dataclasses -import dependency_injector.providers as providers +from dependency_injector import containers, providers, errors -class UsersService: - """Example class UsersService. - - UsersService has dependency on DBAPI 2.0 database connection. - """ - - def __init__(self, database): - """Initialize instance. - - :param database: Database connection. - :type database: sqlite3.dbapi2.Connection - """ - self.database = database - self.database.row_factory = sqlite3.dbapi2.Row - - def init_database(self): - """Initialize database, if it has not been initialized yet.""" - with contextlib.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 contextlib.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 contextlib.closing(self.database.cursor()) as cursor: - cursor.execute('SELECT id, name FROM users WHERE id=?', (id,)) - return cursor.fetchone() +class DbAdapter(metaclass=abc.ABCMeta): + ... -# Database and UsersService providers: -database = providers.Dependency(instance_of=sqlite3.dbapi2.Connection) -users_service_factory = providers.Factory(UsersService, - database=database) +class SqliteDbAdapter(DbAdapter): + ... -# Out of library's scope. -# -# Setting database provider: -database.provided_by(providers.Singleton(sqlite3.dbapi2.Connection, - database=':memory:', - timeout=30, - detect_types=True, - isolation_level='EXCLUSIVE')) -# Creating UsersService instance: -users_service = users_service_factory() +class PostgresDbAdapter(DbAdapter): + ... -# Initializing UsersService 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) +@dataclasses.dataclass +class UserService: + database: DbAdapter -# Making some asserts: -assert test_user['id'] == 1 -assert test_user['name'] == 'test_user' + +class Container(containers.DeclarativeContainer): + + database = providers.Dependency(instance_of=DbAdapter) + + user_service = providers.Factory( + UserService, + database=database, + ) + + +if __name__ == '__main__': + container1 = Container(database=providers.Singleton(SqliteDbAdapter)) + container2 = Container(database=providers.Singleton(PostgresDbAdapter)) + + assert isinstance(container1.user_service().database, SqliteDbAdapter) + assert isinstance(container2.user_service().database, PostgresDbAdapter) + + container3 = Container(database=providers.Singleton(object)) + try: + container3.user_service() + except errors.Error as exception: + print(exception) + # The output is: + # is not an + # instance of