diff --git a/docs/images/providers/abstract_factory.png b/docs/images/providers/abstract_factory.png deleted file mode 100644 index ede202af..00000000 Binary files a/docs/images/providers/abstract_factory.png and /dev/null differ diff --git a/docs/images/providers/custom_provider.png b/docs/images/providers/custom_provider.png deleted file mode 100644 index 398e6bfa..00000000 Binary files a/docs/images/providers/custom_provider.png and /dev/null differ diff --git a/docs/images/providers/overriding_simple.png b/docs/images/providers/overriding_simple.png deleted file mode 100644 index cfa3c610..00000000 Binary files a/docs/images/providers/overriding_simple.png and /dev/null differ diff --git a/docs/images/providers/overriding_users_model.png b/docs/images/providers/overriding_users_model.png deleted file mode 100644 index 16f3ff51..00000000 Binary files a/docs/images/providers/overriding_users_model.png and /dev/null differ diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index f9da42f0..2e482c0e 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -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 ------ diff --git a/docs/providers/custom.rst b/docs/providers/custom.rst index 237504c2..0d2ae08f 100644 --- a/docs/providers/custom.rst +++ b/docs/providers/custom.rst @@ -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 `_. + + 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:: diff --git a/examples/providers/custom_factory.py b/examples/providers/custom_factory.py index 01c17425..706ed04c 100644 --- a/examples/providers/custom_factory.py +++ b/examples/providers/custom_factory.py @@ -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 diff --git a/src/dependency_injector/providers.pyi b/src/dependency_injector/providers.pyi index 0617e8f2..c8d97a70 100644 --- a/src/dependency_injector/providers.pyi +++ b/src/dependency_injector/providers.pyi @@ -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]):