Skip to content

Commit

Permalink
Merge 978d85b into ec22bcf
Browse files Browse the repository at this point in the history
  • Loading branch information
syrusakbary committed Mar 13, 2017
2 parents ec22bcf + 978d85b commit 57bbf1f
Show file tree
Hide file tree
Showing 25 changed files with 2,333 additions and 609 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[run]
omit = promise/compat.py,promise/iterate_promise.py
omit = promise/compat.py,promise/iterate_promise.py,promise/utils.py
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ matrix:
script: flake8
- python: '3.5'
script: |
pip install mypy-lang
mypy promise/ --check-untyped-defs
pip install mypy
mypy promise/ --check-untyped-defs --ignore-missing-imports
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ This creates and returns a new promise. `resolver` must be a function. The `re

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.rejected(value)
#### Promise.reject(value)

Returns a rejected promise with the given value.

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

#### Promise.promisify(obj)
#### Promise.cast(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.
Have the same effects as `Promise.resolve(obj)`.


#### Promise.for_dict(d)
Expand All @@ -85,41 +86,40 @@ an dictionary of promises for values into a promise for a dictionary
of values.


#### Promise.is_thenable(obj)

This function checks if the `obj` is a `Promise`, or could be `promisify`ed.


### Instance Methods

These methods are invoked on a promise instance by calling `myPromise.methodName`

### promise.then(on_fulfilled, on_rejected)
### promise.then(did_fulfill, did_reject)

This method follows the [Promises/A+ spec](http://promises-aplus.github.io/promises-spec/). It explains things very clearly so I recommend you read it.

Either `on_fulfilled` or `on_rejected` will be called and they will not be called more than once. They will be passed a single argument and will always be called asynchronously (in the next turn of the event loop).
Either `did_fulfill` or `did_reject` will be called and they will not be called more than once. They will be passed a single argument and will always be called asynchronously (in the next turn of the event loop).

If the promise is fulfilled then `on_fulfilled` is called. If the promise is rejected then `on_rejected` is called.
If the promise is fulfilled then `did_fulfill` is called. If the promise is rejected then `did_reject` is called.

The call to `.then` also returns a promise. If the handler that is called returns a promise, the promise returned by `.then` takes on the state of that returned promise. If the handler that is called returns a value that is not a promise, the promise returned by `.then` will be fulfilled with that value. If the handler that is called throws an exception then the promise returned by `.then` is rejected with that exception.

#### promise.catch(on_rejected)
#### promise.catch(did_reject)

Sugar for `promise.then(None, on_rejected)`, to mirror `catch` in synchronous code.
Sugar for `promise.then(None, did_reject)`, to mirror `catch` in synchronous code.

#### promise.done(on_fulfilled, on_rejected)
#### promise.done(did_fulfill, did_reject)

The same semantics as `.then` except that it does not return a promise and any exceptions are re-thrown so that they can be logged (crashing the application in non-browser environments)

## Other package functions

### is_thenable(obj)

This function checks if the `obj` is a `Promise`, or could be `promisify`ed.


# Contributing

After cloning this repo, ensure dependencies are installed by running:

```sh
pip install .[test]
pip install -e ".[test]"
```

After developing, the full test suite can be evaluated by running:
Expand Down
60 changes: 39 additions & 21 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.rejected(value)
^^^^^^^^^^^^^^^^^^^^^^^
Promise.reject(value)
^^^^^^^^^^^^^^^^^^^^^

Returns a rejected promise with the given value.

Expand All @@ -90,12 +90,12 @@ replaced by their fulfilled values. e.g.
assert p.get() is True
Promise.promisify(obj)
^^^^^^^^^^^^^^^^^^^^^^
Promise.cast(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.
resolved. Have the same effects as ``Promise.resolve(obj)``.

Promise.for\_dict(d)
^^^^^^^^^^^^^^^^^^^^
Expand All @@ -105,26 +105,32 @@ 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.

Promise.is\_thenable(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^

This function checks if the ``obj`` is a ``Promise``, or could be
``promisify``\ ed.

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

These methods are invoked on a promise instance by calling
``myPromise.methodName``

promise.then(on\_fulfilled, on\_rejected)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
promise.then(did\_fulfill, did\_reject)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This method follows the `Promises/A+
spec <http://promises-aplus.github.io/promises-spec/>`__. It explains
things very clearly so I recommend you read it.

Either ``on_fulfilled`` or ``on_rejected`` will be called and they will
Either ``did_fulfill`` or ``did_reject`` will be called and they will
not be called more than once. They will be passed a single argument and
will always be called asynchronously (in the next turn of the event
loop).

If the promise is fulfilled then ``on_fulfilled`` is called. If the
promise is rejected then ``on_rejected`` is called.
If the promise is fulfilled then ``did_fulfill`` is called. If the
promise is rejected then ``did_reject`` is called.

The call to ``.then`` also returns a promise. If the handler that is
called returns a promise, the promise returned by ``.then`` takes on the
Expand All @@ -134,27 +140,39 @@ fulfilled with that value. If the handler that is called throws an
exception then the promise returned by ``.then`` is rejected with that
exception.

promise.catch(on\_rejected)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
promise.catch(did\_reject)
^^^^^^^^^^^^^^^^^^^^^^^^^^

