Repository: aio-libs/aiohttp

Issue:
mypy AbstractMatchInfo has no attribute "route"
When running mypy, I get the error:
`error: "AbstractMatchInfo" has no attribute "route"  [attr-defined]`

From this code:
```
        match_info = await request.app.router.resolve(request_clone)
        if isinstance(match_info.route, web.ResourceRoute):
```

But, looking at the code, it looks to me like everything is actually returning a `UrlMappingMatchInfo`, which does have this attribute.

Would it make sense to just change the return to `UrlMappingMatchInfo`? I can't see why you would get another type returned from this function.


Discussion:
Sorry for the late response.

Historically, I thought that AbstractMatchInfo can work with custom URL resolvers in additional to the default UrlDispatcher.
For example, sometimes a traversal-based resolver can look attractive.

The idea has failed for several reasons but the ABC class was not updated.
Please feel free to publish a PR.

Resolving patch:
diff --git a/aiohttp/web_app.py b/aiohttp/web_app.py
--- a/aiohttp/web_app.py
+++ b/aiohttp/web_app.py
@@ -482,7 +482,7 @@ async def _handle(self, request: Request) -> StreamResponse:
         match_info.freeze()
 
         resp = None
-        request._match_info = match_info  # type: ignore[assignment]
+        request._match_info = match_info
         expect = request.headers.get(hdrs.EXPECT)
         if expect:
             resp = await match_info.expect_handler(request)
diff --git a/aiohttp/web_middlewares.py b/aiohttp/web_middlewares.py
--- a/aiohttp/web_middlewares.py
+++ b/aiohttp/web_middlewares.py
@@ -22,7 +22,7 @@ async def _check_request_resolves(request: Request, path: str) -> Tuple[bool, Re
     alt_request = request.clone(rel_url=path)
 
     match_info = await request.app.router.resolve(alt_request)
-    alt_request._match_info = match_info  # type: ignore[assignment]
+    alt_request._match_info = match_info
 
     if match_info.http_exception is None:
         return True, alt_request
diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py
--- a/aiohttp/web_urldispatcher.py
+++ b/aiohttp/web_urldispatcher.py
@@ -85,7 +85,7 @@
 
 
 _ExpectHandler = Callable[[Request], Awaitable[None]]
-_Resolve = Tuple[Optional[AbstractMatchInfo], Set[str]]
+_Resolve = Tuple[Optional["UrlMappingMatchInfo"], Set[str]]
 
 
 class _InfoDict(TypedDict, total=False):
@@ -1000,7 +1000,7 @@ def __init__(self) -> None:
         self._resources = []  # type: List[AbstractResource]
         self._named_resources = {}  # type: Dict[str, AbstractResource]
 
-    async def resolve(self, request: Request) -> AbstractMatchInfo:
+    async def resolve(self, request: Request) -> UrlMappingMatchInfo:
         method = request.method
         allowed_methods = set()  # type: Set[str]
 
@@ -1010,11 +1010,11 @@ async def resolve(self, request: Request) -> AbstractMatchInfo:
                 return match_dict
             else:
                 allowed_methods |= allowed
+
+        if allowed_methods:
+            return MatchInfoError(HTTPMethodNotAllowed(method, allowed_methods))
         else:
-            if allowed_methods:
-                return MatchInfoError(HTTPMethodNotAllowed(method, allowed_methods))
-            else:
-                return MatchInfoError(HTTPNotFound())
+            return MatchInfoError(HTTPNotFound())
 
     def __iter__(self) -> Iterator[str]:
         return iter(self._named_resources)


Test patch:
diff --git a/requirements/test.txt b/requirements/test.txt
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -1,4 +1,3 @@
-
 -r base.txt
 Brotli==1.0.9
 coverage==6.0.2