Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions docs/provider/provide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,7 @@ It works similar to :ref:`alias`.
return A()

container = make_async_container(MyProvider())
a = await container.get(A)
a = await container.get(AImpl)
a is a # True
Comment thread
Tishka17 marked this conversation as resolved.
await container.get(A)


WithParents generates only one factory and many aliases and is equivalent to ``AnyOf[AImpl, A]``. The following parents are ignored: ``type``, ``object``, ``Enum``, ``ABC``, ``ABCMeta``, ``Generic``, ``Protocol``, ``Exception``, ``BaseException``
Expand Down
2 changes: 2 additions & 0 deletions src/dishka/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"provide",
"provide_all",
"new_scope",
"WithProtocols",
"ValidationSettings",
"STRICT_VALIDATION",
]
Expand All @@ -39,4 +40,5 @@
from .entities.scope import BaseScope, Scope, new_scope
from .entities.validation_settigs import STRICT_VALIDATION, ValidationSettings
from .entities.with_parents import WithParents
from .entities.with_protocols import WithProtocols
from .provider import Provider
6 changes: 3 additions & 3 deletions src/dishka/dependency_source/make_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ def _make_factory_by_class(
hints = dict(res.get_resolved_members(source).members)
except NameError as e:
name = get_name(source, include_module=True) + ".__init__"
raise NameError(
raise NameError( # type: ignore[call-arg]
f"Failed to analyze `{name}`. \n"
f"Type '{e.name}' is not defined\n\n"
f"If your are using `if TYPE_CHECKING` to import '{e.name}' "
Expand Down Expand Up @@ -279,7 +279,7 @@ def _make_factory_by_function(
hints = get_type_hints(source, include_extras=True)
except NameError as e:
name = get_name(source, include_module=True)
raise NameError(
raise NameError( # type: ignore[call-arg]
f"Failed to analyze `{name}`. \n"
f"Type '{e.name}' is not defined. \n\n"
f"If your are using `if TYPE_CHECKING` to import '{e.name}' "
Expand Down Expand Up @@ -345,7 +345,7 @@ def _make_factory_by_static_method(
hints = get_type_hints(source, include_extras=True)
except NameError as e:
name = get_name(source, include_module=True)
raise NameError(
raise NameError( # type: ignore[call-arg]
f"Failed to analyze `{name}`. \n"
f"Type '{e.name}' is not defined. \n\n"
f"If your are using `if TYPE_CHECKING` to import '{e.name}' "
Expand Down
5 changes: 4 additions & 1 deletion src/dishka/entities/with_parents.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

__all__ = ["WithParents", "ParentsResolver"]

from dishka.text_rendering import get_name

IGNORE_TYPES: Final = (
type,
object,
Expand Down Expand Up @@ -84,8 +86,9 @@ def create_type_vars_map(obj: TypeHint) -> dict[TypeHint, TypeHint]:
class ParentsResolver:
def get_parents(self, child_type: TypeHint) -> list[TypeHint]:
if is_ignored_type(strip_alias(child_type)):
name = get_name(child_type, include_module=False)
raise ValueError(
f"The starting class {child_type!r} is in ignored types",
f"The starting class {name} is in ignored types",
)
if is_parametrized(child_type) or has_orig_bases(child_type):
return self._get_parents_for_generic(child_type)
Expand Down
42 changes: 42 additions & 0 deletions src/dishka/entities/with_protocols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
__all__ = ["WithProtocols"]

from typing import TYPE_CHECKING, TypeVar

from dishka._adaptix.common import TypeHint
from dishka._adaptix.type_tools import is_protocol, strip_alias
from dishka.entities.provides_marker import ProvideMultiple
from dishka.entities.with_parents import ParentsResolver
from dishka.text_rendering import get_name


def get_parents_protocols(type_hint: TypeHint) -> list[TypeHint]:
parents = ParentsResolver().get_parents(type_hint)
new_parents = [
parent for parent in parents
if is_protocol(strip_alias(parent))
]
if new_parents:
return new_parents

name = get_name(type_hint, include_module=False)
error_msg = (
f"Not a single parent of the protocol was found in {name}.\n"
"Hint:\n"
f" * Maybe you meant just {name}, not WithProtocols[{name}]\n"
)
if len(parents) > 1:
error_msg += f" * Perhaps you meant WithParents[{name}]?"
raise ValueError(error_msg)


T = TypeVar("T")
if TYPE_CHECKING:
from typing import Union
WithProtocols = Union[T, T] # noqa: UP007,PYI016
else:
class WithProtocols:
def __class_getitem__(cls, item: TypeHint) -> TypeHint:
parents = get_parents_protocols(item)
if len(parents) > 1:
return ProvideMultiple(parents)
return parents[0]
2 changes: 1 addition & 1 deletion src/dishka/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def _get_type_var_factory(self, dependency: DependencyKey) -> Factory:
scope=self.scope,
dependencies=[],
kw_dependencies={},
provides=DependencyKey(type[typevar], dependency.component),
provides=DependencyKey(type[typevar], dependency.component), # type: ignore[misc]
type_=FactoryType.FACTORY,
is_to_bind=False,
cache=False,
Expand Down
38 changes: 38 additions & 0 deletions tests/unit/container/test_with_protocols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from typing import Protocol

import pytest

from dishka import make_container, Provider, Scope, WithProtocols
from dishka.exceptions import NoFactoryError


class AProtocol(Protocol):
pass


class BProtocol(Protocol):
pass


class C(AProtocol, BProtocol):
pass


def test_get_parents_protocols() -> None:
provider = Provider(scope=Scope.APP)
provider.provide(C, provides=WithProtocols[C])
container = make_container(provider)

assert (
container.get(BProtocol)
is container.get(AProtocol)
)


def test_get_by_not_protocol() -> None:
provider = Provider(scope=Scope.APP)
provider.provide(C, provides=WithProtocols[C])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need either another naming or another logic.
For WithParents we provide class itself together with parents. The same is expected here if we keep the prefix With

container = make_container(provider)

with pytest.raises(NoFactoryError):
container.get(C)