Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Promise class methods #4

Merged
merged 10 commits into from
Jun 1, 2016
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ cache: pip
install:
- pip install pytest pytest-cov coveralls flake8
- pip install futures
- |
if python -c 'import sys; sys.exit(1 if sys.hexversion<0x03000000 else 0)'
then
pip install pytest-asyncio
fi
- pip install -e .
script:
- flake8
Expand Down
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ This creates and returns a new promise. `resolver` must be a function. The `re
1. `resolve` should be called with a single argument. If it is called with a non-promise value then the promise is fulfilled with that value. If it is called with a promise (A) then the returned promise takes on the state of that new promise (A).
2. `reject` should be called with a single argument. The returned promise will be rejected with that argument.

### Static Functions
### Class Methods

These methods are invoked by calling `Promise.methodName`.

#### Promise.resolve(value)

Converts values and foreign promises into Promises/A+ promises. If you pass it a value then it returns a Promise for that value. If you pass it something that is close to a promise (such as a jQuery attempt at a promise) it returns a Promise that takes on the state of `value` (rejected or fulfilled).

#### Promise.reject(value)
#### Promise.rejected(value)

Returns a rejected promise with the given value.

Expand All @@ -71,6 +71,20 @@ p = Promise.all([Promise.resolve('a'), 'b', Promise.resolve('c')]) \
assert p.value is True
```

#### Promise.promisify(obj)

This function wraps the `obj` act as a `Promise` if possible.
Python `Future`s are supported, with a callback to `promise.done` when resolved.


#### Promise.for_dict(d)

A special function that takes a dictionary of promises and turns them
into a promise for a dictionary of values. In other words, this turns
an dictionary of promises for values into a promise for a dictionary
of values.


### Instance Methods

These methods are invoked on a promise instance by calling `myPromise.methodName`
Expand Down Expand Up @@ -100,12 +114,6 @@ The same semantics as `.then` except that it does not return a promise and any e
This function checks if the `obj` is a `Promise`, or could be `promisify`ed.


### promisify(obj)

This function wraps the `obj` act as a `Promise` if possible.
Python `Future`s are supported, with a callback to `promise.done` when resolved.


# Notes

