I am trying to implement the chain of responsibility pattern in python by passing a Request
object into a handler "chain". The Request
object passed in to each handler method in sequence has a few optional fields and some union types.
The general pattern I have for these handlers is to create a few boolean values at the top of the method to check if the current handler method should handle the request and if not, return early by calling the next handler in line.
The following is an example of my setup:
from pathlib import Path
from typing import Any, Callable
class Request:
data: int | Any | None = None
location: str | Path | None = None
def handle_int(request: Request, next_handler: Callable[[Request], int]) -> int:
is_int = isinstance(request.data, int)
is_str = isinstance(request.location, int)
if not (is_int and is_str):
return next_handler(request)
a = request.location.split("_")
return request.data * 3
The issue I am having is that mypy catches union-attr
errors whenever I access the attributes on the request object:
test_mypy.py:17:9: error: Item "Path" of "str | Path | None" has no attribute "split" [union-attr]
test_mypy.py:17:9: error: Item "None" of "str | Path | None" has no attribute "split" [union-attr]
test_mypy.py:19:12: error: Unsupported operand types for * ("None" and "int") [operator]
test_mypy.py:19:12: note: Left operand is of type "int | Any | None"
I can get rid of these by adding assertions after the return statement but that is creating basically duplicate code for the predicate checks before the early return and feels like a bad pattern.
Apart from creating more specific request types is there a way to let mypy know that the fact we are after the early return means that checks have been done on the form of the attributes n the request already?
mypy
is just getting confused because the type narrowing of isinstance
cannot be propagated through variables. It has to be done directly in the if
.
Use
if not (isinstance(request.data, int) and isinstance(request.location, str)):
return next_handler(request)
Instead of
is_int = isinstance(request.data, int)
is_str = isinstance(request.location, str)
if not (is_int and is_str):
return next_handler(request)