Skip to content

Commit

Permalink
Removed support for promise-like thenables (then)
Browse files Browse the repository at this point in the history
Making objects with `.then` method was causing some issues with rx.Observable
  • Loading branch information
syrusakbary committed Jul 14, 2017
1 parent 579cf46 commit 50902a9
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 50 deletions.
46 changes: 22 additions & 24 deletions promise/promise.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class Promise(object):
_rejection_handler0 = None # type: Union[Callable, partial]
_promise0 = None # type: Promise
_future = None # type: Future
_traceback = None # type: TracebackType
_traceback = None # type: TracebackType
# _trace = None
_is_waiting = False

Expand Down Expand Up @@ -226,7 +226,8 @@ def _ensure_possible_rejection_handled(self):
pass

def _reject_callback(self, reason, synchronous=False, traceback=None):
assert isinstance(reason, Exception), "A promise was rejected with a non-error: {}".format(reason)
assert isinstance(
reason, Exception), "A promise was rejected with a non-error: {}".format(reason)
# trace = ensure_error_object(reason)
# has_stack = trace is reason
# self._attach_extratrace(trace, synchronous and has_stack)
Expand Down Expand Up @@ -376,7 +377,8 @@ def _settle_promises(self):
if self._state == STATE_REJECTED:
reason = self._fulfillment_handler0
traceback = self._traceback
self._settle_promise0(self._rejection_handler0, reason, traceback)
self._settle_promise0(
self._rejection_handler0, reason, traceback)
self._reject_promises(length, reason)
else:
value = self._rejection_handler0
Expand Down Expand Up @@ -407,7 +409,7 @@ def reject(reason):

if error is not None:
self._reject_callback(error, True, traceback)

@classmethod
def wait(self, promise, timeout=None):
if not promise.is_pending:
Expand All @@ -425,7 +427,6 @@ def get(self, timeout=None):
self._wait(timeout or DEFAULT_TIMEOUT)
return self._target_settled_value(_raise=True)


def _target_settled_value(self, _raise=False):
return self._target()._settled_value(_raise)

Expand Down Expand Up @@ -497,7 +498,8 @@ def _then(self, did_fulfill=None, did_reject=None):
handler = did_reject
# target._rejection_is_unhandled = False
async_instance.invoke(
partial(target._settle_promise, promise, handler, value, traceback),
partial(target._settle_promise, promise,
handler, value, traceback),
# target._settle_promise instead?
# settler,
# target,
Expand Down Expand Up @@ -597,7 +599,9 @@ def then_all(self, handlers=None):
@classmethod
def _try_convert_to_promise(cls, obj):
_type = obj.__class__
if issubclass(_type, cls):
if issubclass(_type, Promise):
if cls is not Promise:
return cls(obj.then)
return obj

if iscoroutine(obj):
Expand All @@ -609,14 +613,12 @@ def executor(resolve, reject):
if obj.done():
_process_future_result(resolve, reject)(obj)
else:
obj.add_done_callback(_process_future_result(resolve, reject))
obj.add_done_callback(
_process_future_result(resolve, reject))
# _process_future_result(resolve, reject)(obj)
promise = cls(executor)
promise._future = obj
return promise

if is_promise_like(_type):
return cls(obj.then)

return obj

Expand Down Expand Up @@ -648,7 +650,8 @@ def resolve(cls, obj):
@classmethod
def promisify(cls, f):
if not callable(f):
warn("Promise.promisify is now a function decorator, please use Promise.resolve instead.")
warn(
"Promise.promisify is now a function decorator, please use Promise.resolve instead.")
return cls.resolve(f)

@wraps(f)
Expand All @@ -672,7 +675,7 @@ def executor(resolve, reject):

# @wraps(fn)
# def wrapper(*args, **kwargs):
# return cls._safe_resolved_promise.then(lambda v: fn(*args, **kwargs))
# return cls._safe_resolved_promise.then(lambda v: fn(*args, **kwargs))

# return wrapper

Expand Down Expand Up @@ -710,26 +713,21 @@ def is_thenable(cls, obj):
if obj is None or _type in BASE_TYPES:
return False

return issubclass(_type, cls) or \
return issubclass(_type, Promise) or \
iscoroutine(obj) or \
is_future_like(_type) or \
is_promise_like(_type)
is_future_like(_type)


_type_done_callbacks = {} # type: Dict[type, bool]


def is_future_like(_type):
if _type not in _type_done_callbacks:
_type_done_callbacks[_type] = callable(getattr(_type, 'add_done_callback', None))
_type_done_callbacks[_type] = callable(
getattr(_type, 'add_done_callback', None))
return _type_done_callbacks[_type]


_type_then_callbacks = {} # type: Dict[type, bool]
def is_promise_like(_type):
if _type not in _type_then_callbacks:
_type_then_callbacks[_type] = callable(getattr(_type, 'then', None))
return _type_then_callbacks[_type]


promisify = Promise.promisify
promise_for_dict = Promise.for_dict
is_thenable = Promise.is_thenable
Expand Down
27 changes: 1 addition & 26 deletions tests/test_extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,6 @@ def after_throws(v):
assert assert_exc.traceback[-1].path.strpath == __file__


def test_fake_promise():
p = Promise()
p.do_resolve(FakeThenPromise())
assert p.is_rejected
assert_exception(p.reason, Exception, "FakeThenPromise raises in 'then'")


# WAIT
# def test_wait_when():
# p1 = df(5, 0.01)
Expand Down Expand Up @@ -493,7 +486,7 @@ def test_is_thenable_promise():

def test_is_thenable_then_object():
promise = FakeThenPromise()
assert is_thenable(promise)
assert not is_thenable(promise)


def test_is_thenable_future():
Expand Down Expand Up @@ -521,13 +514,6 @@ def test_resolve_then_object(resolve):
assert isinstance(p, Promise)


def test_resolve_then_object_exception(resolve):
promise = FakeThenPromise()
with raises(Exception) as excinfo:
resolve(promise).get()
assert str(excinfo.value) == "FakeThenPromise raises in 'then'"


def test_resolve_future(resolve):
future = Future()
promise = resolve(future)
Expand Down Expand Up @@ -602,17 +588,6 @@ def executor(resolve, reject):
assert p.get(.1) == 2


def test_resolve_promise_like(resolve):
class CustomThenable(object):
def then(self, resolve, reject):
resolve(True)

instance = CustomThenable()

promise = resolve(instance)
assert promise.get() == True


def test_resolve_future_like(resolve):
class CustomThenable(object):
def add_done_callback(self, f):
Expand Down

0 comments on commit 50902a9

Please sign in to comment.