Repository: reagento/dishka

Issue:
Support multiple decorators on method
```python
@provide(provides=A)
@provide(provides=AProto)
def foo(self, a: A) -> A:
    return A()


@decorate(provides=A)
@decorate(provides=AProto)
def bar(self, a: A) -> A:
    return A()


```

Discussion:

Patch:
diff --git a/README.md b/README.md
index 50ed770..51c9abe 100644
--- a/README.md
+++ b/README.md
@@ -182,13 +182,26 @@ This allows you to have multiple parts of application build separately without n
     class MyProvider(Provider):
         a = provide(source=AChild, scope=Scope.REQUEST, provides=A)
     ```
-* Having multiple interfaces which can be created as a same class with defined provider? Use alias:
+* Having multiple interfaces which can be created as a same class? Use `AnyOf`:
 
     ```python
+    from dishka import AnyOf
+
     class MyProvider(Provider):
+        @provide
+        def p(self) -> AnyOf[A, AProtocol]:
+            return A()
+    ```
+
+Use alias if you want to add them in another `Provider`:
+
+    ```python
+    class MyProvider2(Provider):
         p = alias(source=A, provides=AProtocol)
     ```
-    it works the same way as
+
+In both cases it works the same way as
+
     ```python
     class MyProvider(Provider):
         @provide(scope=<Scope of A>)
@@ -196,6 +209,7 @@ This allows you to have multiple parts of application build separately without n
             return a
     ```
 
+
 * Want to apply decorator pattern and do not want to alter existing provide method? Use `decorate`. It will construct object using earlie defined provider and then pass it to your decorator before returning from the container.
 
   ```python
diff --git a/docs/provider/provide.rst b/docs/provider/provide.rst
index 9a3bb72..dfa1800 100644
--- a/docs/provider/provide.rst
+++ b/docs/provider/provide.rst
@@ -73,3 +73,16 @@ By default the result is cached within scope. You can disable it providing ``cac
        @provide(scope=Scope.REQUEST)  # has own scope
        async def get_b(self) -> B:
           return B()
+
+* Having multiple interfaces which can be created as a same class? Use ``AnyOf`` as a result hint:
+
+.. code-block:: python
+
+    from dishka import AnyOf
+
+    class MyProvider(Provider):
+        @provide
+        def p(self) -> AnyOf[A, AProtocol]:
+            return A()
+
+It works similar to :ref:`alias`.
\ No newline at end of file
diff --git a/src/dishka/__init__.py b/src/dishka/__init__.py
index 29b0a9f..6e83444 100644
--- a/src/dishka/__init__.py
+++ b/src/dishka/__init__.py
@@ -1,4 +1,5 @@
 __all__ = [
+    "AnyOf",
     "make_async_container", "AsyncContainer",
     "DEFAULT_COMPONENT", "Component",
     "make_container", "Container", "FromComponent",
@@ -19,5 +20,6 @@ from .dependency_source import (
 from .entities.component import DEFAULT_COMPONENT, Component
 from .entities.depends_marker import FromDishka
 from .entities.key import DependencyKey, FromComponent
+from .entities.provides_marker import AnyOf
 from .entities.scope import BaseScope, Scope
 from .provider import Provider
diff --git a/src/dishka/dependency_source/__init__.py b/src/dishka/dependency_source/__init__.py
index b419e66..020913c 100644
--- a/src/dishka/dependency_source/__init__.py
+++ b/src/dishka/dependency_source/__init__.py
@@ -1,14 +1,17 @@
 __all__ = [
     "DependencySource",
     "alias", "Alias",
-    "context_var", "from_context",
+    "context_var", "ContextVariable", "from_context",
     "decorate", "Decorator",
     "provide", "Factory", "FactoryType",
 ]
 
-from .alias import Alias, alias
-from .context_var import ContextVariable, from_context
-from .decorator import Decorator, decorate
-from .factory import Factory, FactoryType, provide
-
-DependencySource = Alias | Factory | Decorator | ContextVariable
+from .alias import Alias
+from .composite import DependencySource
+from .context_var import ContextVariable
+from .decorator import Decorator
+from .factory import Factory, FactoryType
+from .make_alias import alias
+from .make_context_var import from_context
+from .make_decorator import decorate
+from .make_factory import provide
diff --git a/src/dishka/dependency_source/alias.py b/src/dishka/dependency_source/alias.py
index 1ed651e..7660a5d 100644
--- a/src/dishka/dependency_source/alias.py
+++ b/src/dishka/dependency_source/alias.py
@@ -1,6 +1,4 @@
-from typing import (
-    Any,
-)
+from typing import Any
 
 from dishka.entities.component import Component
 from dishka.entities.key import DependencyKey
@@ -40,24 +38,3 @@ class Alias:
 
     def __get__(self, instance, owner):
         return self
-
-
-def alias(
-        *,
-        source: type,
-        provides: type | None = None,
-        cache: bool = True,
-        component: Component | None = None,
-) -> Alias:
-    if component is provides is None:
-        raise ValueError("Either component or provides must be set in alias")
-    if provides is None:
-        provides = source
-    return Alias(
-        source=DependencyKey(
-            type_hint=source,
-            component=component,
-        ),
-        provides=DependencyKey(provides, None),
-        cache=cache,
-    )
diff --git a/src/dishka/dependency_source/composite.py b/src/dishka/dependency_source/composite.py
new file mode 100644
index 0000000..29825a3
--- /dev/null
+++ b/src/dishka/dependency_source/composite.py
@@ -0,0 +1,47 @@
+from collections.abc import Callable, Sequence
+from typing import Any
+
+from .alias import Alias
+from .context_var import ContextVariable
+from .decorator import Decorator
+from .factory import Factory
+
+DependencySource = Alias | Factory | Decorator | ContextVariable
+
+
+class CompositeDependencySource:
+    _instances = 0
+
+    def __init__(
+            self,
+            origin: Callable,
+            dependency_sources: Sequence[DependencySource] = (),
+    ) -> None:
+        self.dependency_sources = list(dependency_sources)
+        self.origin = origin
+        CompositeDependencySource._instances += 1
+        self.number = self._instances
+
+    def __get__(self, instance, owner) -> "CompositeDependencySource":
+        try:
+            origin = self.origin.__get__(instance, owner)
+        except AttributeError:  # not a valid descriptor
+            origin = self.origin
+        return CompositeDependencySource(
+            origin=origin,
+            dependency_sources=[
+                s.__get__(instance, owner) for s in self.dependency_sources
+            ],
+        )
+
+    def __call__(self, *args, **kwargs) -> Any:
+        return self.origin(*args, **kwargs)
+
+
+def ensure_composite(
+        origin: Callable | CompositeDependencySource,
+) -> CompositeDependencySource:
+    if isinstance(origin, CompositeDependencySource):
+        return origin
+    else:
+        return CompositeDependencySource(origin)
diff --git a/src/dishka/dependency_source/context_var.py b/src/dishka/dependency_source/context_var.py
index 59914e7..401b1a1 100644
--- a/src/dishka/dependency_source/context_var.py
+++ b/src/dishka/dependency_source/context_var.py
@@ -1,15 +1,13 @@
-from typing import (
-    Any,
-)
+from typing import Any
 
 from dishka.entities.component import DEFAULT_COMPONENT, Component
 from dishka.entities.key import DependencyKey
 from dishka.entities.scope import BaseScope
-from .alias import alias
+from .alias import Alias
 from .factory import Factory, FactoryType
 
 
-def _context_stub() -> Any:
+def context_stub() -> Any:
     raise NotImplementedError
 
 
@@ -30,7 +28,7 @@ class ContextVariable:
         if component == DEFAULT_COMPONENT:
             return Factory(
                 scope=self.scope,
-                source=_context_stub,
+                source=context_stub,
                 provides=self.provides,
                 is_to_bind=False,
                 dependencies=[],
@@ -38,9 +36,11 @@ class ContextVariable:
                 cache=False,
             )
         else:
-            aliased = alias(
-                source=self.provides.type_hint,
-                component=DEFAULT_COMPONENT,
+            aliased = Alias(
+                source=self.provides.with_component(DEFAULT_COMPONENT),
+                provides=DependencyKey(self.provides.type_hint,
+                                       component=component),
+                cache=False,
             )
             return aliased.as_factory(scope=self.scope, component=component)
 
@@ -50,15 +50,3 @@ class ContextVariable:
             scope=scope,
             provides=self.provides,
         )
-
-
-def from_context(
-        *, provides: Any, scope: BaseScope | None = None,
-) -> ContextVariable:
-    return ContextVariable(
-        provides=DependencyKey(
-            type_hint=provides,
-            component=DEFAULT_COMPONENT,
-        ),
-        scope=scope,
-    )
diff --git a/src/dishka/dependency_source/decorator.py b/src/dishka/dependency_source/decorator.py
index 44b3d0d..5fd9a9c 100644
--- a/src/dishka/dependency_source/decorator.py
+++ b/src/dishka/dependency_source/decorator.py
@@ -1,23 +1,20 @@
-from collections.abc import (
-    Callable,
-)
-from typing import (
-    Any,
-    overload,
-)
+from typing import Any
 
 from dishka.entities.component import Component
 from dishka.entities.key import DependencyKey
 from dishka.entities.scope import BaseScope
-from .factory import Factory, make_factory
+from .factory import Factory
 
 
 class Decorator:
     __slots__ = ("provides", "factory")
 
-    def __init__(self, factory: Factory):
+    def __init__(self, factory: Factory, provides: Any = None):
         self.factory = factory
-        self.provides = factory.provides
+        if provides:
+            self.provides = provides
+        else:
+            self.provides = factory.provides
 
     def as_factory(
             self, *,
@@ -45,37 +42,3 @@ class Decorator:
 
     def __get__(self, instance, owner):
         return Decorator(self.factory.__get__(instance, owner))
-
-
-@overload
-def decorate(
-        *,
-        provides: Any = None,
-) -> Callable[[Callable], Decorator]:
-    ...
-
-
-@overload
-def decorate(
-        source: Callable | type,
-        *,
-        provides: Any = None,
-) -> Decorator:
-    ...
-
-
-def decorate(
-        source: Callable | type | None = None,
-        provides: Any = None,
-) -> Decorator | Callable[[Callable], Decorator]:
-    if source is not None:
-        return Decorator(make_factory(
-            provides=provides, scope=None, source=source, cache=False,
-        ))
-
-    def scoped(func):
-        return Decorator(make_factory(
-            provides=provides, scope=None, source=func, cache=False,
-        ))
-
-    return scoped
diff --git a/src/dishka/dependency_source/factory.py b/src/dishka/dependency_source/factory.py
index 2a9ae0f..004455a 100644
--- a/src/dishka/dependency_source/factory.py
+++ b/src/dishka/dependency_source/factory.py
@@ -1,42 +1,13 @@
 from collections.abc import (
-    AsyncGenerator,
-    AsyncIterable,
-    AsyncIterator,
-    Callable,
-    Generator,
-    Iterable,
-    Iterator,
     Sequence,
 )
 from enum import Enum
-from inspect import (
-    isasyncgenfunction,
-    isclass,
-    iscoroutinefunction,
-    isfunction,
-    isgeneratorfunction,
-    ismethod,
-    signature,
-)
 from typing import (
     Any,
-    get_args,
-    get_origin,
-    get_type_hints,
-    overload,
 )
 
-from dishka._adaptix.type_tools.basic_utils import (
-    get_all_type_hints,
-    get_type_vars,
-    is_bare_generic,
-)
-from dishka._adaptix.type_tools.generic_resolver import (
-    GenericResolver,
-    MembersStorage,
-)
 from dishka.entities.component import Component
-from dishka.entities.key import DependencyKey, hints_to_dependency_keys
+from dishka.entities.key import DependencyKey
 from dishka.entities.scope import BaseScope
 
 
@@ -50,10 +21,6 @@ class FactoryType(Enum):
     CONTEXT = "context"
 
 
-def _is_bound_method(obj):
-    return ismethod(obj) and obj.__self__
-
-
 class Factory:
     __slots__ = (
         "dependencies", "source", "provides", "scope", "type",
@@ -111,285 +78,3 @@ class Factory:
             cache=self.cache,
             type_=self.type,
         )
-
-
-def _get_init_members(tp) -> MembersStorage[str, None]:
-    type_hints = get_all_type_hints(tp.__init__)
-    if "__init__" in tp.__dict__:
-        overridden = frozenset(type_hints)
-    else:
-        overridden = {}
-
-    return MembersStorage(
-        meta=None,
-        members=type_hints,
-        overriden=overridden,
-    )
-
-
-def _guess_factory_type(source):
-    if isasyncgenfunction(source):
-        return FactoryType.ASYNC_GENERATOR
-    elif isgeneratorfunction(source):
-        return FactoryType.GENERATOR
-    elif iscoroutinefunction(source):
-        return FactoryType.ASYNC_FACTORY
-    else:
-        return FactoryType.FACTORY
-
-
-def _async_generator_result(possible_dependency: Any):
-    origin = get_origin(possible_dependency)
-    if origin is AsyncIterable:
-        return get_args(possible_dependency)[0]
-    elif origin is AsyncIterator:
-        return get_args(possible_dependency)[0]
-    elif origin is AsyncGenerator:
-        return get_args(possible_dependency)[0]
-    else:
-        raise TypeError(
-            f"Unsupported return type {possible_dependency} {origin} "
-            f"for async generator",
-        )
-
-
-def _generator_result(possible_dependency: Any):
-    origin = get_origin(possible_dependency)
-    if origin is Iterable:
-        return get_args(possible_dependency)[0]
-    elif origin is Iterator:
-        return get_args(possible_dependency)[0]
-    elif origin is Generator:
-        return get_args(possible_dependency)[1]
-    else:
-        raise TypeError(
-            f"Unsupported return type {possible_dependency} {origin}"
-            f" for generator",
-        )
-
-
-def _clean_result_hint(factory_type: FactoryType, possible_dependency: Any):
-    if factory_type == FactoryType.ASYNC_GENERATOR:
-        return _async_generator_result(possible_dependency)
-    elif factory_type == FactoryType.GENERATOR:
-        return _generator_result(possible_dependency)
-    return possible_dependency
-
-
-def _make_factory_by_class(
-        *,
-        provides: Any,
-        scope: BaseScope | None,
-        source: Callable,
-        cache: bool,
-):
-    # we need to fix concrete generics and normal classes as well
-    # as classes can be children of concrete generics
-    res = GenericResolver(_get_init_members)
-    hints = dict(res.get_resolved_members(source).members)
-    hints.pop("return", None)
-    dependencies = list(hints.values())
-    if not provides:
-        provides = source
-    is_to_bind = False
-    factory_type = FactoryType.FACTORY
-    return Factory(
-        dependencies=hints_to_dependency_keys(dependencies),
-        type_=factory_type,
-        source=source,
-        scope=scope,
-        provides=DependencyKey(provides, None),
-        is_to_bind=is_to_bind,
-        cache=cache,
-    )
-
-
-def _make_factory_by_method(
-        *,
-        provides: Any,
-        scope: BaseScope | None,
-        source: Callable | classmethod,
-        cache: bool,
-):
-    if isinstance(source, classmethod):
-        params = signature(source.__wrapped__).parameters
-        factory_type = _guess_factory_type(source.__wrapped__)
-    else:
-        params = signature(source).parameters
-        factory_type = _guess_factory_type(source)
-
-    hints = get_type_hints(source, include_extras=True)
-    self = next(iter(params.values()), None)
-    if self:
-        if self.name not in hints:
-            # add self to dependencies, so it can be easily removed
-            # if we will bind factory to provider instance
-            hints = {self.name: Any, **hints}
-        is_to_bind = True
-    else:
-        is_to_bind = False
-    possible_dependency = hints.pop("return", None)
-    dependencies = list(hints.values())
-    if not provides:
-        provides = _clean_result_hint(factory_type, possible_dependency)
-
-    return Factory(
-        dependencies=hints_to_dependency_keys(dependencies),
-        type_=factory_type,
-        source=source,
-        scope=scope,
-        provides=DependencyKey(provides, None),
-        is_to_bind=is_to_bind,
-        cache=cache,
-    )
-
-
-def _make_factory_by_static_method(
-        *,
-        provides: Any,
-        scope: BaseScope | None,
-        source: staticmethod,
-        cache: bool,
-):
-    factory_type = _guess_factory_type(source.__wrapped__)
-    hints = get_type_hints(source, include_extras=True)
-    possible_dependency = hints.pop("return", None)
-    dependencies = list(hints.values())
-    if not provides:
-        provides = _clean_result_hint(factory_type, possible_dependency)
-    return Factory(
-        dependencies=hints_to_dependency_keys(dependencies),
-        type_=factory_type,
-        source=source,
-        scope=scope,
-        provides=DependencyKey(provides, None),
-        is_to_bind=False,
-        cache=cache,
-    )
-
-
-def _make_factory_by_other_callable(
-        *,
-        provides: Any,
-        scope: BaseScope | None,
-        source: Callable,
-        cache: bool,
-):
-    if _is_bound_method(source):
-        to_check = source.__func__
-    else:
-        to_check = type(source).__call__
-    factory = make_factory(
-        provides=provides,
-        source=to_check,
-        cache=cache,
-        scope=scope,
-    )
-    if factory.is_to_bind:
-        dependencies = factory.dependencies[1:]  # remove `self`
-    else:
-        dependencies = factory.dependencies
-    return Factory(
-        dependencies=dependencies,
-        type_=factory.type,
-        source=source,
-        scope=scope,
-        provides=factory.provides,
-        is_to_bind=False,
-        cache=cache,
-    )
-
-
-def make_factory(
-        *,
-        provides: Any,
-        scope: BaseScope | None,
-        source: Callable,
-        cache: bool,
-) -> Factory:
-    if is_bare_generic(source):
-        source = source[get_type_vars(source)]
-
-    if isclass(source) or get_origin(source):
-        return _make_factory_by_class(
-            provides=provides, scope=scope, source=source, cache=cache,
-        )
-    elif isfunction(source) or isinstance(source, classmethod):
-        return _make_factory_by_method(
-            provides=provides, scope=scope, source=source, cache=cache,
-        )
-    elif isinstance(source, staticmethod):
-        return _make_factory_by_static_method(
-            provides=provides, scope=scope, source=source, cache=cache,
-        )
-    elif callable(source):
-        return _make_factory_by_other_callable(
-            provides=provides, scope=scope, source=source, cache=cache,
-        )
-    else:
-        raise TypeError(f"Cannot use {type(source)} as a factory")
-
-
-@overload
-def provide(
-        *,
-        scope: BaseScope = None,
-        provides: Any = None,
-        cache: bool = True,
-) -> Callable[[Callable], Factory]:
-    ...
-
-
-@overload
-def provide(
-        source: Callable | classmethod | staticmethod | type | None,
-        *,
-        scope: BaseScope | None = None,
-        provides: Any = None,
-        cache: bool = True,
-) -> Factory:
-    ...
-
-
-def provide(
-        source: Callable | classmethod | staticmethod | type | None = None,
-        *,
-        scope: BaseScope | None = None,
-        provides: Any = None,
-        cache: bool = True,
-) -> Factory | Callable[[Callable], Factory]:
-    """
-    Mark a method or class as providing some dependency.
-
-    If used as a method decorator then return annotation is used
-    to determine what is provided. User `provides` to override that.
-    Method parameters are analyzed and passed automatically.
-
-    If used with a class a first parameter than `__init__` method parameters
-    are passed automatically. If no provides is passed then it is
-    supposed that class itself is a provided dependency.
-
-    Return value must be saved as a `Provider` class attribute and
-    not intended for direct usage
-
-    :param source: Method to decorate or class.
-    :param scope: Scope of the dependency to limit its lifetime
-    :param provides: Dependency type which is provided by this factory
-    :return: instance of Factory or a decorator returning it
-    :param cache: save created object to scope cache or not
-    """
-    if source is not None:
-        return make_factory(
-            provides=provides, scope=scope, source=source, cache=cache,
-        )
-
-    def scoped(func):
-        return make_factory(
-            provides=provides, scope=scope, source=func, cache=cache,
-        )
-
-    return scoped
-
-
-def _context_stub():
-    raise NotImplementedError
diff --git a/src/dishka/dependency_source/make_alias.py b/src/dishka/dependency_source/make_alias.py
new file mode 100644
index 0000000..0e3d04a
--- /dev/null
+++ b/src/dishka/dependency_source/make_alias.py
@@ -0,0 +1,30 @@
+from dishka.entities.component import Component
+from dishka.entities.key import DependencyKey
+from .alias import Alias
+from .composite import CompositeDependencySource, ensure_composite
+from .unpack_provides import unpack_alias
+
+
+def alias(
+        *,
+        source: type,
+        provides: type | None = None,
+        cache: bool = True,
+        component: Component | None = None,
+) -> CompositeDependencySource:
+    if component is provides is None:
+        raise ValueError("Either component or provides must be set in alias")
+    if provides is None:
+        provides = source
+
+    composite = ensure_composite(source)
+    alias = Alias(
+        source=DependencyKey(
+            type_hint=source,
+            component=component,
+        ),
+        provides=DependencyKey(provides, None),
+        cache=cache,
+    )
+    composite.dependency_sources.extend(unpack_alias(alias))
+    return composite
diff --git a/src/dishka/dependency_source/make_context_var.py b/src/dishka/dependency_source/make_context_var.py
new file mode 100644
index 0000000..32c395a
--- /dev/null
+++ b/src/dishka/dependency_source/make_context_var.py
@@ -0,0 +1,23 @@
+from typing import (
+    Any,
+)
+
+from dishka.entities.component import DEFAULT_COMPONENT
+from dishka.entities.key import DependencyKey
+from dishka.entities.scope import BaseScope
+from .composite import CompositeDependencySource
+from .context_var import ContextVariable, context_stub
+
+
+def from_context(
+        *, provides: Any, scope: BaseScope | None = None,
+) -> CompositeDependencySource:
+    composite = CompositeDependencySource(origin=context_stub)
+    composite.dependency_sources.append(ContextVariable(
+        provides=DependencyKey(
+            type_hint=provides,
+            component=DEFAULT_COMPONENT,
+        ),
+        scope=scope,
+    ))
+    return composite
diff --git a/src/dishka/dependency_source/make_decorator.py b/src/dishka/dependency_source/make_decorator.py
new file mode 100644
index 0000000..3478e8f
--- /dev/null
+++ b/src/dishka/dependency_source/make_decorator.py
@@ -0,0 +1,50 @@
+from collections.abc import Callable
+from typing import Any, overload
+
+from .composite import CompositeDependencySource, ensure_composite
+from .decorator import Decorator
+from .make_factory import make_factory
+from .unpack_provides import unpack_decorator
+
+
+def _decorate(
+        source: Callable | type | None = None,
+        provides: Any = None,
+) -> CompositeDependencySource:
+    composite = ensure_composite(source)
+    decorator = Decorator(make_factory(
+        provides=provides, scope=None, source=source, cache=False,
+    ))
+    composite.dependency_sources.extend(unpack_decorator(decorator))
+    return composite
+
+
+@overload
+def decorate(
+        *,
+        provides: Any = None,
+) -> Callable[[Callable], CompositeDependencySource]:
+    ...
+
+
+@overload
+def decorate(
+        source: Callable | type,
+        *,
+        provides: Any = None,
+) -> CompositeDependencySource:
+    ...
+
+
+def decorate(
+        source: Callable | type | None = None,
+        provides: Any = None,
+) -> CompositeDependencySource | Callable[
+    [Callable], CompositeDependencySource]:
+    if source is not None:
+        return _decorate(source, provides)
+
+    def scoped(func):
+        return _decorate(func, provides)
+
+    return scoped
diff --git a/src/dishka/dependency_source/make_factory.py b/src/dishka/dependency_source/make_factory.py
new file mode 100644
index 0000000..b8858bf
--- /dev/null
+++ b/src/dishka/dependency_source/make_factory.py
@@ -0,0 +1,340 @@
+from collections.abc import (
+    AsyncGenerator,
+    AsyncIterable,
+    AsyncIterator,
+    Callable,
+    Generator,
+    Iterable,
+    Iterator,
+)
+from inspect import (
+    isasyncgenfunction,
+    isclass,
+    iscoroutinefunction,
+    isfunction,
+    isgeneratorfunction,
+    ismethod,
+    signature,
+)
+from typing import (
+    Any,
+    get_args,
+    get_origin,
+    get_type_hints,
+    overload,
+)
+
+from dishka._adaptix.type_tools.basic_utils import (
+    get_all_type_hints,
+    get_type_vars,
+    is_bare_generic,
+)
+from dishka._adaptix.type_tools.generic_resolver import (
+    GenericResolver,
+    MembersStorage,
+)
+from dishka.entities.key import DependencyKey, hints_to_dependency_keys
+from dishka.entities.scope import BaseScope
+from .composite import CompositeDependencySource, ensure_composite
+from .factory import Factory, FactoryType
+from .unpack_provides import unpack_factory
+
+
+def _is_bound_method(obj):
+    return ismethod(obj) and obj.__self__
+
+
+def _get_init_members(tp) -> MembersStorage[str, None]:
+    type_hints = get_all_type_hints(tp.__init__)
+    if "__init__" in tp.__dict__:
+        overridden = frozenset(type_hints)
+    else:
+        overridden = {}
+
+    return MembersStorage(
+        meta=None,
+        members=type_hints,
+        overriden=overridden,
+    )
+
+
+def _guess_factory_type(source):
+    if isasyncgenfunction(source):
+        return FactoryType.ASYNC_GENERATOR
+    elif isgeneratorfunction(source):
+        return FactoryType.GENERATOR
+    elif iscoroutinefunction(source):
+        return FactoryType.ASYNC_FACTORY
+    else:
+        return FactoryType.FACTORY
+
+
+def _async_generator_result(possible_dependency: Any):
+    origin = get_origin(possible_dependency)
+    if origin is AsyncIterable:
+        return get_args(possible_dependency)[0]
+    elif origin is AsyncIterator:
+        return get_args(possible_dependency)[0]
+    elif origin is AsyncGenerator:
+        return get_args(possible_dependency)[0]
+    else:
+        raise TypeError(
+            f"Unsupported return type {possible_dependency} {origin} "
+            f"for async generator",
+        )
+
+
+def _generator_result(possible_dependency: Any):
+    origin = get_origin(possible_dependency)
+    if origin is Iterable:
+        return get_args(possible_dependency)[0]
+    elif origin is Iterator:
+        return get_args(possible_dependency)[0]
+    elif origin is Generator:
+        return get_args(possible_dependency)[1]
+    else:
+        raise TypeError(
+            f"Unsupported return type {possible_dependency} {origin}"
+            f" for generator",
+        )
+
+
+def _clean_result_hint(factory_type: FactoryType, possible_dependency: Any):
+    if factory_type == FactoryType.ASYNC_GENERATOR:
+        return _async_generator_result(possible_dependency)
+    elif factory_type == FactoryType.GENERATOR:
+        return _generator_result(possible_dependency)
+    return possible_dependency
+
+
+def _make_factory_by_class(
+        *,
+        provides: Any,
+        scope: BaseScope | None,
+        source: Callable,
+        cache: bool,
+) -> Factory:
+    # we need to fix concrete generics and normal classes as well
+    # as classes can be children of concrete generics
+    res = GenericResolver(_get_init_members)
+    hints = dict(res.get_resolved_members(source).members)
+    hints.pop("return", None)
+    dependencies = list(hints.values())
+    if not provides:
+        provides = source
+    is_to_bind = False
+    factory_type = FactoryType.FACTORY
+    return Factory(
+        dependencies=hints_to_dependency_keys(dependencies),
+        type_=factory_type,
+        source=source,
+        scope=scope,
+        provides=DependencyKey(provides, None),
+        is_to_bind=is_to_bind,
+        cache=cache,
+    )
+
+
+def _make_factory_by_method(
+        *,
+        provides: Any,
+        scope: BaseScope | None,
+        source: Callable | classmethod,
+        cache: bool,
+) -> Factory:
+    if isinstance(source, classmethod):
+        params = signature(source.__wrapped__).parameters
+        factory_type = _guess_factory_type(source.__wrapped__)
+    else:
+        params = signature(source).parameters
+        factory_type = _guess_factory_type(source)
+
+    hints = get_type_hints(source, include_extras=True)
+    self = next(iter(params.values()), None)
+    if self:
+        if self.name not in hints:
+            # add self to dependencies, so it can be easily removed
+            # if we will bind factory to provider instance
+            hints = {self.name: Any, **hints}
+        is_to_bind = True
+    else:
+        is_to_bind = False
+    possible_dependency = hints.pop("return", None)
+    dependencies = list(hints.values())
+    if not provides:
+        provides = _clean_result_hint(factory_type, possible_dependency)
+    return Factory(
+        dependencies=hints_to_dependency_keys(dependencies),
+        type_=factory_type,
+        source=source,
+        scope=scope,
+        provides=DependencyKey(provides, None),
+        is_to_bind=is_to_bind,
+        cache=cache,
+    )
+
+
+def _make_factory_by_static_method(
+        *,
+        provides: Any,
+        scope: BaseScope | None,
+        source: staticmethod,
+        cache: bool,
+) -> Factory:
+    factory_type = _guess_factory_type(source.__wrapped__)
+    hints = get_type_hints(source, include_extras=True)
+    possible_dependency = hints.pop("return", None)
+    dependencies = list(hints.values())
+    if not provides:
+        provides = _clean_result_hint(factory_type, possible_dependency)
+    return Factory(
+        dependencies=hints_to_dependency_keys(dependencies),
+        type_=factory_type,
+        source=source,
+        scope=scope,
+        provides=DependencyKey(provides, None),
+        is_to_bind=False,
+        cache=cache,
+    )
+
+
+def _make_factory_by_other_callable(
+        *,
+        provides: Any,
+        scope: BaseScope | None,
+        source: Callable,
+        cache: bool,
+) -> Factory:
+    if _is_bound_method(source):
+        to_check = source.__func__
+    else:
+        to_check = type(source).__call__
+    factory = make_factory(
+        provides=provides,
+        source=to_check,
+        cache=cache,
+        scope=scope,
+    )
+    if factory.is_to_bind:
+        dependencies = factory.dependencies[1:]  # remove `self`
+    else:
+        dependencies = factory.dependencies
+    return Factory(
+        dependencies=dependencies,
+        type_=factory.type,
+        source=source,
+        scope=scope,
+        provides=factory.provides,
+        is_to_bind=False,
+        cache=cache,
+    )
+
+
+def make_factory(
+        *,
+        provides: Any,
+        scope: BaseScope | None,
+        source: Callable,
+        cache: bool,
+) -> Factory:
+    if is_bare_generic(source):
+        source = source[get_type_vars(source)]
+
+    if isclass(source) or get_origin(source):
+        return _make_factory_by_class(
+            provides=provides, scope=scope, source=source, cache=cache,
+        )
+    elif isfunction(source) or isinstance(source, classmethod):
+        return _make_factory_by_method(
+            provides=provides, scope=scope, source=source, cache=cache,
+        )
+    elif isinstance(source, staticmethod):
+        return _make_factory_by_static_method(
+            provides=provides, scope=scope, source=source, cache=cache,
+        )
+    elif callable(source):
+        return _make_factory_by_other_callable(
+            provides=provides, scope=scope, source=source, cache=cache,
+        )
+    else:
+        raise TypeError(f"Cannot use {type(source)} as a factory")
+
+
+def _provide(
+        *,
+        source: Callable | classmethod | staticmethod | type | None = None,
+        scope: BaseScope | None = None,
+        provides: Any = None,
+        cache: bool = True,
+) -> CompositeDependencySource:
+    composite = ensure_composite(source)
+    factory = make_factory(
+        provides=provides, scope=scope, source=composite.origin, cache=cache,
+    )
+    composite.dependency_sources.extend(unpack_factory(factory))
+    return composite
+
+
+@overload
+def provide(
+        *,
+        scope: BaseScope = None,
+        provides: Any = None,
+        cache: bool = True,
+) -> Callable[[Callable], CompositeDependencySource]:
+    ...
+
+
+@overload
+def provide(
+        source: Callable | classmethod | staticmethod | type | None,
+        *,
+        scope: BaseScope | None = None,
+        provides: Any = None,
+        cache: bool = True,
+) -> CompositeDependencySource:
+    ...
+
+
+def provide(
+        source: Callable | classmethod | staticmethod | type | None = None,
+        *,
+        scope: BaseScope | None = None,
+        provides: Any = None,
+        cache: bool = True,
+) -> Any:
+    """
+    Mark a method or class as providing some dependency.
+
+    If used as a method decorator then return annotation is used
+    to determine what is provided. User `provides` to override that.
+    Method parameters are analyzed and passed automatically.
+
+    If used with a class a first parameter than `__init__` method parameters
+    are passed automatically. If no provides is passed then it is
+    supposed that class itself is a provided dependency.
+
+    Return value must be saved as a `Provider` class attribute and
+    not intended for direct usage
+
+    :param source: Method to decorate or class.
+    :param scope: Scope of the dependency to limit its lifetime
+    :param provides: Dependency type which is provided by this factory
+    :return: instance of Factory or a decorator returning it
+    :param cache: save created object to scope cache or not
+    """
+    if source is not None:
+        return _provide(
+            provides=provides, scope=scope, source=source, cache=cache,
+        )
+
+    def scoped(func):
+        return _provide(
+            provides=provides, scope=scope, source=func, cache=cache,
+        )
+
+    return scoped
+
+
+def _context_stub():
+    raise NotImplementedError
diff --git a/src/dishka/dependency_source/unpack_provides.py b/src/dishka/dependency_source/unpack_provides.py
new file mode 100644
index 0000000..de27e40
--- /dev/null
+++ b/src/dishka/dependency_source/unpack_provides.py
@@ -0,0 +1,62 @@
+from collections.abc import Sequence
+
+from dishka.entities.key import DependencyKey
+from dishka.entities.provides_marker import ProvideMultiple
+from .alias import Alias
+from .composite import DependencySource
+from .decorator import Decorator
+from .factory import Factory
+
+
+def unpack_factory(factory: Factory) -> Sequence[DependencySource]:
+    if not isinstance(factory.provides.type_hint, ProvideMultiple):
+        return [factory]
+
+    provides_first, *provides_others = factory.provides.type_hint.items
+
+    res: list[DependencySource] = [
+        Alias(
+            provides=DependencyKey(provides_other, factory.provides.component),
+            source=DependencyKey(provides_first, factory.provides.component),
+            cache=factory.cache,
+        )
+        for provides_other in provides_others
+    ]
+    res.append(Factory(
+        dependencies=factory.dependencies,
+        type_=factory.type,
+        source=factory.source,
+        scope=factory.scope,
+        provides=DependencyKey(provides_first,
+                               factory.provides.component),
+        is_to_bind=factory.is_to_bind,
+        cache=factory.cache,
+    ))
+    return res
+
+
+def unpack_decorator(decorator: Decorator) -> Sequence[DependencySource]:
+    if not isinstance(decorator.provides.type_hint, ProvideMultiple):
+        return [decorator]
+
+    return [
+        Decorator(
+            factory=decorator.factory,
+            provides=DependencyKey(provides, decorator.provides.component),
+        )
+        for provides in decorator.provides.type_hint.items
+    ]
+
+
+def unpack_alias(alias: Alias) -> Sequence[DependencySource]:
+    if not isinstance(alias.provides.type_hint, ProvideMultiple):
+        return [alias]
+
+    return [
+        Alias(
+            provides=DependencyKey(provides, alias.provides.component),
+            source=alias.source,
+            cache=alias.cache,
+        )
+        for provides in alias.provides.type_hint.items
+    ]
diff --git a/src/dishka/entities/provides_marker.py b/src/dishka/entities/provides_marker.py
new file mode 100644
index 0000000..dacc06e
--- /dev/null
+++ b/src/dishka/entities/provides_marker.py
@@ -0,0 +1,15 @@
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from typing import Union as AnyOf
+else:
+    class AnyOf:
+        def __class_getitem__(cls, item):
+            if isinstance(item, tuple):
+                return ProvideMultiple(item)
+            return item
+
+
+class ProvideMultiple:
+    def __init__(self, items):
+        self.items = items
diff --git a/src/dishka/provider.py b/src/dishka/provider.py
index 53aee40..5c1f4c5 100644
--- a/src/dishka/provider.py
+++ b/src/dishka/provider.py
@@ -1,5 +1,5 @@
 import inspect
-from collections.abc import Callable
+from collections.abc import Callable, Sequence
 from typing import Any
 
 from dishka.entities.component import DEFAULT_COMPONENT, Component
@@ -15,11 +15,11 @@ from .dependency_source import (
     from_context,
     provide,
 )
-from .exceptions import InvalidGraphError
+from .dependency_source.composite import CompositeDependencySource
 
 
 def is_dependency_source(attribute: Any) -> bool:
-    return isinstance(attribute, DependencySource)
+    return isinstance(attribute, CompositeDependencySource)
 
 
 class BaseProvider:
@@ -54,20 +54,19 @@ class Provider(BaseProvider):
             component: Component | None = None,
     ):
         super().__init__(component)
-        self._init_dependency_sources()
         self.scope = self.scope or scope
+        self._init_dependency_sources()
 
     def _init_dependency_sources(self) -> None:
-        processed_types = {}
-
-        source: DependencySource
-        for name, source in inspect.getmembers(self, is_dependency_source):
-            if source.provides in processed_types:
-                raise InvalidGraphError(
-                    f"Type {source.provides} is registered multiple times "
-                    f"in the same {Provider} by attributes "
-                    f"{processed_types[source.provides]!r} and {name!r}",
-                )
+        sources = inspect.getmembers(self, is_dependency_source)
+        sources.sort(key=lambda s: s[1].number)
+        for name, composite in sources:
+            self._add_dependency_sources(name, composite.dependency_sources)
+
+    def _add_dependency_sources(
+            self, name: str, sources: Sequence[DependencySource],
+    ) -> None:
+        for source in sources:
             if isinstance(source, Alias):
                 self.aliases.append(source)
             if isinstance(source, Factory):
@@ -76,7 +75,6 @@ class Provider(BaseProvider):
                 self.decorators.append(source)
             if isinstance(source, ContextVariable):
                 self.context_vars.append(source)
-            processed_types[source.provides] = name
 
     def provide(
             self,
@@ -85,45 +83,45 @@ class Provider(BaseProvider):
             scope: BaseScope | None = None,
             provides: Any = None,
             cache: bool = True,
-    ) -> Factory:
+    ) -> CompositeDependencySource:
         if scope is None:
             scope = self.scope
-        new_factory = provide(
+        composite = provide(
             source=source,
             scope=scope,
             provides=provides,
             cache=cache,
         )
-        self.factories.append(new_factory)
-        return new_factory
+        self._add_dependency_sources(str(source), composite.dependency_sources)
+        return composite
 
     def alias(
             self,
             *,
             source: type,
-            provides: type,
+            provides: Any,
             cache: bool = True,
-    ) -> Alias:
-        new_alias = alias(
+    ) -> CompositeDependencySource:
+        composite = alias(
             source=source,
             provides=provides,
             cache=cache,
         )
-        self.aliases.append(new_alias)
-        return new_alias
+        self._add_dependency_sources(str(source), composite.dependency_sources)
+        return composite
 
     def decorate(
             self,
             source: Callable | type,
             *,
             provides: Any = None,
-    ) -> Decorator:
-        new_decorator = decorate(
+    ) -> CompositeDependencySource:
+        composite = decorate(
             source=source,
             provides=provides,
         )
-        self.decorators.append(new_decorator)
-        return new_decorator
+        self._add_dependency_sources(str(source), composite.dependency_sources)
+        return composite
 
     def to_component(self, component: Component) -> "ProviderWrapper":
         return ProviderWrapper(component, self)
@@ -131,9 +129,10 @@ class Provider(BaseProvider):
     def from_context(
             self, *, provides: Any, scope: BaseScope,
     ) -> ContextVariable:
-        context_var = from_context(provides=provides, scope=scope)
-        self.context_vars.append(context_var)
-        return context_var
+        composite = from_context(provides=provides, scope=scope)
+        self._add_dependency_sources(str(provides),
+                                     composite.dependency_sources)
+        return composite
 
 
 class ProviderWrapper(BaseProvider):
diff --git a/src/dishka/registry.py b/src/dishka/registry.py
index f5cc224..c326603 100644
--- a/src/dishka/registry.py
+++ b/src/dishka/registry.py
@@ -8,7 +8,6 @@ from .dependency_source import (
     ContextVariable,
     Decorator,
     Factory,
-    from_context,
 )
 from .entities.component import DEFAULT_COMPONENT, Component
 from .entities.key import DependencyKey
@@ -195,8 +194,9 @@ class RegistryBuilder:
     def _init_registries(self) -> None:
         for scope in self.scopes:
             registry = Registry(scope)
-            context_var = from_context(
-                provides=self.container_type, scope=scope,
+            context_var = ContextVariable(
+                provides=DependencyKey(self.container_type, DEFAULT_COMPONENT),
+                scope=scope,
             )
             for component in self.components:
                 registry.add_factory(context_var.as_factory(component))


Test patch:
diff --git a/tests/unit/container/test_alias.py b/tests/unit/container/test_alias.py
index 66355db..a5e3c7c 100644
--- a/tests/unit/container/test_alias.py
+++ b/tests/unit/container/test_alias.py
@@ -1,7 +1,10 @@
+from unittest.mock import Mock
+
 import pytest
 
 from dishka import (
     DEFAULT_COMPONENT,
+    AnyOf,
     DependencyKey,
     Provider,
     Scope,
@@ -50,3 +53,22 @@ def test_missing_factory():
     with pytest.raises(NoFactoryError) as e:
         make_container(p1, p2)
     assert e.value.requested == DependencyKey(int, component=DEFAULT_COMPONENT)
+
+
+def test_implicit():
+    mock = Mock(return_value=42)
+    provider = Provider(scope=Scope.APP)
+    provider.provide(source=lambda: mock(), provides=AnyOf[float, int])
+    container = make_container(provider)
+    assert container.get(float) == 42
+    assert container.get(int) == 42
+    mock.assert_called_once()
+
+
+def test_union_alias():
+    provider = Provider(scope=Scope.APP)
+    provider.provide(source=lambda: 42, provides=int)
+    provider.alias(source=int, provides=AnyOf[float, complex])
+    container = make_container(provider)
+    assert container.get(float) == 42
+    assert container.get(complex) == 42
diff --git a/tests/unit/container/test_decorator.py b/tests/unit/container/test_decorator.py
index 7a50360..120c437 100644
--- a/tests/unit/container/test_decorator.py
+++ b/tests/unit/container/test_decorator.py
@@ -10,7 +10,7 @@ from dishka import (
     make_container,
     provide,
 )
-from dishka.exceptions import InvalidGraphError, NoFactoryError
+from dishka.exceptions import NoFactoryError
 
 
 class A:
@@ -97,14 +97,14 @@ def test_alias():
     assert a is a1
 
 
-def test_double_error():
+def test_double():
     class MyProvider(Provider):
         a = provide(A, scope=Scope.APP)
         ad = decorate(ADecorator, provides=A)
         ad2 = decorate(ADecorator, provides=A)
 
-    with pytest.raises(InvalidGraphError):
-        MyProvider()
+    provider = MyProvider()
+    assert len(provider.decorators) == 2
 
 
 def test_double_ok():
diff --git a/tests/unit/container/test_resolve.py b/tests/unit/container/test_resolve.py
index ff89022..8b3aef3 100644
--- a/tests/unit/container/test_resolve.py
+++ b/tests/unit/container/test_resolve.py
@@ -16,7 +16,7 @@ from ..sample_providers import (
     sync_func_a,
     sync_gen_a,
     sync_iter_a,
-    value_factory,
+    value_source,
 )
 
 
@@ -74,9 +74,21 @@ async def test_async(factory, closed):
     assert a.closed == closed
 
 
+def test_2decorators():
+    class MyProvider(Provider):
+        @provide(scope=Scope.APP)
+        @provide(provides=float, scope=Scope.APP)
+        def get(self) -> int:
+            return 100
+
+    container = make_container(MyProvider())
+    assert container.get(float) == 100
+    assert container.get(int) == 100
+
+
 def test_value():
     class MyProvider(Provider):
-        factory = value_factory
+        factory = value_source
 
     container = make_container(MyProvider())
     assert container.get(ClassA) is A_VALUE
@@ -85,7 +97,7 @@ def test_value():
 @pytest.mark.asyncio
 async def test_value_async():
     class MyProvider(Provider):
-        factory = value_factory
+        factory = value_source
 
     container = make_async_container(MyProvider())
     assert await container.get(ClassA) is A_VALUE
diff --git a/tests/unit/sample_providers.py b/tests/unit/sample_providers.py
index 078a29f..3390953 100644
--- a/tests/unit/sample_providers.py
+++ b/tests/unit/sample_providers.py
@@ -10,6 +10,7 @@ from typing import Any
 
 from dishka import DependencyKey, Scope
 from dishka.dependency_source import Factory, FactoryType
+from dishka.dependency_source.composite import CompositeDependencySource
 
 
 class ClassA:
@@ -72,3 +73,4 @@ value_factory = Factory(
     is_to_bind=False,
     cache=False,
 )
+value_source = CompositeDependencySource(lambda: None, [value_factory])
diff --git a/tests/unit/test_composite.py b/tests/unit/test_composite.py
new file mode 100644
index 0000000..f646412
--- /dev/null
+++ b/tests/unit/test_composite.py
@@ -0,0 +1,11 @@
+from dishka.dependency_source.composite import ensure_composite
+
+
+def test_composite():
+    class A:
+        @ensure_composite
+        def foo(self, a, b):
+            return a + b
+
+    a = A()
+    assert a.foo(1, 2) == 3
diff --git a/tests/unit/test_provider.py b/tests/unit/test_provider.py
index 39fc185..ac26fe3 100644
--- a/tests/unit/test_provider.py
+++ b/tests/unit/test_provider.py
@@ -4,6 +4,7 @@ import pytest
 
 from dishka import Provider, Scope, alias, provide
 from dishka.dependency_source import FactoryType
+from dishka.dependency_source.make_factory import make_factory
 from dishka.entities.key import (
     hint_to_dependency_key,
     hints_to_dependency_keys,
@@ -48,7 +49,10 @@ def test_provider_init():
     ],
 )
 def test_parse_factory(source, provider_type, is_to_bound):
-    factory = provide(source, scope=Scope.REQUEST)
+    composite = provide(source, scope=Scope.REQUEST)
+    assert len(composite.dependency_sources) == 1
+    factory = composite.dependency_sources[0]
+
     assert factory.provides == hint_to_dependency_key(ClassA)
     assert factory.dependencies == hints_to_dependency_keys([Any, int])
     assert factory.is_to_bind == is_to_bound
@@ -80,7 +84,12 @@ def test_parse_factory_invalid_hint_async():
     ],
 )
 def test_parse_factory_cls(source, provider_type, is_to_bound):
-    factory = provide(source, scope=Scope.REQUEST)
+    factory = make_factory(
+        provides=None,
+        source=source,
+        cache=False,
+        scope=Scope.REQUEST,
+    )
     assert factory.provides == hint_to_dependency_key(ClassA)
     assert factory.dependencies == hints_to_dependency_keys([int])
     assert factory.is_to_bind == is_to_bound
@@ -98,7 +107,9 @@ def test_provider_class_scope():
             return f"{x}"
 
     provider = MyProvider()
-    assert provider.foo.scope == Scope.REQUEST
+    assert len(provider.factories) == 1
+    factory = provider.factories[0]
+    assert factory.scope == Scope.REQUEST
 
 
 def test_provider_instance_scope():
@@ -108,7 +119,9 @@ def test_provider_instance_scope():
             return f"{x}"
 
     provider = MyProvider(scope=Scope.REQUEST)
-    assert provider.foo.scope == Scope.REQUEST
+    assert len(provider.factories) == 1
+    factory = provider.factories[0]
+    assert factory.scope == Scope.REQUEST
 
 
 def test_provider_instance_braces():
@@ -118,7 +131,9 @@ def test_provider_instance_braces():
             return f"{x}"
 
     provider = MyProvider(scope=Scope.REQUEST)
-    assert provider.foo.scope == Scope.REQUEST
+    assert len(provider.factories) == 1
+    factory = provider.factories[0]
+    assert factory.scope == Scope.REQUEST
 
 
 def test_self_hint():
@@ -128,7 +143,9 @@ def test_self_hint():
             return "hello"
 
     provider = MyProvider(scope=Scope.REQUEST)
-    assert not provider.foo.dependencies
+    assert len(provider.factories) == 1
+    factory = provider.factories[0]
+    assert not factory.dependencies
 
 
 def test_staticmethod():
@@ -139,7 +156,9 @@ def test_staticmethod():
             return "hello"
 
     provider = MyProvider(scope=Scope.REQUEST)
-    assert not provider.foo.dependencies
+    assert len(provider.factories) == 1
+    factory = provider.factories[0]
+    assert not factory.dependencies
 
 
 def test_classmethod():
@@ -150,7 +169,9 @@ def test_classmethod():
             return "hello"
 
     provider = MyProvider(scope=Scope.REQUEST)
-    assert not provider.foo.dependencies
+    assert len(provider.factories) == 1
+    factory = provider.factories[0]
+    assert not factory.dependencies
 
 
 class MyCallable:
@@ -163,27 +184,39 @@ def test_callable():
         foo = provide(MyCallable())
 
     provider = MyProvider(scope=Scope.REQUEST)
-    assert provider.foo.provides == hint_to_dependency_key(str)
-    assert provider.foo.dependencies == hints_to_dependency_keys([int])
+    assert len(provider.factories) == 1
+    factory = provider.factories[0]
+    assert factory.provides == hint_to_dependency_key(str)
+    assert factory.dependencies == hints_to_dependency_keys([int])
 
 
 def test_provide_as_method():
     provider = Provider(scope=Scope.REQUEST)
+
     foo = provider.provide(MyCallable())
-    assert foo.provides == hint_to_dependency_key(str)
-    assert foo.dependencies == hints_to_dependency_keys([int])
+    assert len(provider.factories) == 1
+    assert len(foo.dependency_sources) == 1
+    factory = foo.dependency_sources[0]
+    assert factory.provides == hint_to_dependency_key(str)
+    assert factory.dependencies == hints_to_dependency_keys([int])
 
     foo = provider.provide(sync_func_a)
-    assert foo.provides == hint_to_dependency_key(ClassA)
-    assert foo.dependencies == hints_to_dependency_keys([Any, int])
+    assert len(foo.dependency_sources) == 1
+    factory = foo.dependency_sources[0]
+    assert factory.provides == hint_to_dependency_key(ClassA)
+    assert factory.dependencies == hints_to_dependency_keys([Any, int])
 
     foo = provider.alias(source=int, provides=str)
-    assert foo.provides == hint_to_dependency_key(str)
-    assert foo.source == hint_to_dependency_key(int)
+    assert len(foo.dependency_sources) == 1
+    factory = foo.dependency_sources[0]
+    assert factory.provides == hint_to_dependency_key(str)
+    assert factory.source == hint_to_dependency_key(int)
 
     foo = provider.decorate(sync_func_a)
-    assert foo.provides == hint_to_dependency_key(ClassA)
-    assert foo.factory.dependencies == hints_to_dependency_keys([Any, int])
+    assert len(foo.dependency_sources) == 1
+    factory = foo.dependency_sources[0]
+    assert factory.provides == hint_to_dependency_key(ClassA)
+    assert factory.factory.dependencies == hints_to_dependency_keys([Any, int])
 
 
 class OtherClass:
@@ -194,5 +227,7 @@ class OtherClass:
 def test_provide_external_method():
     provider = Provider(scope=Scope.REQUEST)
     foo = provider.provide(OtherClass().method)
-    assert foo.provides == hint_to_dependency_key(str)
-    assert foo.dependencies == []
+    assert len(foo.dependency_sources) == 1
+    factory = foo.dependency_sources[0]
+    assert factory.provides == hint_to_dependency_key(str)
+    assert factory.dependencies == []
diff --git a/tests/unit/test_render.py b/tests/unit/test_render.py
index 8b04d94..e93f304 100644
--- a/tests/unit/test_render.py
+++ b/tests/unit/test_render.py
@@ -2,7 +2,7 @@ import textwrap
 
 import pytest
 
-from dishka import provide
+from dishka.dependency_source.make_factory import make_factory
 from dishka.error_rendering import PathRenderer
 
 
@@ -18,7 +18,12 @@ def renderer():
 
 def test_cycle(renderer):
     foo = A().foo
-    factory = provide(foo)
+    factory = make_factory(
+        provides=None,
+        source=foo,
+        cache=False,
+        scope=None,
+    )
 
     res = renderer.render([factory, factory, factory])
     res = textwrap.dedent(res)
@@ -32,7 +37,12 @@ def test_cycle(renderer):
 
 def test_cycle_1(renderer):
     foo = A().foo
-    factory = provide(foo)
+    factory = make_factory(
+        provides=None,
+        source=foo,
+        cache=False,
+        scope=None,
+    )
 
     res = renderer.render([factory])
     res = textwrap.dedent(res)
@@ -44,7 +54,12 @@ def test_cycle_1(renderer):
 
 def test_linear(renderer):
     foo = A().foo
-    factory = provide(foo)
+    factory = make_factory(
+        provides=None,
+        source=foo,
+        cache=False,
+        scope=None,
+    )
 
     res = renderer.render([factory, factory], last=factory.provides)
     res = textwrap.dedent(res)