From 6e7dfb5a0102ce4dcd6118e06fee9aac4cec177a Mon Sep 17 00:00:00 2001 From: Alexander Kozhevnikov Date: Thu, 26 Sep 2024 17:05:42 +0000 Subject: [PATCH] Modernization draft 1. Bring typing file in-package (TODO: actually test). 2. Make the automatically-async-if-needed behavior the default, renamed from `sacompose` to `compose`. * `acompose` remains the same: always awaitable, even if nothing in the composition is. * The no-async behavior is now renamed from `compose` to `scompose`. 3. TODO: replace `setup.py` with something modern. 4. TODO: update README. --- normal.py => compose/__init__.py | 100 ++++++++-------- compose/__init__.pyi | 197 +++++++++++++++++++++++++++++++ no_async.py | 89 -------------- no_positional_only_arguments.py | 187 ----------------------------- 4 files changed, 250 insertions(+), 323 deletions(-) rename normal.py => compose/__init__.py (85%) create mode 100644 compose/__init__.pyi delete mode 100644 no_async.py delete mode 100644 no_positional_only_arguments.py diff --git a/normal.py b/compose/__init__.py similarity index 85% rename from normal.py rename to compose/__init__.py index d633323..6940f5c 100644 --- a/normal.py +++ b/compose/__init__.py @@ -3,8 +3,8 @@ """The classic ``compose``, with all the Pythonic features.""" -__all__ = ('compose', 'acompose', 'sacompose') -__version__ = '1.6.2' +__all__ = ('compose', 'acompose', 'scompose') +__version__ = '2.0.0' from inspect import isawaitable as _isawaitable @@ -21,7 +21,18 @@ def _name(obj): class compose: - """Function composition: compose(f, g)(...) is equivalent to f(g(...)).""" + """Function composition. + + `compose(f, g)(...)` is equivalent to `f(g(...))`, + if neither `f` nor `g` return awaitables. + + If either `f` or `g` return awaitables. + `await compose(f, g)(...)` is automatically either + + * `await f(g(...))`, + * `f(await g(...))`, or + * `await f(await g(...))`. + """ def __init__(self, *functions): """Initialize the composed function. @@ -40,7 +51,10 @@ def __init__(self, *functions): for function in reversed(functions): if not callable(function): raise TypeError(_name(self) + '() arguments must be callable') - if isinstance(function, compose): + if (_iscoroutinefunction(function) + or _iscoroutinefunction(type(function).__call__)): + _markcoroutinefunction(self) + if isinstance(function, (compose, acompose)): _functions.extend(function.functions) else: _functions.append(function) @@ -48,10 +62,20 @@ def __init__(self, *functions): self._wrappers = tuple(_functions[1:]) def __call__(self, /, *args, **kwargs): - """Call the composed function.""" + """Call the composed function. + + The return value will either be a normal value if all composed + callables return normal values, or an awaitable that needs an + `await` if at least one composed callable returns an awaitable. + """ result = self.__wrapped__(*args, **kwargs) - for function in self._wrappers: + if _isawaitable(result): + return _finish(self._wrappers, result) + wrappers = iter(self._wrappers) + for function in wrappers: result = function(result) + if _isawaitable(result): + return _finish(wrappers, result) return result def __get__(self, obj, objtype=None): @@ -80,17 +104,21 @@ def functions(self): return (self.__wrapped__,) + tuple(self._wrappers) -class acompose: - """Asynchronous function composition. +async def _finish(remaining_functions, first_awaitable_result): + result = await first_awaitable_result + for function in remaining_functions: + result = function(result) + if _isawaitable(result): + result = await result + return result - await acompose(f, g)(...) is equivalent to one of - f(g(...)) - await f(g(...)) - f(await g(...)) - await f(await g(...)) +class acompose: + """Forced-asynchronous function composition. - based on whether f and g return awaitable values. + `acompose` is like `compose`, except you always need + to do `await` to get the result, even if neither of + the functions being composed need `await`. """ def __init__(self, *functions): @@ -110,7 +138,7 @@ def __init__(self, *functions): for function in reversed(functions): if not callable(function): raise TypeError(_name(self) + '() arguments must be callable') - if isinstance(function, (acompose, sacompose)): + if isinstance(function, (compose, acompose)): _functions.extend(function.functions) else: _functions.append(function) @@ -134,14 +162,14 @@ async def __call__(self, /, *args, **kwargs): functions = compose.functions -class sacompose: - """Sometimes-asynchronous function composition. - - sacompose(f, g)(...) is equivalent to: +class scompose: + """Forced-synchronous function composition. - acompose(f, g)(...) if either f or g returns an awaitable object. - compose(f, g)(...) if neither f nor g returns an awaitable object. + `scompose(f, g)(...)` is equivalent to `f(g(...))`, + always without `await`, even if `f` or `g` return + awaitables. """ + def __init__(self, *functions): """Initialize the composed function. @@ -159,10 +187,7 @@ def __init__(self, *functions): for function in reversed(functions): if not callable(function): raise TypeError(_name(self) + '() arguments must be callable') - if (_iscoroutinefunction(function) - or _iscoroutinefunction(type(function).__call__)): - _markcoroutinefunction(self) - if isinstance(function, (acompose, sacompose)): + if isinstance(function, scompose): _functions.extend(function.functions) else: _functions.append(function) @@ -170,20 +195,10 @@ def __init__(self, *functions): self._wrappers = tuple(_functions[1:]) def __call__(self, /, *args, **kwargs): - """Call the composed function. - - The return value will either be a normal value if all composed - callables return normal values, or an awaitable that needs an - `await` if at least one composed callable returns an awaitable. - """ + """Call the composed function.""" result = self.__wrapped__(*args, **kwargs) - if _isawaitable(result): - return _finish(self._wrappers, result) - wrappers = iter(self._wrappers) - for function in wrappers: + for function in self._wrappers: result = function(result) - if _isawaitable(result): - return _finish(wrappers, result) return result __repr__ = compose.__repr__ @@ -191,19 +206,10 @@ def __call__(self, /, *args, **kwargs): functions = compose.functions -async def _finish(remaining_functions, first_awaitable_result): - result = await first_awaitable_result - for function in remaining_functions: - result = function(result) - if _isawaitable(result): - result = await result - return result - - # Portability to some minimal Python implementations: try: compose.__name__ except AttributeError: compose.__name__ = 'compose' acompose.__name__ = 'acompose' - sacompose.__name__ = 'sacompose' + scompose.__name__ = 'scompose' diff --git a/compose/__init__.pyi b/compose/__init__.pyi new file mode 100644 index 0000000..7d477a1 --- /dev/null +++ b/compose/__init__.pyi @@ -0,0 +1,197 @@ +from typing import Awaitable, Callable, ParamSpec, TypeVar, Union, overload +P = ParamSpec('P') +R1 = TypeVar('R1') +R2 = TypeVar('R2') +R3 = TypeVar('R3') +R4 = TypeVar('R4') +R5 = TypeVar('R5') +R6 = TypeVar('R6') +R7 = TypeVar('R7') +R8 = TypeVar('R8') +R9 = TypeVar('R9') +R10 = TypeVar('R10') +R11 = TypeVar('R11') +R12 = TypeVar('R12') +R13 = TypeVar('R13') +R14 = TypeVar('R14') +R15 = TypeVar('R15') +@overload +def compose(f1: Callable[P, R1], /) -> Callable[P, R1]: + ... +@overload +def compose(f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R2]: + ... +@overload +def compose(f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R3]: + ... +@overload +def compose(f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R4]: + ... +@overload +def compose(f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R5]: + ... +@overload +def compose(f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R6]: + ... +@overload +def compose(f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R7]: + ... +@overload +def compose(f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R8]: + ... +@overload +def compose(f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R9]: + ... +@overload +def compose(f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R10]: + ... +@overload +def compose(f11: Callable[[R10], R11], f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R11]: + ... +@overload +def compose(f12: Callable[[R11], R12], f11: Callable[[R10], R11], f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R12]: + ... +@overload +def compose(f13: Callable[[R12], R13], f12: Callable[[R11], R12], f11: Callable[[R10], R11], f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R13]: + ... +@overload +def compose(f14: Callable[[R13], R14], f13: Callable[[R12], R13], f12: Callable[[R11], R12], f11: Callable[[R10], R11], f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R14]: + ... +@overload +def compose(f15: Callable[[R14], R15], f14: Callable[[R13], R14], f13: Callable[[R12], R13], f12: Callable[[R11], R12], f11: Callable[[R10], R11], f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R15]: + ... +@overload +def acompose(f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R1]]: + ... +@overload +def acompose(f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R2]]: + ... +@overload +def acompose(f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R3]]: + ... +@overload +def acompose(f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R4]]: + ... +@overload +def acompose(f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R5]]: + ... +@overload +def acompose(f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R6]]: + ... +@overload +def acompose(f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R7]]: + ... +@overload +def acompose(f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R8]]: + ... +@overload +def acompose(f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R9]]: + ... +@overload +def acompose(f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R10]]: + ... +@overload +def acompose(f11: Callable[[R10], Union[R11, Awaitable[R11]]], f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R11]]: + ... +@overload +def acompose(f12: Callable[[R11], Union[R12, Awaitable[R12]]], f11: Callable[[R10], Union[R11, Awaitable[R11]]], f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R12]]: + ... +@overload +def acompose(f13: Callable[[R12], Union[R13, Awaitable[R13]]], f12: Callable[[R11], Union[R12, Awaitable[R12]]], f11: Callable[[R10], Union[R11, Awaitable[R11]]], f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R13]]: + ... +@overload +def acompose(f14: Callable[[R13], Union[R14, Awaitable[R14]]], f13: Callable[[R12], Union[R13, Awaitable[R13]]], f12: Callable[[R11], Union[R12, Awaitable[R12]]], f11: Callable[[R10], Union[R11, Awaitable[R11]]], f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R14]]: + ... +@overload +def acompose(f15: Callable[[R14], Union[R15, Awaitable[R15]]], f14: Callable[[R13], Union[R14, Awaitable[R14]]], f13: Callable[[R12], Union[R13, Awaitable[R13]]], f12: Callable[[R11], Union[R12, Awaitable[R12]]], f11: Callable[[R10], Union[R11, Awaitable[R11]]], f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R15]]: + ... +@overload +def sacompose(f1: Callable[P, R1], /) -> Callable[P, R1]: + ... +@overload +def sacompose(f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R2]: + ... +@overload +def sacompose(f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R3]: + ... +@overload +def sacompose(f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R4]: + ... +@overload +def sacompose(f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R5]: + ... +@overload +def sacompose(f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R6]: + ... +@overload +def sacompose(f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R7]: + ... +@overload +def sacompose(f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R8]: + ... +@overload +def sacompose(f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R9]: + ... +@overload +def sacompose(f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R10]: + ... +@overload +def sacompose(f11: Callable[[R10], R11], f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R11]: + ... +@overload +def sacompose(f12: Callable[[R11], R12], f11: Callable[[R10], R11], f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R12]: + ... +@overload +def sacompose(f13: Callable[[R12], R13], f12: Callable[[R11], R12], f11: Callable[[R10], R11], f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R13]: + ... +@overload +def sacompose(f14: Callable[[R13], R14], f13: Callable[[R12], R13], f12: Callable[[R11], R12], f11: Callable[[R10], R11], f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R14]: + ... +@overload +def sacompose(f15: Callable[[R14], R15], f14: Callable[[R13], R14], f13: Callable[[R12], R13], f12: Callable[[R11], R12], f11: Callable[[R10], R11], f10: Callable[[R9], R10], f9: Callable[[R8], R9], f8: Callable[[R7], R8], f7: Callable[[R6], R7], f6: Callable[[R5], R6], f5: Callable[[R4], R5], f4: Callable[[R3], R4], f3: Callable[[R2], R3], f2: Callable[[R1], R2], f1: Callable[P, R1], /) -> Callable[P, R15]: + ... +@overload +def sacompose(f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R1]]: + ... +@overload +def sacompose(f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R2]]: + ... +@overload +def sacompose(f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R3]]: + ... +@overload +def sacompose(f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R4]]: + ... +@overload +def sacompose(f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R5]]: + ... +@overload +def sacompose(f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R6]]: + ... +@overload +def sacompose(f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R7]]: + ... +@overload +def sacompose(f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R8]]: + ... +@overload +def sacompose(f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R9]]: + ... +@overload +def sacompose(f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R10]]: + ... +@overload +def sacompose(f11: Callable[[R10], Union[R11, Awaitable[R11]]], f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R11]]: + ... +@overload +def sacompose(f12: Callable[[R11], Union[R12, Awaitable[R12]]], f11: Callable[[R10], Union[R11, Awaitable[R11]]], f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R12]]: + ... +@overload +def sacompose(f13: Callable[[R12], Union[R13, Awaitable[R13]]], f12: Callable[[R11], Union[R12, Awaitable[R12]]], f11: Callable[[R10], Union[R11, Awaitable[R11]]], f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R13]]: + ... +@overload +def sacompose(f14: Callable[[R13], Union[R14, Awaitable[R14]]], f13: Callable[[R12], Union[R13, Awaitable[R13]]], f12: Callable[[R11], Union[R12, Awaitable[R12]]], f11: Callable[[R10], Union[R11, Awaitable[R11]]], f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R14]]: + ... +@overload +def sacompose(f15: Callable[[R14], Union[R15, Awaitable[R15]]], f14: Callable[[R13], Union[R14, Awaitable[R14]]], f13: Callable[[R12], Union[R13, Awaitable[R13]]], f12: Callable[[R11], Union[R12, Awaitable[R12]]], f11: Callable[[R10], Union[R11, Awaitable[R11]]], f10: Callable[[R9], Union[R10, Awaitable[R10]]], f9: Callable[[R8], Union[R9, Awaitable[R9]]], f8: Callable[[R7], Union[R8, Awaitable[R8]]], f7: Callable[[R6], Union[R7, Awaitable[R7]]], f6: Callable[[R5], Union[R6, Awaitable[R6]]], f5: Callable[[R4], Union[R5, Awaitable[R5]]], f4: Callable[[R3], Union[R4, Awaitable[R4]]], f3: Callable[[R2], Union[R3, Awaitable[R3]]], f2: Callable[[R1], Union[R2, Awaitable[R2]]], f1: Callable[P, Union[R1, Awaitable[R1]]], /) -> Callable[P, Awaitable[R15]]: + ... diff --git a/no_async.py b/no_async.py deleted file mode 100644 index f34ece0..0000000 --- a/no_async.py +++ /dev/null @@ -1,89 +0,0 @@ -# SPDX-License-Identifier: 0BSD -# Copyright 2019 Alexander Kozhevnikov - -"""The classic ``compose``, with all the Pythonic features.""" - -__all__ = ('compose',) -__version__ = '1.6.2' - - -try: - from reprlib import recursive_repr as _recursive_repr - _recursive_repr_if_available = _recursive_repr('<...>') -except ImportError: - def _recursive_repr_if_available(function): - return function - - -def _name(obj): - return type(obj).__name__ - - -class compose(object): - """Function composition: compose(f, g)(...) is equivalent to f(g(...)).""" - - def __init__(self, *functions): - """Initialize the composed function. - - Arguments: - *functions: Functions (or other callables) to compose. - - Raises: - TypeError: - If no arguments are given. - If any argument is not callable. - """ - if not functions: - raise TypeError(_name(self) + '() needs at least one argument') - _functions = [] - for function in reversed(functions): - if not callable(function): - raise TypeError(_name(self) + '() arguments must be callable') - if isinstance(function, compose): - _functions.extend(function.functions) - else: - _functions.append(function) - self.__wrapped__ = _functions[0] - self._wrappers = tuple(_functions[1:]) - - def __call__(*args, **kwargs): - """Call the composed function.""" - def __call__(self, *args): - return self, args - self, args = __call__(*args) - result = self.__wrapped__(*args, **kwargs) - for function in self._wrappers: - result = function(result) - return result - - def __get__(self, obj, objtype=None): - """Get the composed function as a bound method.""" - wrapped = self.__wrapped__ - try: - bind = type(wrapped).__get__ - except AttributeError: - return self - bound_wrapped = bind(wrapped, obj, objtype) - if bound_wrapped is wrapped: - return self - bound_self = type(self)(bound_wrapped) - bound_self._wrappers = self._wrappers - return bound_self - - @_recursive_repr_if_available - def __repr__(self): - """Represent the composed function as an unambiguous string.""" - arguments = ', '.join(map(repr, reversed(self.functions))) - return _name(self) + '(' + arguments + ')' - - @property - def functions(self): - """Read-only tuple of the composed callables, in order of execution.""" - return (self.__wrapped__,) + tuple(self._wrappers) - - -# Portability to some minimal Python implementations: -try: - compose.__name__ -except AttributeError: - compose.__name__ = 'compose' diff --git a/no_positional_only_arguments.py b/no_positional_only_arguments.py deleted file mode 100644 index 252ba84..0000000 --- a/no_positional_only_arguments.py +++ /dev/null @@ -1,187 +0,0 @@ -# SPDX-License-Identifier: 0BSD -# Copyright 2019 Alexander Kozhevnikov - -"""The classic ``compose``, with all the Pythonic features.""" - -__all__ = ('compose', 'acompose', 'sacompose') -__version__ = '1.6.2' - - -from inspect import isawaitable as _isawaitable -from reprlib import recursive_repr as _recursive_repr - - -def _name(obj): - return type(obj).__name__ - - -class compose: - """Function composition: compose(f, g)(...) is equivalent to f(g(...)).""" - - def __init__(self, *functions): - """Initialize the composed function. - - Arguments: - *functions: Functions (or other callables) to compose. - - Raises: - TypeError: - If no arguments are given. - If any argument is not callable. - """ - if not functions: - raise TypeError(_name(self) + '() needs at least one argument') - _functions = [] - for function in reversed(functions): - if not callable(function): - raise TypeError(_name(self) + '() arguments must be callable') - if isinstance(function, compose): - _functions.extend(function.functions) - else: - _functions.append(function) - self.__wrapped__ = _functions[0] - self._wrappers = tuple(_functions[1:]) - - def __call__(*args, **kwargs): - """Call the composed function.""" - def __call__(self, *args): - return self, args - self, args = __call__(*args) - result = self.__wrapped__(*args, **kwargs) - for function in self._wrappers: - result = function(result) - return result - - def __get__(self, obj, objtype=None): - """Get the composed function as a bound method.""" - wrapped = self.__wrapped__ - try: - bind = type(wrapped).__get__ - except AttributeError: - return self - bound_wrapped = bind(wrapped, obj, objtype) - if bound_wrapped is wrapped: - return self - bound_self = type(self)(bound_wrapped) - bound_self._wrappers = self._wrappers - return bound_self - - @_recursive_repr('<...>') - def __repr__(self): - """Represent the composed function as an unambiguous string.""" - arguments = ', '.join(map(repr, reversed(self.functions))) - return _name(self) + '(' + arguments + ')' - - @property - def functions(self): - """Read-only tuple of the composed callables, in order of execution.""" - return (self.__wrapped__,) + tuple(self._wrappers) - - -class acompose: - """Asynchronous function composition. - - await acompose(f, g)(...) is equivalent to one of - - f(g(...)) - await f(g(...)) - f(await g(...)) - await f(await g(...)) - - based on whether f and g return awaitable values. - """ - - def __init__(self, *functions): - """Initialize the composed function. - - Arguments: - *functions: Functions (or other callables) to compose. - - Raises: - TypeError: - If no arguments are given. - If any argument is not callable. - """ - if not functions: - raise TypeError(_name(self) + '() needs at least one argument') - _functions = [] - for function in reversed(functions): - if not callable(function): - raise TypeError(_name(self) + '() arguments must be callable') - if isinstance(function, (acompose, sacompose)): - _functions.extend(function.functions) - else: - _functions.append(function) - self.__wrapped__ = _functions[0] - self._wrappers = tuple(_functions[1:]) - - async def __call__(*args, **kwargs): - """Call the composed function.""" - def __call__(self, *args): - return self, args - self, args = __call__(*args) - result = self.__wrapped__(*args, **kwargs) - if _isawaitable(result): - result = await result - for function in self._wrappers: - result = function(result) - if _isawaitable(result): - result = await result - return result - - __repr__ = compose.__repr__ - __get__ = compose.__get__ - functions = compose.functions - - -class sacompose: - """Sometimes-asynchronous function composition. - - sacompose(f, g)(...) is equivalent to: - - acompose(f, g)(...) if either f or g returns an awaitable object. - compose(f, g)(...) if neither f nor g returns an awaitable object. - """ - __init__ = acompose.__init__ - - def __call__(*args, **kwargs): - """Call the composed function. - - The return value will either be a normal value if all composed - callables return normal values, or an awaitable that needs an - `await` if at least one composed callable returns an awaitable. - """ - def __call__(self, *args): - return self, args - self, args = __call__(*args) - result = self.__wrapped__(*args, **kwargs) - if _isawaitable(result): - return _finish(self._wrappers, result) - wrappers = iter(self._wrappers) - for function in wrappers: - result = function(result) - if _isawaitable(result): - return _finish(wrappers, result) - return result - - __repr__ = compose.__repr__ - __get__ = compose.__get__ - functions = compose.functions - - -async def _finish(remaining_functions, first_awaitable_result): - result = await first_awaitable_result - for function in remaining_functions: - result = function(result) - if _isawaitable(result): - result = await result - return result - - -# Portability to some minimal Python implementations: -try: - compose.__name__ -except AttributeError: - compose.__name__ = 'compose' - acompose.__name__ = 'acompose' - sacompose.__name__ = 'sacompose'