When creating a derived class of CallbackDict, I am unable to pass an on_update function annotated with the derived type and pass type checks with mypy or pyright.
The following is the derived class in question and associated on_update function (which has to be separate from the class to pass type checks due to a seeming quirk of UpdateDictMixin but that's a separate issue):
from flask.sessions import SessionMixin
from typing import Union, Iterable, Tuple, Mapping, Optional, Any
from werkzeug.datastructures import CallbackDict
class MongoSession(CallbackDict, SessionMixin):
def __init__(
self,
initial: Union[Mapping, Iterable[Tuple[Any, Any]], None] = None,
sid: Optional[str] = None
):
super(MongoSession, self).__init__(initial, on_update)
self.sid = sid
self.modified = False
def on_update(self: MongoSession) -> None:
self.modified = True
Currently, mypy type checking will raise the following error here:
.../mongo_sessions.py:32: error: Argument 2 to "__init__" of "CallbackDict" has incompatible type "Callable[[MongoSession], None]"; expected "Optional[Callable[[CallbackDict[Any, Any]], None]]"
This is due to the specific use of CallbackDict in the datastructures.pyi file for the on_update parameter:
class CallbackDict(UpdateDictMixin[K, V], Dict[K, V]):
def __init__(
self,
initial: Optional[Union[Mapping[K, V], Iterable[Tuple[K, V]]]] = None,
on_update: Optional[Callable[[CallbackDict], None]] = None,
) -> None: ...
This form of typing is interpreted as invariant, meaning that I have to pass in a function that specifically takes a CallbackDict and not a derived type.
In such a case I would not be able to modify properties on the derived class in on_update without incurring other typing errors (e.g. modified is not a property on CallbackDict nor UpdateDictMixin, but is in my derived class).
I would expect that I should be able to pass in an on_update function that is annotated to accept a derived class to perform more specific updates on.
This could be addressed by updating the datastructure.pyi file to use a _CD = TypeVar("_CD", bound="CallbackDict") instead of a concrete, invariant type annotation:
_CD = TypeVar("_CD", bound="CallbackDict")
class CallbackDict(UpdateDictMixin[K, V], Dict[K, V]):
def __init__(
self,
initial: Optional[Union[Mapping[K, V], Iterable[Tuple[K, V]]]] = None,
on_update: Optional[Callable[[_CD], None]] = None,
) -> None: ...
I have tested this locally and it shows correctly rejecting a non-CallbackDict derived type annotation, but does accept derived types like the above MongoSession.
Environment:
- Python version: 3.6.8
- Werkzeug version: 2.0.1
When creating a derived class of
CallbackDict, I am unable to pass anon_updatefunction annotated with the derived type and pass type checks with mypy or pyright.The following is the derived class in question and associated
on_updatefunction (which has to be separate from the class to pass type checks due to a seeming quirk ofUpdateDictMixinbut that's a separate issue):Currently, mypy type checking will raise the following error here:
This is due to the specific use of
CallbackDictin the datastructures.pyi file for theon_updateparameter:This form of typing is interpreted as invariant, meaning that I have to pass in a function that specifically takes a
CallbackDictand not a derived type.In such a case I would not be able to modify properties on the derived class in
on_updatewithout incurring other typing errors (e.g.modifiedis not a property onCallbackDictnorUpdateDictMixin, but is in my derived class).I would expect that I should be able to pass in an
on_updatefunction that is annotated to accept a derived class to perform more specific updates on.This could be addressed by updating the
datastructure.pyifile to use a_CD = TypeVar("_CD", bound="CallbackDict")instead of a concrete, invariant type annotation:I have tested this locally and it shows correctly rejecting a non-
CallbackDictderived type annotation, but does accept derived types like the aboveMongoSession.Environment: