Update documentation and example on creating a custom provider

This commit is contained in:
Roman Mogylatov 2020-09-02 20:59:55 -04:00
parent aeace8cba5
commit dd2ded7321
8 changed files with 62 additions and 50 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@ -11,6 +11,7 @@ Development version
-------------------
- Update providers overriding documentation and rework examples.
- Update documentation on injecting provided object attributes, items or method calls.
- Update documentation and example on creating a custom provider.
3.35.1
------

View File

@ -1,35 +1,43 @@
Writing of custom providers
---------------------------
Creating a custom providers
===========================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Custom provider, Create
:description: This page demonstrates how to create a custom provider.
.. currentmodule:: dependency_injector.providers
List of *Dependency Injector* providers could be widened with custom providers.
You can create a custom provider.
Below are some tips and recommendations that have to be met:
To create a custom provider you need to follow these rules:
1. Every custom provider has to extend base provider class -
:py:class:`Provider`.
2. Custom provider's ``__init__()`` could be overridden, but parent's
initializer (:py:meth:`Provider.__init__`) has to be called.
3. Providing strategy has to be implemented in custom provider's
:py:meth:`Provider.__call__` method.
4. If custom provider is based on some standard providers, it is better to
use delegation of standard providers, then extending of them.
5. If custom provider defines any attributes, it is good to list them in
``__slots__`` attribute (as *Dependency Injector* does). It can save
some memory.
6. If custom provider deals with injections, it is strongly recommended
to be consistent with :py:class:`Factory`, :py:class:`Singleton` and
:py:class:`Callable` providers style.
Example:
.. image:: /images/providers/custom_provider.png
:width: 100%
:align: center
1. New provider class should inherit :py:class:`Provider`.
2. You need to implement the ``Provider._provide()`` method.
3. You need to implement the ``Provider.__deepcopy__()`` method. It should return an
equivalent copy of a provider. All providers must be copied with a ``deepcopy()`` function
from the ``providers`` module. After the a new provider object is created use
``Provider._copy_overriding()`` method to copy all overriding providers. See the example
below.
4. If the new provider has a ``__init__()`` method, it should call the parent
``Provider.__init__()``.
.. literalinclude:: ../../examples/providers/custom_factory.py
:language: python
:lines: 3-
.. note::
1. Prefer delegation over inheritance. If you choose between inheriting a ``Factory`` or
inheriting a ``Provider`` and use ``Factory`` internally - the last is better.
2. When create a new provider follow the ``Factory``-like injections style. Consistency matters.
3. Use the ``__slots__`` attribute to make sure nothing could be attached to your provider. You
will also save some memory.
.. note::
If you don't find needed provider in the ``providers`` module and experience troubles creating
one by your own - open a
`Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_.
I'll help you to resolve the issue if that's possible. If the new provider can be useful for
others I'll include it into the ``providers`` module.
.. disqus::

View File

@ -1,40 +1,42 @@
"""Custom `Factory` example."""
"""Custom provider example."""
import dependency_injector.providers as providers
from dependency_injector import providers
class User:
"""Example class User."""
class UsersFactory(providers.Provider):
"""Example users factory."""
class CustomFactory(providers.Provider):
__slots__ = ('_factory',)
def __init__(self):
"""Initialize instance."""
self._factory = providers.Factory(User)
def __init__(self, provides, *args, **kwargs):
self._factory = providers.Factory(provides, *args, **kwargs)
super().__init__()
def __call__(self, *args, **kwargs):
"""Return provided object.
def __deepcopy__(self, memo):
copied = memo.get(id(self))
if copied is not None:
return copied
Callable interface implementation.
"""
if self.last_overriding is not None:
return self.last_overriding._provide(args, kwargs)
copied = self.__class__(
self._factory.provides,
*providers.deepcopy(self._factory.args, memo),
**providers.deepcopy(self._factory.kwargs, memo),
)
self._copy_overridings(copied, memo)
return copied
def _provide(self, args, kwargs):
return self._factory(*args, **kwargs)
# Users factory:
users_factory = UsersFactory()
factory = CustomFactory(object)
# Creating several User objects:
user1 = users_factory()
user2 = users_factory()
# Making some asserts:
assert isinstance(user1, User)
assert isinstance(user2, User)
assert user1 is not user2
if __name__ == '__main__':
object1 = factory()
assert isinstance(object1, object)
object2 = factory()
assert isinstance(object1, object)
assert object1 is not object2

View File

@ -43,6 +43,7 @@ class Provider(_Provider):
def delegate(self) -> Provider: ...
@property
def provider(self) -> Provider: ...
def _copy_overridings(self, copied: Provider, memo: Optional[Dict[str, Any]]) -> None: ...
class Object(Provider, Generic[T]):