Sugar for ``promise.then(None, on_rejected)``, to mirror ``catch`` in
Sugar for ``promise.then(None, did_reject)``, to mirror ``catch`` in
synchronous code.

promise.done(on\_fulfilled, on\_rejected)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
promise.done(did\_fulfill, did\_reject)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The same semantics as ``.then`` except that it does not return a promise
and any exceptions are re-thrown so that they can be logged (crashing
the application in non-browser environments)

Other package functions
-----------------------
Contributing
============

is\_thenable(obj)
~~~~~~~~~~~~~~~~~
After cloning this repo, ensure dependencies are installed by running:

This function checks if the ``obj`` is a ``Promise``, or could be
``promisify``\ ed.
.. code:: sh
pip install -e ".[test]"
After developing, the full test suite can be evaluated by running:

.. code:: sh
py.test tests --cov=promise --benchmark-skip # Use -v -s for verbose mode
You can also run the benchmarks with:

.. code:: sh
py.test tests --benchmark-only
Notes
=====
Expand Down
21 changes: 19 additions & 2 deletions promise/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
from .promise import Promise, promise_for_dict, promisify, is_thenable
from .pyutils.version import get_version

__all__ = ['Promise', 'promise_for_dict', 'promisify', 'is_thenable']

try:
# This variable is injected in the __builtins__ by the build
# process. It used to enable importing subpackages when
# the required packages are not installed
__SETUP__ # type: ignore
except NameError:
__SETUP__ = False


VERSION = (2, 0, 0, 'alpha', 0)

__version__ = get_version(VERSION)

if not __SETUP__:
from .promise import Promise, promise_for_dict, promisify, is_thenable, async_instance

__all__ = ['Promise', 'promise_for_dict', 'promisify', 'is_thenable', 'async_instance']
126 changes: 126 additions & 0 deletions promise/async_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# from threading import Timer, Thread

from .compat import Queue
# from .context import Context

# Based on https://github.com/petkaantonov/bluebird/blob/master/src/async.js


class Scheduler(object):

def call(self, fn):
# thread = Thread(target=fn)
# thread = Timer(0.01, fn)
# fn = thread.start
try:
# c = Context.peek_context()
# if not c:
fn()
# else:
# c.on_exit(fn)
except:
pass
# thread = Thread(target=fn)
# thread = Timer(0.001, fn)
# thread.start()


def get_default_scheduler():
return Scheduler()


# https://docs.python.org/2/library/queue.html#Queue.Queue
LATE_QUEUE_CAPACITY = 0 # The queue size is infinite
NORMAL_QUEUE_CAPACITY = 0 # The queue size is infinite


class Async(object):

def __init__(self, schedule=None):
self.is_tick_used = False
self.late_queue = Queue(LATE_QUEUE_CAPACITY)
self.normal_queue = Queue(NORMAL_QUEUE_CAPACITY)
self.have_drained_queues = False
self.trampoline_enabled = True
self.schedule = schedule or get_default_scheduler()

def enable_trampoline(self):
self.trampoline_enabled = True

def disable_trampoline(self):
self.trampoline_enabled = False

def have_items_queued(self):
return self.is_tick_used or self.have_drained_queues

def _async_invoke_later(self, fn, context):
self.late_queue.put(fn)
self.queue_tick(context)

def _async_invoke(self, fn, context):
self.normal_queue.put(fn)
self.queue_tick(context)

def _async_settle_promise(self, promise):
self.normal_queue.put(promise)
self.queue_tick(context=promise._trace)

def invoke_later(self, fn, context):
if self.trampoline_enabled:
self._async_invoke_later(fn, context)
else:
self.schedule.call_later(0.1, fn)

def invoke(self, fn, context):
if self.trampoline_enabled:
self._async_invoke(fn, context)
else:
self.schedule.call(
fn
)

def settle_promises(self, promise):
if self.trampoline_enabled:
self._async_settle_promise(promise)
else:
self.schedule.call(
promise._settle_promises
)

def throw_later(self, reason):
def fn():
raise reason

self.schedule.call(fn)

fatal_error = throw_later

def drain_queue(self, queue):
from .promise import Promise
while not queue.empty():
fn = queue.get()
if (isinstance(fn, Promise)):
fn._settle_promises()
continue
fn()

def drain_queues(self):
assert self.is_tick_used
self.drain_queue(self.normal_queue)
self.reset()
self.have_drained_queues = True
self.drain_queue(self.late_queue)

def queue_context_tick(self):
if not self.is_tick_used:
self.is_tick_used = True
self.schedule.call(self.drain_queues)

def queue_tick(self, context):
if not context:
self.queue_context_tick()
else:
context.on_exit(self.queue_context_tick)

def reset(self):
self.is_tick_used = False
5 changes: 5 additions & 0 deletions promise/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
except ImportError:

class Future: # type: ignore

def __init__(self):
raise Exception("You need asyncio for using Futures")

Expand All @@ -18,6 +19,10 @@ def ensure_future(): # type: ignore
def iscoroutine(obj): # type: ignore
return False

try:
from Queue import Queue # type: ignore # flake8: noqa
except ImportError:
from queue import Queue # type: ignore # flake8: noqa

try:
from .iterate_promise import iterate_promise
Expand Down
Loading

0 comments on commit 57bbf1f

Please sign in to comment.