This package is heavily insipired in [aplus](https://github.com/xogeny/aplus).
Expand Down
30 changes: 19 additions & 11 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ The ``resolver`` function is passed two arguments:
2. ``reject`` should be called with a single argument. The returned
promise will be rejected with that argument.

Static Functions
~~~~~~~~~~~~~~~~
Class Methods
~~~~~~~~~~~~~

These methods are invoked by calling ``Promise.methodName``.

Expand All @@ -71,8 +71,8 @@ something that is close to a promise (such as a jQuery attempt at a
promise) it returns a Promise that takes on the state of ``value``
(rejected or fulfilled).

Promise.reject(value)
^^^^^^^^^^^^^^^^^^^^^
Promise.rejected(value)
^^^^^^^^^^^^^^^^^^^^^^^

Returns a rejected promise with the given value.

Expand All @@ -90,6 +90,21 @@ replaced by their fulfilled values. e.g.

assert p.value is True

Promise.promisify(obj)
^^^^^^^^^^^^^^^^^^^^^^

This function wraps the ``obj`` act as a ``Promise`` if possible. Python
``Future``\ s are supported, with a callback to ``promise.done`` when
resolved.

Promise.for\_dict(d)
^^^^^^^^^^^^^^^^^^^^

A special function that takes a dictionary of promises and turns them
into a promise for a dictionary of values. In other words, this turns an
dictionary of promises for values into a promise for a dictionary of
values.

Instance Methods
~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -141,13 +156,6 @@ is\_thenable(obj)
This function checks if the ``obj`` is a ``Promise``, or could be
``promisify``\ ed.

promisify(obj)
~~~~~~~~~~~~~~

This function wraps the ``obj`` act as a ``Promise`` if possible. Python
``Future``\ s are supported, with a callback to ``promise.done`` when
resolved.

Notes
=====

Expand Down
116 changes: 63 additions & 53 deletions promise.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ def __init__(self, fn=None):
if fn:
self.do_resolve(fn)

def __iter__(self):
yield self.get()

def __await__(self):
yield self.get()

def do_resolve(self, fn):
self._done = False

Expand All @@ -78,15 +84,15 @@ def reject_fn(x):
except Exception as e:
self.reject(e)

@staticmethod
def fulfilled(x):
p = Promise()
@classmethod
def fulfilled(cls, x):
p = cls()
p.fulfill(x)
return p

@staticmethod
def rejected(reason):
p = Promise()
@classmethod
def rejected(cls, reason):
p = cls()
p.reject(reason)
return p

Expand All @@ -99,7 +105,7 @@ def fulfill(self, x):
raise TypeError("Cannot resolve promise with itself.")
elif is_thenable(x):
try:
promisify(x).done(self.fulfill, self.reject)
self.promisify(x).done(self.fulfill, self.reject)
except Exception as e:
self.reject(e)
else:
Expand All @@ -109,7 +115,7 @@ def fulfill(self, x):

def _fulfill(self, value):
with self._cb_lock:
if self._state != Promise.PENDING:
if self._state != self.PENDING:
return

self._value = value
Expand Down Expand Up @@ -141,7 +147,7 @@ def reject(self, reason):
assert isinstance(reason, Exception)

with self._cb_lock:
if self._state != Promise.PENDING:
if self._state != self.PENDING:
return

self._reason = reason
Expand Down Expand Up @@ -321,7 +327,7 @@ def then(self, success=None, failure=None):
:type failure: (object) -> object
:rtype : Promise
"""
ret = Promise()
ret = self.__class__()

def call_and_fulfill(v):
"""
Expand Down Expand Up @@ -384,8 +390,8 @@ def then_all(self, *handlers):

return promises

@staticmethod
def all(values_or_promises):
@classmethod
def all(cls, values_or_promises):
"""
A special function that takes a bunch of promises
and turns them into a promise for a vector of values.
Expand All @@ -395,9 +401,9 @@ def all(values_or_promises):
promises = list(filter(is_thenable, values_or_promises))
if len(promises) == 0:
# All the values or promises are resolved
return Promise.fulfilled(values_or_promises)
return cls.fulfilled(values_or_promises)

all_promise = Promise()
all_promise = cls()
counter = CountdownLatch(len(promises))

def handleSuccess(_):
Expand All @@ -406,10 +412,52 @@ def handleSuccess(_):
all_promise.fulfill(values)

for p in promises:
promisify(p).done(handleSuccess, all_promise.reject)
cls.promisify(p).done(handleSuccess, all_promise.reject)

return all_promise

@classmethod
def promisify(cls, obj):
if isinstance(obj, cls):
return obj
elif is_future(obj):
promise = cls()
obj.add_done_callback(_process_future_result(promise))
return promise
elif hasattr(obj, "done") and callable(getattr(obj, "done")):
p = cls()
obj.done(p.fulfill, p.reject)
return p
elif hasattr(obj, "then") and callable(getattr(obj, "then")):
p = cls()
obj.then(p.fulfill, p.reject)
return p
else:
raise TypeError("Object is not a Promise like object.")

@classmethod
def for_dict(cls, m):
"""
A special function that takes a dictionary of promises
and turns them into a promise for a dictionary of values.
In other words, this turns an dictionary of promises for values
into a promise for a dictionary of values.
"""
if not m:
return cls.fulfilled({})

keys, values = zip(*m.items())
dict_type = type(m)

def handleSuccess(resolved_values):
return dict_type(zip(keys, resolved_values))

return cls.all(values).then(handleSuccess)


promisify = Promise.promisify
promise_for_dict = Promise.for_dict


def _process_future_result(promise):
def handle_future_result(future):
Expand All @@ -434,41 +482,3 @@ def is_thenable(obj):
return isinstance(obj, Promise) or is_future(obj) or (
hasattr(obj, "done") and callable(getattr(obj, "done"))) or (
hasattr(obj, "then") and callable(getattr(obj, "then")))


def promisify(obj):
if isinstance(obj, Promise):
return obj
elif is_future(obj):
promise = Promise()
obj.add_done_callback(_process_future_result(promise))
return promise
elif hasattr(obj, "done") and callable(getattr(obj, "done")):
p = Promise()
obj.done(p.fulfill, p.reject)
return p
elif hasattr(obj, "then") and callable(getattr(obj, "then")):
p = Promise()
obj.then(p.fulfill, p.reject)
return p
else:
raise TypeError("Object is not a Promise like object.")


def promise_for_dict(m):
"""
A special function that takes a dictionary of promises
and turns them into a promise for a dictionary of values.
In other words, this turns an dictionary of promises for values
into a promise for a dictionary of values.
"""
if not m:
return Promise.fulfilled({})

keys, values = zip(*m.items())
dict_type = type(m)

def handleSuccess(resolved_values):
return dict_type(zip(keys, resolved_values))

return Promise.all(values).then(handleSuccess)
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import sys

collect_ignore = []
if sys.version_info[:2] < (3, 4):
collect_ignore.append('test_awaitable.py')
if sys.version_info[:2] < (3, 5):
collect_ignore.append('test_awaitable_35.py')
9 changes: 9 additions & 0 deletions tests/test_awaitable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import asyncio
import pytest
from promise import Promise


@pytest.mark.asyncio
@asyncio.coroutine
def test_await():
yield from Promise.resolve(None)
7 changes: 7 additions & 0 deletions tests/test_awaitable_35.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pytest
from promise import Promise


@pytest.mark.asyncio
async def test_await():
await Promise.resolve(None)
Loading