Repository: reagento/dishka
Pull request #439 (2025-04-17 21:45:45)

Add literal args type checking.
Hello!

I inserted literal checking and MRE from issue #423 is now not falling in `_TypeMatcher` anymore. But instead got this :D

```
  File ".../dishka/src/dishka/container.py", line 182, in _get_unlocked
    raise NoFactoryError(key)
dishka.exceptions.NoFactoryError: Cannot find factory for (Literal[a], component=''). It is missing or has invalid scope.
      ~~~ component='', Scope.APP ~~~
   ↓  __main__.B[Literal[a]]   alias
   ↓  __main__.A[Literal[a]]   MyProvider.a
    → typing.Literal[a]        ???
```  

Is this error related to question in Issue? I'm asking, because all tests passes successful. 


Patch:
diff --git a/src/dishka/dependency_source/type_match.py b/src/dishka/dependency_source/type_match.py
index 932613d..29df20f 100644
--- a/src/dishka/dependency_source/type_match.py
+++ b/src/dishka/dependency_source/type_match.py
@@ -1,8 +1,15 @@
 from __future__ import annotations
 
 import sys
-from collections.abc import Sequence
-from typing import Any, ForwardRef, TypeVar, get_args, get_origin
+from collections.abc import Iterable, Sequence
+from typing import (
+    Any,
+    ForwardRef,
+    Literal,
+    TypeVar,
+    get_args,
+    get_origin,
+)
 
 from dishka._adaptix.type_tools.basic_utils import eval_forward_ref, is_generic
 from dishka.exception_base import DishkaError
@@ -55,8 +62,13 @@ class _TypeMatcher:
         if get_origin(bound1):
             raise UnsupportedGenericBoundsError(t1)
         if not isinstance(t2, TypeVar):
-            if get_origin(t2):
-                t2 = get_origin(t2)
+            origin2 = get_origin(t2)
+            if origin2 == Literal:
+                if self._is_literal_same_types(t2, [bound1]):
+                    return True
+                return False
+            if origin2:
+                t2 = origin2
             return issubclass(t2, bound1)
         if t2.__bound__:
             bound2 = eval_maybe_forward(t2.__bound__, t2)
@@ -66,6 +78,11 @@ class _TypeMatcher:
     def _is_constraint_broader(self, t1: TypeVar, t2: Any) -> bool:
         constraints1 = eval_maybe_forward_many(t1.__constraints__, t1)
         if not isinstance(t2, TypeVar):
+            origin2 = get_origin(t2)
+            if origin2 == Literal:
+                if self._is_literal_same_types(t2, constraints1):
+                    return True
+                return False
             return t2 in constraints1
         if t2.__constraints__:
             constraints2 = eval_maybe_forward_many(t2.__constraints__, t2)
@@ -85,6 +102,17 @@ class _TypeMatcher:
             return self._is_constraint_broader(t1, t2)
         return False
 
+    def _is_literal_same_types(
+        self, t_literal: Any, types_to_check: Iterable[Any],
+    ) -> bool:
+        literal_args = get_args(t_literal)
+        if len(literal_args) > 1 or len(literal_args) == 0:
+            return False
+        return any(
+            isinstance(literal_args[0], type_to_check)
+            for type_to_check in types_to_check
+        )
+
     def is_broader_or_same_type(self, t1: Any, t2: Any) -> bool:
         if t1 == t2:
             return True


Test patch:
diff --git a/tests/unit/test_type_match.py b/tests/unit/test_type_match.py
index cc5651a..6b0215f 100644
--- a/tests/unit/test_type_match.py
+++ b/tests/unit/test_type_match.py
@@ -1,4 +1,4 @@
-from typing import Generic, TypeVar
+from typing import Generic, Literal, TypeVar
 
 import pytest
 
@@ -7,6 +7,8 @@ from dishka.dependency_source.decorator import is_broader_or_same_type
 T = TypeVar("T")
 T2 = TypeVar("T2")
 T3 = TypeVar("T3")
+T4 = TypeVar("T4", bound=str)
+T5 = TypeVar("T5", int, str)
 
 
 class AGeneric(Generic[T]): ...
@@ -30,6 +32,12 @@ class SubC(C): ...
 class D: ...
 
 
+class CGeneric(Generic[T4]): ...
+
+
+class DGeneric(Generic[T5]): ...
+
+
 TC = TypeVar("TC", bound=C)
 TCD = TypeVar("TCD", C, D)
 TSubCCD = TypeVar("TSubCCD", "C", SubC, D)
@@ -62,6 +70,12 @@ TSubCCD = TypeVar("TSubCCD", "C", SubC, D)
             Mutiple[AGeneric[int], AGeneric[AGeneric[str]], T2],
             False,
         ),
+        (CGeneric[T4], CGeneric[Literal["a"]], True),
+        (CGeneric[T4], CGeneric[Literal[1]], False),
+        (DGeneric[T5], DGeneric[Literal[1]], True),
+        (DGeneric[T5], DGeneric[Literal["a"]], True),
+        (DGeneric[T5], DGeneric[Literal[True]], True),
+        (DGeneric[T5], DGeneric[Literal["a", 1]], False),
     ],
 )
 def test_is_broader_or_same_type(*, first: T, second: T, match: bool):