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 89 def __call__(self) -> OutT: 90 ... 91 92 class _Call1Arg(Generic[In1T, OutT]): 93 """Single argument variant of call wrapper.""" 94 95 def __init__(self, _call: Callable[[In1T], OutT]): 96 ... 97 98 def __call__(self, _arg1: In1T) -> OutT: 99 ... 100 101 class _Call2Args(Generic[In1T, In2T, OutT]): 102 """Two argument variant of call wrapper""" 103 104 def __init__(self, _call: Callable[[In1T, In2T], OutT]): 105 ... 106 107 def __call__(self, _arg1: In1T, _arg2: In2T) -> OutT: 108 ... 109 110 class _Call3Args(Generic[In1T, In2T, In3T, OutT]): 111 """Three argument variant of call wrapper""" 112 113 def __init__(self, _call: Callable[[In1T, In2T, In3T], OutT]): 114 ... 115 116 def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T) -> OutT: 117 ... 118 119 class _Call4Args(Generic[In1T, In2T, In3T, In4T, OutT]): 120 """Four argument variant of call wrapper""" 121 122 def __init__(self, _call: Callable[[In1T, In2T, In3T, In4T], OutT]): 123 ... 124 125 def __call__( 126 self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T 127 ) -> OutT: 128 ... 129 130 class _Call5Args(Generic[In1T, In2T, In3T, In4T, In5T, OutT]): 131 """Five argument variant of call wrapper""" 132 133 def __init__( 134 self, _call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT] 135 ): 136 ... 137 138 def __call__( 139 self, 140 _arg1: In1T, 141 _arg2: In2T, 142 _arg3: In3T, 143 _arg4: In4T, 144 _arg5: In5T, 145 ) -> OutT: 146 ... 147 148 class _Call6Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, OutT]): 149 """Six argument variant of call wrapper""" 150 151 def __init__( 152 self, _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT] 153 ): 154 ... 155 156 def __call__( 157 self, 158 _arg1: In1T, 159 _arg2: In2T, 160 _arg3: In3T, 161 _arg4: In4T, 162 _arg5: In5T, 163 _arg6: In6T, 164 ) -> OutT: 165 ... 166 167 class _Call7Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, In7T, OutT]): 168 """Seven argument variant of call wrapper""" 169 170 def __init__( 171 self, 172 _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT], 173 ): 174 ... 175 176 def __call__( 177 self, 178 _arg1: In1T, 179 _arg2: In2T, 180 _arg3: In3T, 181 _arg4: In4T, 182 _arg5: In5T, 183 _arg6: In6T, 184 _arg7: In7T, 185 ) -> OutT: 186 ... 187 188 # No arg call; no args bundled. 189 # noinspection PyPep8Naming 190 @overload 191 def Call(call: Callable[[], OutT]) -> _CallNoArgs[OutT]: 192 ... 193 194 # 1 arg call; 1 arg bundled. 195 # noinspection PyPep8Naming 196 @overload 197 def Call(call: Callable[[In1T], OutT], arg1: In1T) -> _CallNoArgs[OutT]: 198 ... 199 200 # 1 arg call; no args bundled. 201 # noinspection PyPep8Naming 202 @overload 203 def Call(call: Callable[[In1T], OutT]) -> _Call1Arg[In1T, OutT]: 204 ... 205 206 # 2 arg call; 2 args bundled. 207 # noinspection PyPep8Naming 208 @overload 209 def Call( 210 call: Callable[[In1T, In2T], OutT], arg1: In1T, arg2: In2T 211 ) -> _CallNoArgs[OutT]: 212 ... 213 214 # 2 arg call; 1 arg bundled. 215 # noinspection PyPep8Naming 216 @overload 217 def Call( 218 call: Callable[[In1T, In2T], OutT], arg1: In1T 219 ) -> _Call1Arg[In2T, OutT]: 220 ... 221 222 # 2 arg call; no args bundled. 223 # noinspection PyPep8Naming 224 @overload 225 def Call( 226 call: Callable[[In1T, In2T], OutT] 227 ) -> _Call2Args[In1T, In2T, OutT]: 228 ... 229 230 # 3 arg call; 3 args bundled. 231 # noinspection PyPep8Naming 232 @overload 233 def Call( 234 call: Callable[[In1T, In2T, In3T], OutT], 235 arg1: In1T, 236 arg2: In2T, 237 arg3: In3T, 238 ) -> _CallNoArgs[OutT]: 239 ... 240 241 # 3 arg call; 2 args bundled. 242 # noinspection PyPep8Naming 243 @overload 244 def Call( 245 call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T, arg2: In2T 246 ) -> _Call1Arg[In3T, OutT]: 247 ... 248 249 # 3 arg call; 1 arg bundled. 250 # noinspection PyPep8Naming 251 @overload 252 def Call( 253 call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T 254 ) -> _Call2Args[In2T, In3T, OutT]: 255 ... 256 257 # 3 arg call; no args bundled. 258 # noinspection PyPep8Naming 259 @overload 260 def Call( 261 call: Callable[[In1T, In2T, In3T], OutT] 262 ) -> _Call3Args[In1T, In2T, In3T, OutT]: 263 ... 264 265 # 4 arg call; 4 args bundled. 266 # noinspection PyPep8Naming 267 @overload 268 def Call( 269 call: Callable[[In1T, In2T, In3T, In4T], OutT], 270 arg1: In1T, 271 arg2: In2T, 272 arg3: In3T, 273 arg4: In4T, 274 ) -> _CallNoArgs[OutT]: 275 ... 276 277 # 4 arg call; 3 args bundled. 278 # noinspection PyPep8Naming 279 @overload 280 def Call( 281 call: Callable[[In1T, In2T, In3T, In4T], OutT], 282 arg1: In1T, 283 arg2: In2T, 284 arg3: In3T, 285 ) -> _Call1Arg[In4T, OutT]: 286 ... 287 288 # 4 arg call; 2 args bundled. 289 # noinspection PyPep8Naming 290 @overload 291 def Call( 292 call: Callable[[In1T, In2T, In3T, In4T], OutT], 293 arg1: In1T, 294 arg2: In2T, 295 ) -> _Call2Args[In3T, In4T, OutT]: 296 ... 297 298 # 4 arg call; 1 arg bundled. 299 # noinspection PyPep8Naming 300 @overload 301 def Call( 302 call: Callable[[In1T, In2T, In3T, In4T], OutT], 303 arg1: In1T, 304 ) -> _Call3Args[In2T, In3T, In4T, OutT]: 305 ... 306 307 # 4 arg call; no args bundled. 308 # noinspection PyPep8Naming 309 @overload 310 def Call( 311 call: Callable[[In1T, In2T, In3T, In4T], OutT], 312 ) -> _Call4Args[In1T, In2T, In3T, In4T, OutT]: 313 ... 314 315 # 5 arg call; 5 args bundled. 316 # noinspection PyPep8Naming 317 @overload 318 def Call( 319 call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT], 320 arg1: In1T, 321 arg2: In2T, 322 arg3: In3T, 323 arg4: In4T, 324 arg5: In5T, 325 ) -> _CallNoArgs[OutT]: 326 ... 327 328 # 6 arg call; 6 args bundled. 329 # noinspection PyPep8Naming 330 @overload 331 def Call( 332 call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT], 333 arg1: In1T, 334 arg2: In2T, 335 arg3: In3T, 336 arg4: In4T, 337 arg5: In5T, 338 arg6: In6T, 339 ) -> _CallNoArgs[OutT]: 340 ... 341 342 # 7 arg call; 7 args bundled. 343 # noinspection PyPep8Naming 344 @overload 345 def Call( 346 call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT], 347 arg1: In1T, 348 arg2: In2T, 349 arg3: In3T, 350 arg4: In4T, 351 arg5: In5T, 352 arg6: In6T, 353 arg7: In7T, 354 ) -> _CallNoArgs[OutT]: 355 ... 356 357 # noinspection PyPep8Naming 358 def Call(*_args: Any, **_keywds: Any) -> Any: 359 ... 360 361 # (Type-safe Partial) 362 # A convenient wrapper around functools.partial which adds type-safety 363 # (though it does not support keyword arguments). 364 tpartial = Call 365else: 366 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.