efro.call
Call related functionality shared between all efro components.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Call related functionality shared between all efro components.""" 4 5from __future__ import annotations 6 7from typing import TYPE_CHECKING, TypeVar, Generic, Callable, cast 8import functools 9 10if TYPE_CHECKING: 11 from typing import Any, overload 12 13CT = TypeVar('CT', bound=Callable) 14 15 16class _CallbackCall(Generic[CT]): 17 """Descriptor for exposing a call with a type defined by a TypeVar.""" 18 19 def __get__(self, obj: Any, type_in: Any = None) -> CT: 20 return cast(CT, None) 21 22 23class CallbackSet(Generic[CT]): 24 """Wrangles callbacks for a particular event in a type-safe manner.""" 25 26 # In the type-checker's eyes, our 'run' attr is a CallbackCall which 27 # returns a callable with the type we were created with. This lets us 28 # type-check our run calls. (Is there another way to expose a function 29 # with a signature defined by a generic?..) 30 # At runtime, run() simply passes its args verbatim to its registered 31 # callbacks; no types are checked. 32 if TYPE_CHECKING: 33 run: _CallbackCall[CT] = _CallbackCall() 34 else: 35 36 def run(self, *args, **keywds): 37 """Run all callbacks.""" 38 print('HELLO FROM RUN', *args, **keywds) 39 40 def __init__(self) -> None: 41 print('CallbackSet()') 42 43 def __del__(self) -> None: 44 print('~CallbackSet()') 45 46 def add(self, call: CT) -> None: 47 """Add a callback to be run.""" 48 print('Would add call', call) 49 50 51# Define Call() which can be used in type-checking call-wrappers that behave 52# similarly to functools.partial (in that they take a callable and some 53# positional arguments to be passed to it). 54 55# In type-checking land, We define several different _CallXArg classes 56# corresponding to different argument counts and define Call() as an 57# overloaded function which returns one of them based on how many args are 58# passed. 59 60# To use this, simply assign your call type to this Call for type checking: 61# Example: 62# class _MyCallWrapper: 63# <runtime class defined here> 64# if TYPE_CHECKING: 65# MyCallWrapper = efro.call.Call 66# else: 67# MyCallWrapper = _MyCallWrapper 68 69# Note that this setup currently only works with positional arguments; if you 70# would like to pass args via keyword you can wrap a lambda or local function 71# which takes keyword args and converts to a call containing keywords. 72 73if TYPE_CHECKING: 74 In1T = TypeVar('In1T') 75 In2T = TypeVar('In2T') 76 In3T = TypeVar('In3T') 77 In4T = TypeVar('In4T') 78 In5T = TypeVar('In5T') 79 In6T = TypeVar('In6T') 80 In7T = TypeVar('In7T') 81 OutT = TypeVar('OutT') 82 83 class _CallNoArgs(Generic[OutT]): 84 """Single argument variant of call wrapper.""" 85 86 def __init__(self, _call: Callable[[], OutT]): ... 87 88 def __call__(self) -> OutT: ... 89 90 class _Call1Arg(Generic[In1T, OutT]): 91 """Single argument variant of call wrapper.""" 92 93 def __init__(self, _call: Callable[[In1T], OutT]): ... 94 95 def __call__(self, _arg1: In1T) -> OutT: ... 96 97 class _Call2Args(Generic[In1T, In2T, OutT]): 98 """Two argument variant of call wrapper""" 99 100 def __init__(self, _call: Callable[[In1T, In2T], OutT]): ... 101 102 def __call__(self, _arg1: In1T, _arg2: In2T) -> OutT: ... 103 104 class _Call3Args(Generic[In1T, In2T, In3T, OutT]): 105 """Three argument variant of call wrapper""" 106 107 def __init__(self, _call: Callable[[In1T, In2T, In3T], OutT]): ... 108 109 def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T) -> OutT: ... 110 111 class _Call4Args(Generic[In1T, In2T, In3T, In4T, OutT]): 112 """Four argument variant of call wrapper""" 113 114 def __init__(self, _call: Callable[[In1T, In2T, In3T, In4T], OutT]): ... 115 116 def __call__( 117 self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T 118 ) -> OutT: ... 119 120 class _Call5Args(Generic[In1T, In2T, In3T, In4T, In5T, OutT]): 121 """Five argument variant of call wrapper""" 122 123 def __init__( 124 self, _call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT] 125 ): ... 126 127 def __call__( 128 self, 129 _arg1: In1T, 130 _arg2: In2T, 131 _arg3: In3T, 132 _arg4: In4T, 133 _arg5: In5T, 134 ) -> OutT: ... 135 136 class _Call6Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, OutT]): 137 """Six argument variant of call wrapper""" 138 139 def __init__( 140 self, _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT] 141 ): ... 142 143 def __call__( 144 self, 145 _arg1: In1T, 146 _arg2: In2T, 147 _arg3: In3T, 148 _arg4: In4T, 149 _arg5: In5T, 150 _arg6: In6T, 151 ) -> OutT: ... 152 153 class _Call7Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, In7T, OutT]): 154 """Seven argument variant of call wrapper""" 155 156 def __init__( 157 self, 158 _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT], 159 ): ... 160 161 def __call__( 162 self, 163 _arg1: In1T, 164 _arg2: In2T, 165 _arg3: In3T, 166 _arg4: In4T, 167 _arg5: In5T, 168 _arg6: In6T, 169 _arg7: In7T, 170 ) -> OutT: ... 171 172 # No arg call; no args bundled. 173 # noinspection PyPep8Naming 174 @overload 175 def Call(call: Callable[[], OutT]) -> _CallNoArgs[OutT]: ... 176 177 # 1 arg call; 1 arg bundled. 178 # noinspection PyPep8Naming 179 @overload 180 def Call(call: Callable[[In1T], OutT], arg1: In1T) -> _CallNoArgs[OutT]: ... 181 182 # 1 arg call; no args bundled. 183 # noinspection PyPep8Naming 184 @overload 185 def Call(call: Callable[[In1T], OutT]) -> _Call1Arg[In1T, OutT]: ... 186 187 # 2 arg call; 2 args bundled. 188 # noinspection PyPep8Naming 189 @overload 190 def Call( 191 call: Callable[[In1T, In2T], OutT], arg1: In1T, arg2: In2T 192 ) -> _CallNoArgs[OutT]: ... 193 194 # 2 arg call; 1 arg bundled. 195 # noinspection PyPep8Naming 196 @overload 197 def Call( 198 call: Callable[[In1T, In2T], OutT], arg1: In1T 199 ) -> _Call1Arg[In2T, OutT]: ... 200 201 # 2 arg call; no args bundled. 202 # noinspection PyPep8Naming 203 @overload 204 def Call( 205 call: Callable[[In1T, In2T], OutT] 206 ) -> _Call2Args[In1T, In2T, OutT]: ... 207 208 # 3 arg call; 3 args bundled. 209 # noinspection PyPep8Naming 210 @overload 211 def Call( 212 call: Callable[[In1T, In2T, In3T], OutT], 213 arg1: In1T, 214 arg2: In2T, 215 arg3: In3T, 216 ) -> _CallNoArgs[OutT]: ... 217 218 # 3 arg call; 2 args bundled. 219 # noinspection PyPep8Naming 220 @overload 221 def Call( 222 call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T, arg2: In2T 223 ) -> _Call1Arg[In3T, OutT]: ... 224 225 # 3 arg call; 1 arg bundled. 226 # noinspection PyPep8Naming 227 @overload 228 def Call( 229 call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T 230 ) -> _Call2Args[In2T, In3T, OutT]: ... 231 232 # 3 arg call; no args bundled. 233 # noinspection PyPep8Naming 234 @overload 235 def Call( 236 call: Callable[[In1T, In2T, In3T], OutT] 237 ) -> _Call3Args[In1T, In2T, In3T, OutT]: ... 238 239 # 4 arg call; 4 args bundled. 240 # noinspection PyPep8Naming 241 @overload 242 def Call( 243 call: Callable[[In1T, In2T, In3T, In4T], OutT], 244 arg1: In1T, 245 arg2: In2T, 246 arg3: In3T, 247 arg4: In4T, 248 ) -> _CallNoArgs[OutT]: ... 249 250 # 4 arg call; 3 args bundled. 251 # noinspection PyPep8Naming 252 @overload 253 def Call( 254 call: Callable[[In1T, In2T, In3T, In4T], OutT], 255 arg1: In1T, 256 arg2: In2T, 257 arg3: In3T, 258 ) -> _Call1Arg[In4T, OutT]: ... 259 260 # 4 arg call; 2 args bundled. 261 # noinspection PyPep8Naming 262 @overload 263 def Call( 264 call: Callable[[In1T, In2T, In3T, In4T], OutT], 265 arg1: In1T, 266 arg2: In2T, 267 ) -> _Call2Args[In3T, In4T, OutT]: ... 268 269 # 4 arg call; 1 arg bundled. 270 # noinspection PyPep8Naming 271 @overload 272 def Call( 273 call: Callable[[In1T, In2T, In3T, In4T], OutT], 274 arg1: In1T, 275 ) -> _Call3Args[In2T, In3T, In4T, OutT]: ... 276 277 # 4 arg call; no args bundled. 278 # noinspection PyPep8Naming 279 @overload 280 def Call( 281 call: Callable[[In1T, In2T, In3T, In4T], OutT], 282 ) -> _Call4Args[In1T, In2T, In3T, In4T, OutT]: ... 283 284 # 5 arg call; 5 args bundled. 285 # noinspection PyPep8Naming 286 @overload 287 def Call( 288 call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT], 289 arg1: In1T, 290 arg2: In2T, 291 arg3: In3T, 292 arg4: In4T, 293 arg5: In5T, 294 ) -> _CallNoArgs[OutT]: ... 295 296 # 6 arg call; 6 args bundled. 297 # noinspection PyPep8Naming 298 @overload 299 def Call( 300 call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT], 301 arg1: In1T, 302 arg2: In2T, 303 arg3: In3T, 304 arg4: In4T, 305 arg5: In5T, 306 arg6: In6T, 307 ) -> _CallNoArgs[OutT]: ... 308 309 # 7 arg call; 7 args bundled. 310 # noinspection PyPep8Naming 311 @overload 312 def Call( 313 call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT], 314 arg1: In1T, 315 arg2: In2T, 316 arg3: In3T, 317 arg4: In4T, 318 arg5: In5T, 319 arg6: In6T, 320 arg7: In7T, 321 ) -> _CallNoArgs[OutT]: ... 322 323 # noinspection PyPep8Naming 324 def Call(*_args: Any, **_keywds: Any) -> Any: ... 325 326 # (Type-safe Partial) 327 # A convenient wrapper around functools.partial which adds type-safety 328 # (though it does not support keyword arguments). 329 tpartial = Call 330else: 331 tpartial = functools.partial
class
CallbackSet(typing.Generic[~CT]):
24class CallbackSet(Generic[CT]): 25 """Wrangles callbacks for a particular event in a type-safe manner.""" 26 27 # In the type-checker's eyes, our 'run' attr is a CallbackCall which 28 # returns a callable with the type we were created with. This lets us 29 # type-check our run calls. (Is there another way to expose a function 30 # with a signature defined by a generic?..) 31 # At runtime, run() simply passes its args verbatim to its registered 32 # callbacks; no types are checked. 33 if TYPE_CHECKING: 34 run: _CallbackCall[CT] = _CallbackCall() 35 else: 36 37 def run(self, *args, **keywds): 38 """Run all callbacks.""" 39 print('HELLO FROM RUN', *args, **keywds) 40 41 def __init__(self) -> None: 42 print('CallbackSet()') 43 44 def __del__(self) -> None: 45 print('~CallbackSet()') 46 47 def add(self, call: CT) -> None: 48 """Add a callback to be run.""" 49 print('Would add call', call)
Wrangles callbacks for a particular event in a type-safe manner.
def
tpartial(*_args: Any, **_keywds: Any) -> Any:
def
Call(*_args: Any, **_keywds: Any) -> Any: