2021-03-29 03:24:13 +05:30
|
|
|
# Released under the MIT License. See LICENSE for details.
|
|
|
|
|
#
|
|
|
|
|
"""Call related functionality shared between all efro components."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from typing import TYPE_CHECKING, TypeVar, Generic, Callable, cast
|
2021-11-10 17:26:07 +05:30
|
|
|
import functools
|
2021-03-29 03:24:13 +05:30
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from typing import Any, overload
|
|
|
|
|
|
|
|
|
|
CT = TypeVar('CT', bound=Callable)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _CallbackCall(Generic[CT]):
|
|
|
|
|
"""Descriptor for exposing a call with a type defined by a TypeVar."""
|
|
|
|
|
|
|
|
|
|
def __get__(self, obj: Any, type_in: Any = None) -> CT:
|
|
|
|
|
return cast(CT, None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CallbackSet(Generic[CT]):
|
|
|
|
|
"""Wrangles callbacks for a particular event in a type-safe manner."""
|
|
|
|
|
|
|
|
|
|
# In the type-checker's eyes, our 'run' attr is a CallbackCall which
|
|
|
|
|
# returns a callable with the type we were created with. This lets us
|
|
|
|
|
# type-check our run calls. (Is there another way to expose a function
|
|
|
|
|
# with a signature defined by a generic?..)
|
|
|
|
|
# At runtime, run() simply passes its args verbatim to its registered
|
|
|
|
|
# callbacks; no types are checked.
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
run: _CallbackCall[CT] = _CallbackCall()
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
|
|
def run(self, *args, **keywds):
|
|
|
|
|
"""Run all callbacks."""
|
|
|
|
|
print('HELLO FROM RUN', *args, **keywds)
|
|
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
print('CallbackSet()')
|
|
|
|
|
|
|
|
|
|
def __del__(self) -> None:
|
|
|
|
|
print('~CallbackSet()')
|
|
|
|
|
|
|
|
|
|
def add(self, call: CT) -> None:
|
|
|
|
|
"""Add a callback to be run."""
|
|
|
|
|
print('Would add call', call)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Define Call() which can be used in type-checking call-wrappers that behave
|
|
|
|
|
# similarly to functools.partial (in that they take a callable and some
|
|
|
|
|
# positional arguments to be passed to it).
|
|
|
|
|
|
|
|
|
|
# In type-checking land, We define several different _CallXArg classes
|
|
|
|
|
# corresponding to different argument counts and define Call() as an
|
|
|
|
|
# overloaded function which returns one of them based on how many args are
|
|
|
|
|
# passed.
|
|
|
|
|
|
|
|
|
|
# To use this, simply assign your call type to this Call for type checking:
|
|
|
|
|
# Example:
|
|
|
|
|
# class _MyCallWrapper:
|
|
|
|
|
# <runtime class defined here>
|
|
|
|
|
# if TYPE_CHECKING:
|
|
|
|
|
# MyCallWrapper = efro.call.Call
|
|
|
|
|
# else:
|
|
|
|
|
# MyCallWrapper = _MyCallWrapper
|
|
|
|
|
|
|
|
|
|
# Note that this setup currently only works with positional arguments; if you
|
|
|
|
|
# would like to pass args via keyword you can wrap a lambda or local function
|
|
|
|
|
# which takes keyword args and converts to a call containing keywords.
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
In1T = TypeVar('In1T')
|
|
|
|
|
In2T = TypeVar('In2T')
|
|
|
|
|
In3T = TypeVar('In3T')
|
|
|
|
|
In4T = TypeVar('In4T')
|
|
|
|
|
In5T = TypeVar('In5T')
|
|
|
|
|
In6T = TypeVar('In6T')
|
|
|
|
|
In7T = TypeVar('In7T')
|
|
|
|
|
OutT = TypeVar('OutT')
|
|
|
|
|
|
|
|
|
|
class _CallNoArgs(Generic[OutT]):
|
|
|
|
|
"""Single argument variant of call wrapper."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, _call: Callable[[], OutT]):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def __call__(self) -> OutT:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
class _Call1Arg(Generic[In1T, OutT]):
|
|
|
|
|
"""Single argument variant of call wrapper."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, _call: Callable[[In1T], OutT]):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def __call__(self, _arg1: In1T) -> OutT:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
class _Call2Args(Generic[In1T, In2T, OutT]):
|
|
|
|
|
"""Two argument variant of call wrapper"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, _call: Callable[[In1T, In2T], OutT]):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def __call__(self, _arg1: In1T, _arg2: In2T) -> OutT:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
class _Call3Args(Generic[In1T, In2T, In3T, OutT]):
|
|
|
|
|
"""Three argument variant of call wrapper"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, _call: Callable[[In1T, In2T, In3T], OutT]):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T) -> OutT:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
class _Call4Args(Generic[In1T, In2T, In3T, In4T, OutT]):
|
|
|
|
|
"""Four argument variant of call wrapper"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, _call: Callable[[In1T, In2T, In3T, In4T], OutT]):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T,
|
|
|
|
|
_arg4: In4T) -> OutT:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
class _Call5Args(Generic[In1T, In2T, In3T, In4T, In5T, OutT]):
|
|
|
|
|
"""Five argument variant of call wrapper"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, _call: Callable[[In1T, In2T, In3T, In4T, In5T],
|
|
|
|
|
OutT]):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T,
|
|
|
|
|
_arg5: In5T) -> OutT:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
class _Call6Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, OutT]):
|
|
|
|
|
"""Six argument variant of call wrapper"""
|
|
|
|
|
|
|
|
|
|
def __init__(self,
|
|
|
|
|
_call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T],
|
|
|
|
|
OutT]):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T,
|
|
|
|
|
_arg5: In5T, _arg6: In6T) -> OutT:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
class _Call7Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, In7T, OutT]):
|
|
|
|
|
"""Seven argument variant of call wrapper"""
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
_call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T],
|
|
|
|
|
OutT]):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T,
|
|
|
|
|
_arg5: In5T, _arg6: In6T, _arg7: In7T) -> OutT:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# No arg call; no args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[], OutT]) -> _CallNoArgs[OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 1 arg call; 1 arg bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T], OutT], arg1: In1T) -> _CallNoArgs[OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 1 arg call; no args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T], OutT]) -> _Call1Arg[In1T, OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 2 arg call; 2 args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T, In2T], OutT], arg1: In1T,
|
|
|
|
|
arg2: In2T) -> _CallNoArgs[OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 2 arg call; 1 arg bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T, In2T], OutT],
|
|
|
|
|
arg1: In1T) -> _Call1Arg[In2T, OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 2 arg call; no args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(
|
|
|
|
|
call: Callable[[In1T, In2T],
|
|
|
|
|
OutT]) -> _Call2Args[In1T, In2T, OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 3 arg call; 3 args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T, arg2: In2T,
|
|
|
|
|
arg3: In3T) -> _CallNoArgs[OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 3 arg call; 2 args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T,
|
|
|
|
|
arg2: In2T) -> _Call1Arg[In3T, OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 3 arg call; 1 arg bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T, In2T, In3T], OutT],
|
|
|
|
|
arg1: In1T) -> _Call2Args[In2T, In3T, OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 3 arg call; no args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(
|
|
|
|
|
call: Callable[[In1T, In2T, In3T], OutT]
|
|
|
|
|
) -> _Call3Args[In1T, In2T, In3T, OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 4 arg call; 4 args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T, In2T, In3T, In4T], OutT], arg1: In1T,
|
|
|
|
|
arg2: In2T, arg3: In3T, arg4: In4T) -> _CallNoArgs[OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 5 arg call; 5 args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T, In2T, In3T, In4T, In5T],
|
|
|
|
|
OutT], arg1: In1T, arg2: In2T, arg3: In3T,
|
|
|
|
|
arg4: In4T, arg5: In5T) -> _CallNoArgs[OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 6 arg call; 6 args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T],
|
|
|
|
|
OutT], arg1: In1T, arg2: In2T, arg3: In3T,
|
|
|
|
|
arg4: In4T, arg5: In5T, arg6: In6T) -> _CallNoArgs[OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# 7 arg call; 7 args bundled.
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
@overload
|
|
|
|
|
def Call(call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT],
|
|
|
|
|
arg1: In1T, arg2: In2T, arg3: In3T, arg4: In4T, arg5: In5T,
|
|
|
|
|
arg6: In6T, arg7: In7T) -> _CallNoArgs[OutT]:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# noinspection PyPep8Naming
|
|
|
|
|
def Call(*_args: Any, **_keywds: Any) -> Any:
|
|
|
|
|
...
|
|
|
|
|
|
2022-03-13 17:33:09 +05:30
|
|
|
# (Type-safe Partial)
|
2021-11-10 17:26:07 +05:30
|
|
|
# A convenient wrapper around functools.partial which adds type-safety
|
|
|
|
|
# (though it does not support keyword arguments).
|
2022-03-13 17:33:09 +05:30
|
|
|
tpartial = Call
|
2021-11-10 17:26:07 +05:30
|
|
|
else:
|
2022-03-13 17:33:09 +05:30
|
|
|
tpartial = functools.partial
|