...
 
from threading import Thread from threading import Thread
from typing import Callable, Any, TypeVar, Optional from typing import Callable, Any, TypeVar, Optional, Generic, List
from typing import Iterable from typing import Iterable
__all__ = ['Call'] __all__ = ['Call']
T = TypeVar('T') T = TypeVar('T')
TT = TypeVar('TT') TT = TypeVar('TT')
E = TypeVar('E') E = TypeVar('E', bound=Exception)
Thenable = Callable[[T], TT] Thenable = Callable[[T], TT]
Resolvable = Callable[[T], None] Resolvable = Callable[[T], None]
...@@ -14,14 +14,14 @@ Rejectable = Callable[[E], None] ...@@ -14,14 +14,14 @@ Rejectable = Callable[[E], None]
Callback = Callable[[Callable, Callable], Any] Callback = Callable[[Callable, Callable], Any]
class Call: class Call(Generic[T]):
"""Asynchronously run code, letting further code subscribe to resolved values or failed exceptions.""" """Asynchronously run code, letting further code subscribe to resolved values or failed exceptions."""
PENDING = 'PENDING' PENDING = 'PENDING'
RESOLVED = 'RESOLVED' RESOLVED = 'RESOLVED'
REJECTED = 'REJECTED' REJECTED = 'REJECTED'
def __init__(self, callback): def __init__(self, callback):
# type: (Callback) -> Call # type: (Callback) -> Call[T]
"""Initialize a new asynchronous Call. """Initialize a new asynchronous Call.
The callback must have signature (resolve, reject), which are two callback functions of their own; the first one The callback must have signature (resolve, reject), which are two callback functions of their own; the first one
is to be called with the resulting value, while the second one is to be called with an error. is to be called with the resulting value, while the second one is to be called with an error.
...@@ -29,16 +29,17 @@ class Call: ...@@ -29,16 +29,17 @@ class Call:
:param callback: Callback function. Must have (resolve, reject) functions.""" :param callback: Callback function. Must have (resolve, reject) functions."""
self.status = self.PENDING self.status = self.PENDING
self.data = None # type: T self.data = None # type: T
self.error = None # type: E self.error = None # type: Optional[E]
self.t = Thread(target=callback, args=(self._on_resolve, self._on_rejected)) self.t = Thread(target=callback, args=(self._on_resolve, self._on_rejected))
self.t.start() self.t.start()
def then(self, callback): def then(self, callback):
# type: (Thenable) -> Call # type: (Thenable) -> Call[TT]
"""Chain callback, called with the resolved value of the previous Call. """Chain callback, called with the resolved value of the previous Call.
:param callback: Callback function to be called with the resolved value of the current Call.""" :param callback: Callback function to be called with the resolved value of the current Call."""
def cb(resolve, reject): def cb(resolve, reject):
# type: (Callable, Callable) -> None # type: (Callable, Callable) -> None
self.t.join() self.t.join()
...@@ -58,6 +59,7 @@ class Call: ...@@ -58,6 +59,7 @@ class Call:
"""Chain callback, called if a failure occurred somewhere in the chain before this. """Chain callback, called if a failure occurred somewhere in the chain before this.
:param callback: Callback function, called on error further up the chain.""" :param callback: Callback function, called on error further up the chain."""
def cb(resolve, reject): def cb(resolve, reject):
self.t.join() self.t.join()
if self.status == self.REJECTED: if self.status == self.REJECTED:
...@@ -88,7 +90,7 @@ class Call: ...@@ -88,7 +90,7 @@ class Call:
@classmethod @classmethod
def resolve(cls, value=None): def resolve(cls, value=None):
# type: (Optional[T]) -> Call # type: (TT) -> Call[TT]
"""Create a Call that immediately resolves with the value """Create a Call that immediately resolves with the value
:param value: Value to be resolved to""" :param value: Value to be resolved to"""
...@@ -96,7 +98,7 @@ class Call: ...@@ -96,7 +98,7 @@ class Call:
@classmethod @classmethod
def reject(cls, error): def reject(cls, error):
# type: (E) -> Call # type: (E) -> Call[None]
"""Create a Call that immediately rejects with the error """Create a Call that immediately rejects with the error
:param error: Error to be passed. If not an exception, will be turned into one.""" :param error: Error to be passed. If not an exception, will be turned into one."""
...@@ -106,7 +108,7 @@ class Call: ...@@ -106,7 +108,7 @@ class Call:
@classmethod @classmethod
def all(cls, calls): def all(cls, calls):
# type: (Iterable[Call]) -> Call # type: (Iterable[Call[TT]]) -> Call[List[TT]]
"""Resolve a list of calls' resolved values, or fail with the first exception """Resolve a list of calls' resolved values, or fail with the first exception
:param calls: List of calls to resolve, in the same order than the Calls list""" :param calls: List of calls to resolve, in the same order than the Calls list"""
...@@ -124,19 +126,21 @@ class Call: ...@@ -124,19 +126,21 @@ class Call:
@classmethod @classmethod
def from_function(cls, func, *args, **kwargs): def from_function(cls, func, *args, **kwargs):
# type: (Callable[[Any], T], *Any, **Any) -> Call # type: (Callable[[Any], TT], *Any, **Any) -> Call[TT]
"""Create a Call from a synchronous function. The function will then be called asynchronously, its return """Create a Call from a synchronous function. The function will then be called asynchronously, its return
value used as the resolved value, and any exception raised as a reject error value. value used as the resolved value, and any exception raised as a reject error value.
:param func: Synchronous function to be called :param func: Synchronous function to be called
:param args: Positional arguments to be passed to the function func :param args: Positional arguments to be passed to the function func
:param kwargs: Dictionary arguments to be passed to the function func""" :param kwargs: Dictionary arguments to be passed to the function func"""
def cb(resolve, reject): def cb(resolve, reject):
# type: (Callable, Callable) -> None # type: (Callable, Callable) -> None
try: try:
resolve(func(*args, **kwargs)) resolve(func(*args, **kwargs))
except Exception as e: except Exception as e:
reject(e) reject(e)
return Call(cb) return Call(cb)
def _on_resolve(self, data): def _on_resolve(self, data):
......