Skip to content

Commit

Permalink
Merge c52b6aa into a294be6
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed Apr 17, 2024
2 parents a294be6 + c52b6aa commit d1e33b4
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 40 deletions.
39 changes: 23 additions & 16 deletions src/coaster/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,36 @@

from __future__ import annotations

from typing import Any, Callable, Protocol, TypeVar
from typing_extensions import Concatenate, ParamSpec
from typing import Any, Callable, Protocol, TypeVar, overload
from typing_extensions import ParamSpec, Self

# These two are obsolete and should be replaced with ParamSpec-based definitions
WrappedFunc = TypeVar('WrappedFunc', bound=Callable)
#: Return type for decorator factories
ReturnDecorator = Callable[[WrappedFunc], WrappedFunc]

#: Recurring use ParamSpec
_P = ParamSpec('_P')
#: Recurring use type spec
_T = TypeVar('_T')
_T_contra = TypeVar('_T_contra', contravariant=True)
_R_co = TypeVar('_R_co', covariant=True)


class MethodProtocol(Protocol[_P, _T]):
"""
Protocol that matches a method without also matching against a type constructor.
class BoundMethod(Protocol[_T_contra, _P, _R_co]):
"""Protocol for a bound instance method. See :class:`Method` for use."""

Replace ``Callable[Concatenate[Any, P], R]`` with ``MethodProtocol[Concatenate[Any,
P], R]``. This is needed because the typeshed defines ``type.__call__``, so any type
will also validate as a callable. Mypy special-cases callable protocols as not
matching ``type.__call__`` in https://github.com/python/mypy/pull/14121.
"""
# pylint: disable=no-self-argument
def __call__(
__self, self: _T_contra, *args: _P.args, **kwargs: _P.kwargs
) -> _R_co: ...

# Using ``def __call__`` seems to break Mypy, so we use this hack
# https://github.com/python/typing/discussions/1312#discussioncomment-4416217
__call__: Callable[Concatenate[Any, _P], _T]

class Method(Protocol[_P, _R_co]):
"""Protocol for an instance method."""

# pylint: disable=no-self-argument
def __call__(__self, self: Any, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ...

@overload
def __get__(self, obj: None, cls: type[_T]) -> Self: ...

@overload
def __get__(self, obj: _T, cls: type[_T]) -> BoundMethod[_T, _P, _R_co]: ...
34 changes: 10 additions & 24 deletions src/coaster/views/classview.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@

from ..auth import add_auth_attribute, current_auth
from ..sqlalchemy import Query, UrlForMixin
from ..typing import MethodProtocol, ReturnDecorator, WrappedFunc
from ..typing import Method, ReturnDecorator, WrappedFunc
from ..utils import InspectableSet
from .misc import ensure_sync

Expand Down Expand Up @@ -106,15 +106,13 @@ def __call__(self, __decorated: ClassViewType) -> ClassViewType: ...
def __call__(self, __decorated: ViewMethod[_P, _R_co]) -> ViewMethod[_P, _R_co]: ...

@overload
def __call__(
self, __decorated: MethodProtocol[Concatenate[Any, _P], _R_co]
) -> ViewMethod[_P, _R_co]: ...
def __call__(self, __decorated: Method[_P, _R_co]) -> ViewMethod[_P, _R_co]: ...

def __call__( # skipcq: PTC-W0049
self,
__decorated: Union[
ClassViewType,
MethodProtocol[Concatenate[Any, _P], _R_co],
Method[_P, _R_co],
ViewMethod[_P, _R_co],
],
) -> Union[ClassViewType, ViewMethod[_P, _R_co]]: ...
Expand All @@ -127,15 +125,11 @@ class ViewDataDecoratorProtocol(Protocol):
def __call__(self, __decorated: ViewMethod[_P, _R_co]) -> ViewMethod[_P, _R_co]: ...

@overload
def __call__(
self, __decorated: MethodProtocol[Concatenate[Any, _P], _R_co]
) -> ViewMethod[_P, _R_co]: ...
def __call__(self, __decorated: Method[_P, _R_co]) -> ViewMethod[_P, _R_co]: ...

def __call__( # skipcq: PTC-W0049
self,
__decorated: Union[
MethodProtocol[Concatenate[Any, _P], _R_co], ViewMethod[_P, _R_co]
],
__decorated: Union[Method[_P, _R_co], ViewMethod[_P, _R_co]],
) -> ViewMethod[_P, _R_co]: ...


Expand Down Expand Up @@ -201,14 +195,12 @@ def decorator(decorated: ClassViewType) -> ClassViewType: ...
def decorator(decorated: ViewMethod[_P, _R_co]) -> ViewMethod[_P, _R_co]: ...

@overload
def decorator(
decorated: MethodProtocol[Concatenate[Any, _P], _R_co]
) -> ViewMethod[_P, _R_co]: ...
def decorator(decorated: Method[_P, _R_co]) -> ViewMethod[_P, _R_co]: ...

def decorator(
decorated: Union[
ClassViewType,
MethodProtocol[Concatenate[Any, _P], _R_co],
Method[_P, _R_co],
ViewMethod[_P, _R_co],
]
) -> Union[ClassViewType, ViewMethod[_P, _R_co]]:
Expand Down Expand Up @@ -244,14 +236,10 @@ def viewdata(**kwargs: Any) -> ViewDataDecoratorProtocol:
def decorator(decorated: ViewMethod[_P, _R_co]) -> ViewMethod[_P, _R_co]: ...

@overload
def decorator(
decorated: MethodProtocol[Concatenate[Any, _P], _R_co]
) -> ViewMethod[_P, _R_co]: ...
def decorator(decorated: Method[_P, _R_co]) -> ViewMethod[_P, _R_co]: ...

def decorator(
decorated: Union[
ViewMethod[_P, _R_co], MethodProtocol[Concatenate[Any, _P], _R_co]
]
decorated: Union[ViewMethod[_P, _R_co], Method[_P, _R_co]]
) -> ViewMethod[_P, _R_co]:
return ViewMethod(decorated, data=kwargs)

Expand Down Expand Up @@ -348,9 +336,7 @@ def __repr__(self) -> str:

def replace(
self,
__f: Union[
ViewMethod[_P2, _R2_co], MethodProtocol[Concatenate[Any, _P2], _R2_co]
],
__f: Union[ViewMethod[_P2, _R2_co], Method[_P2, _R2_co]],
) -> ViewMethod[_P2, _R2_co]:
"""
Replace a view method in a subclass while keeping its URL routes.
Expand Down

0 comments on commit d1e33b4

Please sign in to comment.