pax_global_header 0000666 0000000 0000000 00000000064 13576352764 0014534 g ustar 00root root 0000000 0000000 52 comment=ad8ebe68cd9c9686793e3449457f175a0fe43226
promise-2.3.0/ 0000775 0000000 0000000 00000000000 13576352764 0013214 5 ustar 00root root 0000000 0000000 promise-2.3.0/.coveragerc 0000664 0000000 0000000 00000000113 13576352764 0015330 0 ustar 00root root 0000000 0000000 [run]
omit = promise/compat.py,promise/iterate_promise.py,promise/utils.py
promise-2.3.0/.gitignore 0000664 0000000 0000000 00000001524 13576352764 0015206 0 ustar 00root root 0000000 0000000 # Created by https://www.gitignore.io
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# IntelliJ
.idea
# OS X
.DS_Store
/.vscode
/.pytest_cache
/.pyre
/.mypy_cache
/type_info.json
promise-2.3.0/.travis.yml 0000664 0000000 0000000 00000000666 13576352764 0015335 0 ustar 00root root 0000000 0000000 language: python
sudo: false
python:
- 2.7
- 3.5
- 3.6
- 3.7
- 3.8
- pypy
cache: pip
install:
- pip install -e .[test]
script:
- py.test --cov=promise tests
after_success:
- coveralls
matrix:
fast_finish: true
include:
- python: '3.8'
script: |
# pip install pyre-check
# pyre --source-directory promise check
pip install flake8
flake8
pip install mypy
mypy promise --ignore-missing-imports
promise-2.3.0/LICENSE 0000664 0000000 0000000 00000002067 13576352764 0014226 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2016 Syrus Akbary
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
promise-2.3.0/MANIFEST.in 0000664 0000000 0000000 00000000101 13576352764 0014742 0 ustar 00root root 0000000 0000000 global-exclude tests/*
recursive-exclude tests *
include LICENSE
promise-2.3.0/README.md 0000664 0000000 0000000 00000013107 13576352764 0014475 0 ustar 00root root 0000000 0000000 # Promise
This is a implementation of Promises in Python.
It is a super set of Promises/A+ designed to have readable, performant code and to provide just the extensions that are absolutely necessary for using promises in Python.
Its fully compatible with the [Promises/A+ spec](http://promises-aplus.github.io/promises-spec/)
[![travis][travis-image]][travis-url]
[![pypi][pypi-image]][pypi-url]
[![coveralls][coveralls-image]][coveralls-url]
[travis-image]: https://img.shields.io/travis/syrusakbary/promise.svg?style=flat
[travis-url]: https://travis-ci.org/syrusakbary/promise
[pypi-image]: https://img.shields.io/pypi/v/promise.svg?style=flat
[pypi-url]: https://pypi.python.org/pypi/promise
[coveralls-image]: https://coveralls.io/repos/syrusakbary/promise/badge.svg?branch=master&service=github
[coveralls-url]: https://coveralls.io/github/syrusakbary/promise?branch=master
## Installation
$ pip install promise
## Usage
The example below shows how you can load the promise library. It then demonstrates creating a promise from scratch. You simply call `Promise(fn)`. There is a complete specification for what is returned by this method in [Promises/A+](http://promises-aplus.github.com/promises-spec/).
```python
from promise import Promise
promise = Promise(
lambda resolve, reject: resolve('RESOLVED!')
)
```
## API
Before all examples, you will need:
```python
from promise import Promise
```
### Promise(resolver)
This creates and returns a new promise. `resolver` must be a function. The `resolver` function is passed two arguments:
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.
### 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)
Returns a rejected promise with the given value.
#### Promise.all(list)
Returns a promise for a list. If it is called with a single argument then this returns a promise for a copy of that list with any promises replaced by their fulfilled values. e.g.
```python
p = Promise.all([Promise.resolve('a'), 'b', Promise.resolve('c')]) \
.then(lambda res: res == ['a', 'b', 'c'])
assert p.get() is True
```
#### Promise.cast(obj)
This function wraps the `obj` ect as a `Promise` if possible.
Python `Future`s are supported, with a callback to `promise.done` when resolved.
Has the same effects as `Promise.resolve(obj)`.
#### 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
a 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 `cast`ed.
#### Promise.promisify(func)
This function wraps the result of calling `func` in a `Promise` instance.
### Instance Methods
These methods are invoked on a promise instance by calling `myPromise.methodName`
### 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 `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 `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(did_reject)
Sugar for `promise.then(None, did_reject)`, to mirror `catch` in synchronous code.
#### 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)
# Contributing
After cloning this repo, ensure dependencies are installed by running:
```sh
pip install -e ".[test]"
```
After developing, the full test suite can be evaluated by running:
```sh
py.test tests --cov=promise --benchmark-skip # Use -v -s for verbose mode
```
You can also run the benchmarks with:
```sh
py.test tests --benchmark-only
```
## Static type checking
Python type annotations are very useful for making sure we use the libary the way is intended.
You can run `mypy` static type checker:
```sh
pip install mypy
mypy promise --ignore-missing-imports
```
Or `pyre`:
```sh
pip install pyre-check
pyre --source-directory promise check
```
# Notes
This package is heavily insipired in [aplus](https://github.com/xogeny/aplus).
## License
[MIT License](https://github.com/syrusakbary/promise/blob/master/LICENSE)
promise-2.3.0/README.rst 0000664 0000000 0000000 00000013750 13576352764 0014711 0 ustar 00root root 0000000 0000000 Promise
=======
This is a implementation of Promises in Python. It is a super set of
Promises/A+ designed to have readable, performant code and to provide
just the extensions that are absolutely necessary for using promises in
Python.
Its fully compatible with the `Promises/A+
spec `__
|travis| |pypi| |coveralls|
Installation
------------
::
$ pip install promise
Usage
-----
The example below shows how you can load the promise library. It then
demonstrates creating a promise from scratch. You simply call
``Promise(fn)``. There is a complete specification for what is returned
by this method in
`Promises/A+ `__.
.. code:: python
from promise import Promise
promise = Promise(
lambda resolve, reject: resolve('RESOLVED!')
)
API
---
Before all examples, you will need:
.. code:: python
from promise import Promise
Promise(resolver)
~~~~~~~~~~~~~~~~~
This creates and returns a new promise. ``resolver`` must be a function.
The ``resolver`` function is passed two arguments:
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.
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)
^^^^^^^^^^^^^^^^^^^^^
Returns a rejected promise with the given value.
Promise.all(list)
^^^^^^^^^^^^^^^^^
Returns a promise for a list. If it is called with a single argument
then this returns a promise for a copy of that list with any promises
replaced by their fulfilled values. e.g.
.. code:: python
p = Promise.all([Promise.resolve('a'), 'b', Promise.resolve('c')]) \
.then(lambda res: res == ['a', 'b', 'c'])
assert p.get() is True
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)
^^^^^^^^^^^^^^^^^^^^
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.
Promise.is\_thenable(obj)
^^^^^^^^^^^^^^^^^^^^^^^^^
This function checks if the ``obj`` is a ``Promise``, or could be
``cast``\ ed.
Promise.promisify(func)
^^^^^^^^^^^^^^^^^^^^^^^
This function wraps the result of calling ``func`` in a ``Promise``
instance.
Instance Methods
~~~~~~~~~~~~~~~~
These methods are invoked on a promise instance by calling
``myPromise.methodName``
promise.then(did\_fulfill, did\_reject)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This method follows the `Promises/A+
spec `__. It explains
things very clearly so I recommend you read it.
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 ``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(did\_reject)
^^^^^^^^^^^^^^^^^^^^^^^^^^
Sugar for ``promise.then(None, did_reject)``, to mirror ``catch`` in
synchronous code.
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)
Contributing
============
After cloning this repo, ensure dependencies are installed by running:
.. 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
Static type checking
--------------------
Python type annotations are very useful for making sure we use the
libary the way is intended.
You can run ``mypy`` static type checker:
.. code:: sh
pip install mypy
mypy promise --ignore-missing-imports
Or ``pyre``:
.. code:: sh
pip install pyre-check
pyre --source-directory promise check
Notes
=====
This package is heavily insipired in
`aplus `__.
License
-------
`MIT
License `__
.. |travis| image:: https://img.shields.io/travis/syrusakbary/promise.svg?style=flat
:target: https://travis-ci.org/syrusakbary/promise
.. |pypi| image:: https://img.shields.io/pypi/v/promise.svg?style=flat
:target: https://pypi.python.org/pypi/promise
.. |coveralls| image:: https://coveralls.io/repos/syrusakbary/promise/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/syrusakbary/promise?branch=master
promise-2.3.0/conftest.py 0000664 0000000 0000000 00000001762 13576352764 0015421 0 ustar 00root root 0000000 0000000 # Configuration for pytest to automatically collect types.
# Thanks to Guilherme Salgado.
import pytest
try:
import pyannotate_runtime
PYANOTATE_PRESENT = True
except ImportError:
PYANOTATE_PRESENT = False
if PYANOTATE_PRESENT:
def pytest_collection_finish(session):
"""Handle the pytest collection finish hook: configure pyannotate.
Explicitly delay importing `collect_types` until all tests have
been collected. This gives gevent a chance to monkey patch the
world before importing pyannotate.
"""
from pyannotate_runtime import collect_types
collect_types.init_types_collection()
@pytest.fixture(autouse=True)
def collect_types_fixture():
from pyannotate_runtime import collect_types
collect_types.resume()
yield
collect_types.pause()
def pytest_sessionfinish(session, exitstatus):
from pyannotate_runtime import collect_types
collect_types.dump_stats("type_info.json")
promise-2.3.0/promise/ 0000775 0000000 0000000 00000000000 13576352764 0014672 5 ustar 00root root 0000000 0000000 promise-2.3.0/promise/__init__.py 0000664 0000000 0000000 00000001551 13576352764 0017005 0 ustar 00root root 0000000 0000000 from .pyutils.version import get_version
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, 3, 0, "final", 0)
__version__ = get_version(VERSION)
if not __SETUP__:
from .promise import (
Promise,
promise_for_dict,
promisify,
is_thenable,
async_instance,
get_default_scheduler,
set_default_scheduler,
)
from .schedulers.immediate import ImmediateScheduler
__all__ = [
"Promise",
"promise_for_dict",
"promisify",
"is_thenable",
"async_instance",
"get_default_scheduler",
"set_default_scheduler",
"ImmediateScheduler",
]
promise-2.3.0/promise/async_.py 0000664 0000000 0000000 00000007567 13576352764 0016537 0 ustar 00root root 0000000 0000000 # Based on https://github.com/petkaantonov/bluebird/blob/master/src/promise.js
from collections import deque
from threading import local
if False:
from .promise import Promise
from typing import Any, Callable, Optional, Union # flake8: noqa
class Async(local):
def __init__(self, trampoline_enabled=True):
self.is_tick_used = False
self.late_queue = deque() # type: ignore
self.normal_queue = deque() # type: ignore
self.have_drained_queues = False
self.trampoline_enabled = trampoline_enabled
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, scheduler):
self.late_queue.append(fn)
self.queue_tick(scheduler)
def _async_invoke(self, fn, scheduler):
# type: (Callable, Any) -> None
self.normal_queue.append(fn)
self.queue_tick(scheduler)
def _async_settle_promise(self, promise):
# type: (Promise) -> None
self.normal_queue.append(promise)
self.queue_tick(promise.scheduler)
def invoke_later(self, fn):
if self.trampoline_enabled:
self._async_invoke_later(fn, scheduler)
else:
scheduler.call_later(0.1, fn)
def invoke(self, fn, scheduler):
# type: (Callable, Any) -> None
if self.trampoline_enabled:
self._async_invoke(fn, scheduler)
else:
scheduler.call(fn)
def settle_promises(self, promise):
# type: (Promise) -> None
if self.trampoline_enabled:
self._async_settle_promise(promise)
else:
promise.scheduler.call(promise._settle_promises)
def throw_later(self, reason, scheduler):
# type: (Exception, Any) -> None
def fn():
# type: () -> None
raise reason
scheduler.call(fn)
fatal_error = throw_later
def drain_queue(self, queue):
# type: (deque) -> None
from .promise import Promise
while queue:
fn = queue.popleft()
if isinstance(fn, Promise):
fn._settle_promises()
continue
fn()
def drain_queue_until_resolved(self, promise):
# type: (Promise) -> None
from .promise import Promise
queue = self.normal_queue
while queue:
if not promise.is_pending:
return
fn = queue.popleft()
if isinstance(fn, Promise):
fn._settle_promises()
continue
fn()
self.reset()
self.have_drained_queues = True
self.drain_queue(self.late_queue)
def wait(self, promise, timeout=None):
# type: (Promise, Optional[float]) -> None
if not promise.is_pending:
# We return if the promise is already
# fulfilled or rejected
return
target = promise._target()
if self.trampoline_enabled:
if self.is_tick_used:
self.drain_queue_until_resolved(target)
if not promise.is_pending:
# We return if the promise is already
# fulfilled or rejected
return
target.scheduler.wait(target, timeout)
def drain_queues(self):
# type: () -> None
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_tick(self, scheduler):
# type: (Any) -> None
if not self.is_tick_used:
self.is_tick_used = True
scheduler.call(self.drain_queues)
def reset(self):
# type: () -> None
self.is_tick_used = False
promise-2.3.0/promise/compat.py 0000664 0000000 0000000 00000001553 13576352764 0016533 0 ustar 00root root 0000000 0000000 try:
from inspect import iscoroutine
except ImportError:
def iscoroutine(obj): # type: ignore
return False
try:
from asyncio import Future, ensure_future # type: ignore
except ImportError:
class Future: # type: ignore
def __init__(self):
raise Exception("You need asyncio for using Futures")
def set_result(self):
raise Exception("You need asyncio for using Futures")
def set_exception(self):
raise Exception("You need asyncio for using Futures")
def ensure_future(): # type: ignore
raise Exception("ensure_future needs asyncio for executing")
try:
from .iterate_promise import iterate_promise
except (SyntaxError, ImportError):
def iterate_promise(promise): # type: ignore
raise Exception('You need "yield from" syntax for iterate in a Promise.')
promise-2.3.0/promise/dataloader.py 0000664 0000000 0000000 00000025145 13576352764 0017353 0 ustar 00root root 0000000 0000000 from collections import namedtuple
try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable
from functools import partial
from threading import local
from .promise import Promise, async_instance, get_default_scheduler
if False:
from typing import (
Any,
List,
Sized,
Callable,
Optional,
Tuple,
Union,
Iterator,
Hashable,
) # flake8: noqa
def get_chunks(iterable_obj, chunk_size=1):
# type: (List[Loader], int) -> Iterator
chunk_size = max(1, chunk_size)
return (
iterable_obj[i : i + chunk_size]
for i in range(0, len(iterable_obj), chunk_size)
)
Loader = namedtuple("Loader", "key,resolve,reject")
class DataLoader(local):
batch = True
max_batch_size = None # type: int
cache = True
def __init__(
self,
batch_load_fn=None, # type: Callable
batch=None, # type: Optional[Any]
max_batch_size=None, # type: Optional[int]
cache=None, # type: Optional[Any]
get_cache_key=None, # type: Optional[Any]
cache_map=None, # type: Optional[Any]
scheduler=None, # type: Optional[Any]
):
# type: (...) -> None
if batch_load_fn is not None:
self.batch_load_fn = batch_load_fn
if not callable(self.batch_load_fn):
raise TypeError(
(
"DataLoader must be have a batch_load_fn which accepts "
"List and returns Promise>, but got: {}."
).format(batch_load_fn)
)
if batch is not None:
self.batch = batch
if max_batch_size is not None:
self.max_batch_size = max_batch_size
if cache is not None:
self.cache = cache
self.get_cache_key = get_cache_key or (lambda x: x)
self._promise_cache = cache_map or {}
self._queue = [] # type: List[Loader]
self._scheduler = scheduler
def load(self, key=None):
# type: (Hashable) -> Promise
"""
Loads a key, returning a `Promise` for the value represented by that key.
"""
if key is None:
raise TypeError(
(
"The loader.load() function must be called with a value,"
+ "but got: {}."
).format(key)
)
cache_key = self.get_cache_key(key)
# If caching and there is a cache-hit, return cached Promise.
if self.cache:
cached_promise = self._promise_cache.get(cache_key)
if cached_promise:
return cached_promise
# Otherwise, produce a new Promise for this value.
promise = Promise(partial(self.do_resolve_reject, key)) # type: ignore
# If caching, cache this promise.
if self.cache:
self._promise_cache[cache_key] = promise
return promise
def do_resolve_reject(self, key, resolve, reject):
# type: (Hashable, Callable, Callable) -> None
# Enqueue this Promise to be dispatched.
self._queue.append(Loader(key=key, resolve=resolve, reject=reject))
# Determine if a dispatch of this queue should be scheduled.
# A single dispatch should be scheduled per queue at the time when the
# queue changes from "empty" to "full".
if len(self._queue) == 1:
if self.batch:
# If batching, schedule a task to dispatch the queue.
enqueue_post_promise_job(partial(dispatch_queue, self), self._scheduler)
else:
# Otherwise dispatch the (queue of one) immediately.
dispatch_queue(self)
def load_many(self, keys):
# type: (Iterable[Hashable]) -> Promise
"""
Loads multiple keys, promising an array of values
>>> a, b = await my_loader.load_many([ 'a', 'b' ])
This is equivalent to the more verbose:
>>> a, b = await Promise.all([
>>> my_loader.load('a'),
>>> my_loader.load('b')
>>> ])
"""
if not isinstance(keys, Iterable):
raise TypeError(
(
"The loader.loadMany() function must be called with Array "
+ "but got: {}."
).format(keys)
)
return Promise.all([self.load(key) for key in keys])
def clear(self, key):
# type: (Hashable) -> DataLoader
"""
Clears the value at `key` from the cache, if it exists. Returns itself for
method chaining.
"""
cache_key = self.get_cache_key(key)
self._promise_cache.pop(cache_key, None)
return self
def clear_all(self):
# type: () -> DataLoader
"""
Clears the entire cache. To be used when some event results in unknown
invalidations across this particular `DataLoader`. Returns itself for
method chaining.
"""
self._promise_cache.clear()
return self
def prime(self, key, value):
# type: (Hashable, Any) -> DataLoader
"""
Adds the provied key and value to the cache. If the key already exists, no
change is made. Returns itself for method chaining.
"""
cache_key = self.get_cache_key(key)
# Only add the key if it does not already exist.
if cache_key not in self._promise_cache:
# Cache a rejected promise if the value is an Error, in order to match
# the behavior of load(key).
if isinstance(value, Exception):
promise = Promise.reject(value)
else:
promise = Promise.resolve(value)
self._promise_cache[cache_key] = promise
return self
# Private: Enqueue a Job to be executed after all "PromiseJobs" Jobs.
#
# ES6 JavaScript uses the concepts Job and JobQueue to schedule work to occur
# after the current execution context has completed:
# http://www.ecma-international.org/ecma-262/6.0/#sec-jobs-and-job-queues
#
# Node.js uses the `process.nextTick` mechanism to implement the concept of a
# Job, maintaining a global FIFO JobQueue for all Jobs, which is flushed after
# the current call stack ends.
#
# When calling `then` on a Promise, it enqueues a Job on a specific
# "PromiseJobs" JobQueue which is flushed in Node as a single Job on the
# global JobQueue.
#
# DataLoader batches all loads which occur in a single frame of execution, but
# should include in the batch all loads which occur during the flushing of the
# "PromiseJobs" JobQueue after that same execution frame.
#
# In order to avoid the DataLoader dispatch Job occuring before "PromiseJobs",
# A Promise Job is created with the sole purpose of enqueuing a global Job,
# ensuring that it always occurs after "PromiseJobs" ends.
# Private: cached resolved Promise instance
cache = local()
def enqueue_post_promise_job(fn, scheduler):
# type: (Callable, Any) -> None
global cache
if not hasattr(cache, 'resolved_promise'):
cache.resolved_promise = Promise.resolve(None)
if not scheduler:
scheduler = get_default_scheduler()
def on_promise_resolve(v):
# type: (Any) -> None
async_instance.invoke(fn, scheduler)
cache.resolved_promise.then(on_promise_resolve)
def dispatch_queue(loader):
# type: (DataLoader) -> None
"""
Given the current state of a Loader instance, perform a batch load
from its current queue.
"""
# Take the current loader queue, replacing it with an empty queue.
queue = loader._queue
loader._queue = []
# If a maxBatchSize was provided and the queue is longer, then segment the
# queue into multiple batches, otherwise treat the queue as a single batch.
max_batch_size = loader.max_batch_size
if max_batch_size and max_batch_size < len(queue):
chunks = get_chunks(queue, max_batch_size)
for chunk in chunks:
dispatch_queue_batch(loader, chunk)
else:
dispatch_queue_batch(loader, queue)
def dispatch_queue_batch(loader, queue):
# type: (DataLoader, List[Loader]) -> None
# Collect all keys to be loaded in this dispatch
keys = [l.key for l in queue]
# Call the provided batch_load_fn for this loader with the loader queue's keys.
try:
batch_promise = loader.batch_load_fn(keys)
except Exception as e:
failed_dispatch(loader, queue, e)
return None
# Assert the expected response from batch_load_fn
if not batch_promise or not isinstance(batch_promise, Promise):
failed_dispatch(
loader,
queue,
TypeError(
(
"DataLoader must be constructed with a function which accepts "
"Array and returns Promise>, but the function did "
"not return a Promise: {}."
).format(batch_promise)
),
)
return None
def batch_promise_resolved(values):
# type: (Sized) -> None
# Assert the expected resolution from batchLoadFn.
if not isinstance(values, Iterable):
raise TypeError(
(
"DataLoader must be constructed with a function which accepts "
"Array and returns Promise>, but the function did "
"not return a Promise of an Array: {}."
).format(values)
)
if len(values) != len(keys):
raise TypeError(
(
"DataLoader must be constructed with a function which accepts "
"Array and returns Promise>, but the function did "
"not return a Promise of an Array of the same length as the Array "
"of keys."
"\n\nKeys:\n{}"
"\n\nValues:\n{}"
).format(keys, values)
)
# Step through the values, resolving or rejecting each Promise in the
# loaded queue.
for l, value in zip(queue, values):
if isinstance(value, Exception):
l.reject(value)
else:
l.resolve(value)
batch_promise.then(batch_promise_resolved).catch(
partial(failed_dispatch, loader, queue)
)
def failed_dispatch(loader, queue, error):
# type: (DataLoader, Iterable[Loader], Exception) -> None
"""
Do not cache individual loads if the entire batch dispatch fails,
but still reject each request so they do not hang.
"""
for l in queue:
loader.clear(l.key)
l.reject(error)
promise-2.3.0/promise/iterate_promise.py 0000664 0000000 0000000 00000000450 13576352764 0020436 0 ustar 00root root 0000000 0000000 # flake8: noqa
if False:
from .promise import Promise
from typing import Iterator
def iterate_promise(promise):
# type: (Promise) -> Iterator
if not promise.is_fulfilled:
yield from promise.future # type: ignore
assert promise.is_fulfilled
return promise.get()
promise-2.3.0/promise/promise.py 0000664 0000000 0000000 00000067147 13576352764 0016741 0 ustar 00root root 0000000 0000000 from collections import namedtuple
from functools import partial, wraps
from sys import version_info, exc_info
from threading import RLock
from types import TracebackType
from weakref import WeakKeyDictionary
from six import reraise # type: ignore
from .async_ import Async
from .compat import (
Future,
ensure_future,
iscoroutine, # type: ignore
iterate_promise,
) # type: ignore
from .utils import deprecated, integer_types, string_types, text_type, binary_type, warn
from .promise_list import PromiseList
from .schedulers.immediate import ImmediateScheduler
from typing import TypeVar, Generic
# from .schedulers.gevent import GeventScheduler
# from .schedulers.asyncio import AsyncioScheduler
# from .schedulers.thread import ThreadScheduler
if False:
from typing import (
Type,
List,
Any,
Callable,
Dict,
Iterator,
Optional, # flake8: noqa
Tuple,
Union,
Generic,
Hashable,
MutableMapping,
)
default_scheduler = ImmediateScheduler()
async_instance = Async()
def get_default_scheduler():
# type: () -> ImmediateScheduler
return default_scheduler
def set_default_scheduler(scheduler):
global default_scheduler
default_scheduler = scheduler
IS_PYTHON2 = version_info[0] == 2
DEFAULT_TIMEOUT = None # type: Optional[float]
MAX_LENGTH = 0xFFFF | 0
CALLBACK_SIZE = 3
CALLBACK_FULFILL_OFFSET = 0
CALLBACK_REJECT_OFFSET = 1
CALLBACK_PROMISE_OFFSET = 2
BASE_TYPES = set(
integer_types
+ string_types
+ (bool, float, complex, tuple, list, dict, text_type, binary_type)
)
# These are the potential states of a promise
STATE_PENDING = -1
STATE_REJECTED = 0
STATE_FULFILLED = 1
def make_self_resolution_error():
# type: () -> TypeError
return TypeError("Promise is self")
def try_catch(handler, *args, **kwargs):
# type: (Callable, Any, Any) -> Union[Tuple[Any, None], Tuple[None, Tuple[Exception, Optional[TracebackType]]]]
try:
return (handler(*args, **kwargs), None)
except Exception as e:
tb = exc_info()[2]
return (None, (e, tb))
T = TypeVar("T")
S = TypeVar("S", contravariant=True)
class Promise(Generic[T]):
"""
This is the Promise class that complies
Promises/A+ specification.
"""
# __slots__ = ('_state', '_is_final', '_is_bound', '_is_following', '_is_async_guaranteed',
# '_length', '_handlers', '_fulfillment_handler0', '_rejection_handler0', '_promise0',
# '_is_waiting', '_future', '_trace', '_event_instance'
# )
_state = STATE_PENDING # type: int
_is_final = False
_is_bound = False
_is_following = False
_is_async_guaranteed = False
_length = 0
_handlers = None # type: Dict[int, Union[Callable, Promise, None]]
_fulfillment_handler0 = None # type: Any
_rejection_handler0 = None # type: Any
_promise0 = None # type: Optional[Promise]
_future = None # type: Future
_traceback = None # type: Optional[TracebackType]
# _trace = None
_is_waiting = False
_scheduler = None
def __init__(self, executor=None, scheduler=None):
# type: (Optional[Callable[[Callable[[T], None], Callable[[Exception], None]], None]], Any) -> None
"""
Initialize the Promise into a pending state.
"""
# self._state = STATE_PENDING # type: int
# self._is_final = False
# self._is_bound = False
# self._is_following = False
# self._is_async_guaranteed = False
# self._length = 0
# self._handlers = None # type: Dict[int, Union[Callable, None]]
# self._fulfillment_handler0 = None # type: Union[Callable, partial]
# self._rejection_handler0 = None # type: Union[Callable, partial]
# self._promise0 = None # type: Promise
# self._future = None # type: Future
# self._event_instance = None # type: Event
# self._is_waiting = False
self._scheduler = scheduler
if executor is not None:
self._resolve_from_executor(executor)
# For compatibility reasons
# self.reject = self._deprecated_reject
# self.resolve = self._deprecated_resolve
@property
def scheduler(self):
# type: () -> ImmediateScheduler
return self._scheduler or default_scheduler
@property
def future(self):
# type: (Promise) -> Future
if not self._future:
self._future = Future() # type: ignore
self._then( # type: ignore
self._future.set_result, self._future.set_exception
)
return self._future
def __iter__(self):
# type: () -> Iterator
return iterate_promise(self._target()) # type: ignore
__await__ = __iter__
@deprecated(
"Rejecting directly in a Promise instance is deprecated, as Promise.reject() is now a class method. "
"Please use promise.do_reject() instead.",
name="reject",
)
def _deprecated_reject(self, e):
self.do_reject(e)
@deprecated(
"Resolving directly in a Promise instance is deprecated, as Promise.resolve() is now a class method. "
"Please use promise.do_resolve() instead.",
name="resolve",
)
def _deprecated_resolve(self, value):
self.do_resolve(value)
def _resolve_callback(self, value):
# type: (T) -> None
if value is self:
return self._reject_callback(make_self_resolution_error(), False)
if not self.is_thenable(value):
return self._fulfill(value)
promise = self._try_convert_to_promise(value)._target()
if promise == self:
self._reject(make_self_resolution_error())
return
if promise._state == STATE_PENDING:
len = self._length
if len > 0:
promise._migrate_callback0(self)
for i in range(1, len):
promise._migrate_callback_at(self, i)
self._is_following = True
self._length = 0
self._set_followee(promise)
elif promise._state == STATE_FULFILLED:
self._fulfill(promise._value())
elif promise._state == STATE_REJECTED:
self._reject(promise._reason(), promise._target()._traceback)
def _settled_value(self, _raise=False):
# type: (bool) -> Any
assert not self._is_following
if self._state == STATE_FULFILLED:
return self._rejection_handler0
elif self._state == STATE_REJECTED:
if _raise:
raise_val = self._fulfillment_handler0
reraise(type(raise_val), raise_val, self._traceback)
return self._fulfillment_handler0
def _fulfill(self, value):
# type: (T) -> None
if value is self:
err = make_self_resolution_error()
# self._attach_extratrace(err)
return self._reject(err)
self._state = STATE_FULFILLED
self._rejection_handler0 = value
if self._length > 0:
if self._is_async_guaranteed:
self._settle_promises()
else:
async_instance.settle_promises(self)
def _reject(self, reason, traceback=None):
# type: (Exception, Optional[TracebackType]) -> None
self._state = STATE_REJECTED
self._fulfillment_handler0 = reason
self._traceback = traceback
if self._is_final:
assert self._length == 0
async_instance.fatal_error(reason, self.scheduler)
return
if self._length > 0:
async_instance.settle_promises(self)
else:
self._ensure_possible_rejection_handled()
if self._is_async_guaranteed:
self._settle_promises()
else:
async_instance.settle_promises(self)
def _ensure_possible_rejection_handled(self):
# type: () -> None
# self._rejection_is_unhandled = True
# async_instance.invoke_later(self._notify_unhandled_rejection, self)
pass
def _reject_callback(self, reason, synchronous=False, traceback=None):
# type: (Exception, bool, Optional[TracebackType]) -> None
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)
self._reject(reason, traceback)
def _clear_callback_data_index_at(self, index):
# type: (int) -> None
assert not self._is_following
assert index > 0
base = index * CALLBACK_SIZE - CALLBACK_SIZE
self._handlers[base + CALLBACK_PROMISE_OFFSET] = None
self._handlers[base + CALLBACK_FULFILL_OFFSET] = None
self._handlers[base + CALLBACK_REJECT_OFFSET] = None
def _fulfill_promises(self, length, value):
# type: (int, T) -> None
for i in range(1, length):
handler = self._fulfillment_handler_at(i)
promise = self._promise_at(i)
self._clear_callback_data_index_at(i)
self._settle_promise(promise, handler, value, None)
def _reject_promises(self, length, reason):
# type: (int, Exception) -> None
for i in range(1, length):
handler = self._rejection_handler_at(i)
promise = self._promise_at(i)
self._clear_callback_data_index_at(i)
self._settle_promise(promise, handler, reason, None)
def _settle_promise(
self,
promise, # type: Optional[Promise]
handler, # type: Optional[Callable]
value, # type: Union[T, Exception]
traceback, # type: Optional[TracebackType]
):
# type: (...) -> None
assert not self._is_following
is_promise = isinstance(promise, self.__class__)
async_guaranteed = self._is_async_guaranteed
if callable(handler):
if not is_promise:
handler(value) # , promise
else:
if async_guaranteed:
promise._is_async_guaranteed = True # type: ignore
self._settle_promise_from_handler( # type: ignore
handler, value, promise # type: ignore
) # type: ignore
elif is_promise:
if async_guaranteed:
promise._is_async_guaranteed = True # type: ignore
if self._state == STATE_FULFILLED:
promise._fulfill(value) # type: ignore
else:
promise._reject(value, self._traceback) # type: ignore
def _settle_promise0(
self,
handler, # type: Optional[Callable]
value, # type: Any
traceback, # type: Optional[TracebackType]
):
# type: (...) -> None
promise = self._promise0
self._promise0 = None
self._settle_promise(promise, handler, value, traceback) # type: ignore
def _settle_promise_from_handler(self, handler, value, promise):
# type: (Callable, Any, Promise) -> None
value, error_with_tb = try_catch(handler, value) # , promise
if error_with_tb:
error, tb = error_with_tb
promise._reject_callback(error, False, tb)
else:
promise._resolve_callback(value)
def _promise_at(self, index):
# type: (int) -> Optional[Promise]
assert index > 0
assert not self._is_following
return self._handlers.get( # type: ignore
index * CALLBACK_SIZE - CALLBACK_SIZE + CALLBACK_PROMISE_OFFSET
)
def _fulfillment_handler_at(self, index):
# type: (int) -> Optional[Callable]
assert not self._is_following
assert index > 0
return self._handlers.get( # type: ignore
index * CALLBACK_SIZE - CALLBACK_SIZE + CALLBACK_FULFILL_OFFSET
)
def _rejection_handler_at(self, index):
# type: (int) -> Optional[Callable]
assert not self._is_following
assert index > 0
return self._handlers.get( # type: ignore
index * CALLBACK_SIZE - CALLBACK_SIZE + CALLBACK_REJECT_OFFSET
)
def _migrate_callback0(self, follower):
# type: (Promise) -> None
self._add_callbacks(
follower._fulfillment_handler0,
follower._rejection_handler0,
follower._promise0,
)
def _migrate_callback_at(self, follower, index):
self._add_callbacks(
follower._fulfillment_handler_at(index),
follower._rejection_handler_at(index),
follower._promise_at(index),
)
def _add_callbacks(
self,
fulfill, # type: Optional[Callable]
reject, # type: Optional[Callable]
promise, # type: Optional[Promise]
):
# type: (...) -> int
assert not self._is_following
if self._handlers is None:
self._handlers = {}
index = self._length
if index > MAX_LENGTH - CALLBACK_SIZE:
index = 0
self._length = 0
if index == 0:
assert not self._promise0
assert not self._fulfillment_handler0
assert not self._rejection_handler0
self._promise0 = promise
if callable(fulfill):
self._fulfillment_handler0 = fulfill
if callable(reject):
self._rejection_handler0 = reject
else:
base = index * CALLBACK_SIZE - CALLBACK_SIZE
assert (base + CALLBACK_PROMISE_OFFSET) not in self._handlers
assert (base + CALLBACK_FULFILL_OFFSET) not in self._handlers
assert (base + CALLBACK_REJECT_OFFSET) not in self._handlers
self._handlers[base + CALLBACK_PROMISE_OFFSET] = promise
if callable(fulfill):
self._handlers[base + CALLBACK_FULFILL_OFFSET] = fulfill
if callable(reject):
self._handlers[base + CALLBACK_REJECT_OFFSET] = reject
self._length = index + 1
return index
def _target(self):
# type: () -> Promise
ret = self
while ret._is_following:
ret = ret._followee()
return ret
def _followee(self):
# type: () -> Promise
assert self._is_following
assert isinstance(self._rejection_handler0, Promise)
return self._rejection_handler0
def _set_followee(self, promise):
# type: (Promise) -> None
assert self._is_following
assert not isinstance(self._rejection_handler0, Promise)
self._rejection_handler0 = promise
def _settle_promises(self):
# type: () -> None
length = self._length
if length > 0:
if self._state == STATE_REJECTED:
reason = self._fulfillment_handler0
traceback = self._traceback
self._settle_promise0(self._rejection_handler0, reason, traceback)
self._reject_promises(length, reason)
else:
value = self._rejection_handler0
self._settle_promise0(self._fulfillment_handler0, value, None)
self._fulfill_promises(length, value)
self._length = 0
def _resolve_from_executor(self, executor):
# type: (Callable[[Callable[[T], None], Callable[[Exception], None]], None]) -> None
# self._capture_stacktrace()
synchronous = True
def resolve(value):
# type: (T) -> None
self._resolve_callback(value)
def reject(reason, traceback=None):
# type: (Exception, TracebackType) -> None
self._reject_callback(reason, synchronous, traceback)
error = None
traceback = None
try:
executor(resolve, reject)
except Exception as e:
traceback = exc_info()[2]
error = e
synchronous = False
if error is not None:
self._reject_callback(error, True, traceback)
@classmethod
def wait(cls, promise, timeout=None):
# type: (Promise, Optional[float]) -> None
async_instance.wait(promise, timeout)
def _wait(self, timeout=None):
# type: (Optional[float]) -> None
self.wait(self, timeout)
def get(self, timeout=None):
# type: (Optional[float]) -> T
target = self._target()
self._wait(timeout or DEFAULT_TIMEOUT)
return self._target_settled_value(_raise=True)
def _target_settled_value(self, _raise=False):
# type: (bool) -> Any
return self._target()._settled_value(_raise)
_value = _reason = _target_settled_value
value = reason = property(_target_settled_value)
def __repr__(self):
# type: () -> str
hex_id = hex(id(self))
if self._is_following:
return "".format(hex_id, self._target())
state = self._state
if state == STATE_PENDING:
return "".format(hex_id)
elif state == STATE_FULFILLED:
return "".format(
hex_id, repr(self._rejection_handler0)
)
elif state == STATE_REJECTED:
return "".format(
hex_id, repr(self._fulfillment_handler0)
)
return ""
@property
def is_pending(self):
# type: (Promise) -> bool
"""Indicate whether the Promise is still pending. Could be wrong the moment the function returns."""
return self._target()._state == STATE_PENDING
@property
def is_fulfilled(self):
# type: (Promise) -> bool
"""Indicate whether the Promise has been fulfilled. Could be wrong the moment the function returns."""
return self._target()._state == STATE_FULFILLED
@property
def is_rejected(self):
# type: (Promise) -> bool
"""Indicate whether the Promise has been rejected. Could be wrong the moment the function returns."""
return self._target()._state == STATE_REJECTED
def catch(self, on_rejection):
# type: (Promise, Callable[[Exception], Any]) -> Promise
"""
This method returns a Promise and deals with rejected cases only.
It behaves the same as calling Promise.then(None, on_rejection).
"""
return self.then(None, on_rejection)
def _then(
self,
did_fulfill=None, # type: Optional[Callable[[T], S]]
did_reject=None, # type: Optional[Callable[[Exception], S]]
):
# type: (...) -> Promise[S]
promise = self.__class__() # type: Promise
target = self._target()
state = target._state
if state == STATE_PENDING:
target._add_callbacks(did_fulfill, did_reject, promise)
else:
traceback = None
if state == STATE_FULFILLED:
value = target._rejection_handler0
handler = did_fulfill
elif state == STATE_REJECTED:
value = target._fulfillment_handler0
traceback = target._traceback
handler = did_reject # type: ignore
# target._rejection_is_unhandled = False
async_instance.invoke(
partial(target._settle_promise, promise, handler, value, traceback),
promise.scheduler
# target._settle_promise instead?
# settler,
# target,
)
return promise
fulfill = _resolve_callback
do_resolve = _resolve_callback
do_reject = _reject_callback
def then(self, did_fulfill=None, did_reject=None):
# type: (Promise, Callable[[T], S], Optional[Callable[[Exception], S]]) -> Promise[S]
"""
This method takes two optional arguments. The first argument
is used if the "self promise" is fulfilled and the other is
used if the "self promise" is rejected. In either case, this
method returns another promise that effectively represents
the result of either the first of the second argument (in the
case that the "self promise" is fulfilled or rejected,
respectively).
Each argument can be either:
* None - Meaning no action is taken
* A function - which will be called with either the value
of the "self promise" or the reason for rejection of
the "self promise". The function may return:
* A value - which will be used to fulfill the promise
returned by this method.
* A promise - which, when fulfilled or rejected, will
cascade its value or reason to the promise returned
by this method.
* A value - which will be assigned as either the value
or the reason for the promise returned by this method
when the "self promise" is either fulfilled or rejected,
respectively.
:type success: (Any) -> object
:type failure: (Any) -> object
:rtype : Promise
"""
return self._then(did_fulfill, did_reject)
def done(self, did_fulfill=None, did_reject=None):
# type: (Optional[Callable], Optional[Callable]) -> None
promise = self._then(did_fulfill, did_reject)
promise._is_final = True
def done_all(self, handlers=None):
# type: (Promise, Optional[List[Union[Dict[str, Optional[Callable]], Tuple[Callable, Callable], Callable]]]) -> None
"""
:type handlers: list[(Any) -> object] | list[((Any) -> object, (Any) -> object)]
"""
if not handlers:
return
for handler in handlers:
if isinstance(handler, tuple):
s, f = handler
self.done(s, f)
elif isinstance(handler, dict):
s = handler.get("success") # type: ignore
f = handler.get("failure") # type: ignore
self.done(s, f)
else:
self.done(handler)
def then_all(self, handlers=None):
# type: (Promise, List[Callable]) -> List[Promise]
"""
Utility function which calls 'then' for each handler provided. Handler can either
be a function in which case it is used as success handler, or a tuple containing
the success and the failure handler, where each of them could be None.
:type handlers: list[(Any) -> object] | list[((Any) -> object, (Any) -> object)]
:param handlers
:rtype : list[Promise]
"""
if not handlers:
return []
promises = [] # type: List[Promise]
for handler in handlers:
if isinstance(handler, tuple):
s, f = handler
promises.append(self.then(s, f))
elif isinstance(handler, dict):
s = handler.get("success")
f = handler.get("failure")
promises.append(self.then(s, f))
else:
promises.append(self.then(handler))
return promises
@classmethod
def _try_convert_to_promise(cls, obj):
# type: (Any) -> Promise
_type = obj.__class__
if issubclass(_type, Promise):
if cls is not Promise:
return cls(obj.then, obj._scheduler)
return obj
if iscoroutine(obj): # type: ignore
obj = ensure_future(obj) # type: ignore
_type = obj.__class__
if is_future_like(_type):
def executor(resolve, reject):
# type: (Callable, Callable) -> None
if obj.done():
_process_future_result(resolve, reject)(obj)
else:
obj.add_done_callback(_process_future_result(resolve, reject))
# _process_future_result(resolve, reject)(obj)
promise = cls(executor) # type: Promise
promise._future = obj
return promise
return obj
@classmethod
def reject(cls, reason):
# type: (Exception) -> Promise
ret = cls() # type: Promise
# ret._capture_stacktrace();
# ret._rejectCallback(reason, true);
ret._reject_callback(reason, True)
return ret
rejected = reject
@classmethod
def resolve(cls, obj):
# type: (T) -> Promise[T]
if not cls.is_thenable(obj):
ret = cls() # type: Promise
# ret._capture_stacktrace()
ret._state = STATE_FULFILLED
ret._rejection_handler0 = obj
return ret
return cls._try_convert_to_promise(obj)
cast = resolve
fulfilled = cast
@classmethod
def promisify(cls, f):
# type: (Callable) -> Callable[..., Promise]
if not callable(f):
warn(
"Promise.promisify is now a function decorator, please use Promise.resolve instead."
)
return cls.resolve(f)
@wraps(f)
def wrapper(*args, **kwargs):
# type: (*Any, **Any) -> Promise
def executor(resolve, reject):
# type: (Callable, Callable) -> Optional[Any]
return resolve(f(*args, **kwargs))
return cls(executor)
return wrapper
_safe_resolved_promise = None # type: Promise
@classmethod
def safe(cls, fn):
# type: (Callable) -> Callable
from functools import wraps
if not cls._safe_resolved_promise:
cls._safe_resolved_promise = Promise.resolve(None)
@wraps(fn)
def wrapper(*args, **kwargs):
# type: (*Any, **Any) -> Promise
return cls._safe_resolved_promise.then(lambda v: fn(*args, **kwargs))
return wrapper
@classmethod
def all(cls, promises):
# type: (Any) -> Promise
return PromiseList(promises, promise_class=cls).promise
@classmethod
def for_dict(cls, m):
# type: (Dict[Hashable, Promise[S]]) -> Promise[Dict[Hashable, S]]
"""
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.
"""
dict_type = type(m) # type: Type[Dict]
if not m:
return cls.resolve(dict_type()) # type: ignore
def handle_success(resolved_values):
# type: (List[S]) -> Dict[Hashable, S]
return dict_type(zip(m.keys(), resolved_values))
return cls.all(m.values()).then(handle_success)
@classmethod
def is_thenable(cls, obj):
# type: (Any) -> bool
"""
A utility function to determine if the specified
object is a promise using "duck typing".
"""
_type = obj.__class__
if obj is None or _type in BASE_TYPES:
return False
return (
issubclass(_type, Promise)
or iscoroutine(obj) # type: ignore
or is_future_like(_type)
)
_type_done_callbacks = WeakKeyDictionary() # type: MutableMapping[type, bool]
def is_future_like(_type):
# type: (type) -> bool
if _type not in _type_done_callbacks:
_type_done_callbacks[_type] = callable(
getattr(_type, "add_done_callback", None)
)
return _type_done_callbacks[_type]
promisify = Promise.promisify
promise_for_dict = Promise.for_dict
is_thenable = Promise.is_thenable
def _process_future_result(resolve, reject):
# type: (Callable, Callable) -> Callable
def handle_future_result(future):
# type: (Any) -> None
try:
resolve(future.result())
except Exception as e:
tb = exc_info()[2]
reject(e, tb)
return handle_future_result
promise-2.3.0/promise/promise_list.py 0000664 0000000 0000000 00000011046 13576352764 0017757 0 ustar 00root root 0000000 0000000 from functools import partial
try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable
if False:
from .promise import Promise
from typing import (
Any,
Optional,
Tuple,
Union,
List,
Type,
Collection,
) # flake8: noqa
class PromiseList(object):
__slots__ = ("_values", "_length", "_total_resolved", "promise", "_promise_class")
def __init__(self, values, promise_class):
# type: (Union[Collection, Promise[Collection]], Type[Promise]) -> None
self._promise_class = promise_class
self.promise = self._promise_class()
self._length = 0
self._total_resolved = 0
self._values = None # type: Optional[Collection]
Promise = self._promise_class
if Promise.is_thenable(values):
values_as_promise = Promise._try_convert_to_promise(
values
)._target() # type: ignore
self._init_promise(values_as_promise)
else:
self._init(values) # type: ignore
def __len__(self):
# type: () -> int
return self._length
def _init_promise(self, values):
# type: (Promise[Collection]) -> None
if values.is_fulfilled:
values = values._value()
elif values.is_rejected:
self._reject(values._reason())
return
self.promise._is_async_guaranteed = True
values._then(self._init, self._reject)
return
def _init(self, values):
# type: (Collection) -> None
self._values = values
if not isinstance(values, Iterable):
err = Exception(
"PromiseList requires an iterable. Received {}.".format(repr(values))
)
self.promise._reject_callback(err, False)
return
if not values:
self._resolve([])
return
self._iterate(values)
return
def _iterate(self, values):
# type: (Collection[Any]) -> None
Promise = self._promise_class
is_resolved = False
self._length = len(values)
self._values = [None] * self._length
result = self.promise
for i, val in enumerate(values):
if Promise.is_thenable(val):
maybe_promise = Promise._try_convert_to_promise(val)._target()
# if is_resolved:
# # maybe_promise.suppressUnhandledRejections
# pass
if maybe_promise.is_pending:
maybe_promise._add_callbacks(
partial(self._promise_fulfilled, i=i),
self._promise_rejected,
None,
)
self._values[i] = maybe_promise
elif maybe_promise.is_fulfilled:
is_resolved = self._promise_fulfilled(maybe_promise._value(), i)
elif maybe_promise.is_rejected:
is_resolved = self._promise_rejected(maybe_promise._reason())
else:
is_resolved = self._promise_fulfilled(val, i)
if is_resolved:
break
if not is_resolved:
result._is_async_guaranteed = True
def _promise_fulfilled(self, value, i):
# type: (Any, int) -> bool
if self.is_resolved:
return False
# assert not self.is_resolved
# assert isinstance(self._values, Iterable)
# assert isinstance(i, int)
self._values[i] = value # type: ignore
self._total_resolved += 1
if self._total_resolved >= self._length:
self._resolve(self._values) # type: ignore
return True
return False
def _promise_rejected(self, reason):
# type: (Exception) -> bool
if self.is_resolved:
return False
# assert not self.is_resolved
# assert isinstance(self._values, Iterable)
self._total_resolved += 1
self._reject(reason)
return True
@property
def is_resolved(self):
# type: () -> bool
return self._values is None
def _resolve(self, value):
# type: (Collection[Any]) -> None
assert not self.is_resolved
assert not isinstance(value, self._promise_class)
self._values = None
self.promise._fulfill(value)
def _reject(self, reason):
# type: (Exception) -> None
assert not self.is_resolved
self._values = None
self.promise._reject_callback(reason, False)
promise-2.3.0/promise/py.typed 0000664 0000000 0000000 00000000000 13576352764 0016357 0 ustar 00root root 0000000 0000000 promise-2.3.0/promise/pyutils/ 0000775 0000000 0000000 00000000000 13576352764 0016403 5 ustar 00root root 0000000 0000000 promise-2.3.0/promise/pyutils/__init__.py 0000664 0000000 0000000 00000000000 13576352764 0020502 0 ustar 00root root 0000000 0000000 promise-2.3.0/promise/pyutils/version.py 0000664 0000000 0000000 00000004713 13576352764 0020447 0 ustar 00root root 0000000 0000000 from __future__ import unicode_literals
import datetime
import os
import subprocess
def get_version(version=None):
"Returns a PEP 440-compliant version number from VERSION."
version = get_complete_version(version)
# Now build the two parts of the version number:
# main = X.Y[.Z]
# sub = .devN - for pre-alpha releases
# | {a|b|rc}N - for alpha, beta, and rc releases
main = get_main_version(version)
sub = ""
if version[3] == "alpha" and version[4] == 0:
git_changeset = get_git_changeset()
if git_changeset:
sub = ".dev%s" % git_changeset
else:
sub = ".dev"
elif version[3] != "final":
mapping = {"alpha": "a", "beta": "b", "rc": "rc"}
sub = mapping[version[3]] + str(version[4])
return str(main + sub)
def get_main_version(version=None):
"Returns main version (X.Y[.Z]) from VERSION."
version = get_complete_version(version)
parts = 2 if version[2] == 0 else 3
return ".".join(str(x) for x in version[:parts])
def get_complete_version(version=None):
"""Returns a tuple of the promise version. If version argument is non-empty,
then checks for correctness of the tuple provided.
"""
if version is None:
from promise import VERSION
return VERSION
else:
assert len(version) == 5
assert version[3] in ("alpha", "beta", "rc", "final")
return version
def get_docs_version(version=None):
version = get_complete_version(version)
if version[3] != "final":
return "dev"
else:
return "%d.%d" % version[:2]
def get_git_changeset():
"""Returns a numeric identifier of the latest git changeset.
The result is the UTC timestamp of the changeset in YYYYMMDDHHMMSS format.
This value isn't guaranteed to be unique, but collisions are very unlikely,
so it's sufficient for generating the development version numbers.
"""
repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
try:
git_log = subprocess.Popen(
"git log --pretty=format:%ct --quiet -1 HEAD",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
cwd=repo_dir,
universal_newlines=True,
)
timestamp = git_log.communicate()[0]
timestamp = datetime.datetime.utcfromtimestamp(int(timestamp))
except Exception:
return None
return timestamp.strftime("%Y%m%d%H%M%S")
promise-2.3.0/promise/schedulers/ 0000775 0000000 0000000 00000000000 13576352764 0017033 5 ustar 00root root 0000000 0000000 promise-2.3.0/promise/schedulers/__init__.py 0000664 0000000 0000000 00000000000 13576352764 0021132 0 ustar 00root root 0000000 0000000 promise-2.3.0/promise/schedulers/asyncio.py 0000664 0000000 0000000 00000001000 13576352764 0021041 0 ustar 00root root 0000000 0000000 from __future__ import absolute_import
from asyncio import get_event_loop, Event
class AsyncioScheduler(object):
def __init__(self, loop=None):
self.loop = loop or get_event_loop()
def call(self, fn):
self.loop.call_soon(fn)
def wait(self, promise, timeout=None):
e = Event()
def on_resolve_or_reject(_):
e.set()
promise._then(on_resolve_or_reject, on_resolve_or_reject)
# We can't use the timeout in Asyncio event
e.wait()
promise-2.3.0/promise/schedulers/gevent.py 0000664 0000000 0000000 00000000766 13576352764 0020706 0 ustar 00root root 0000000 0000000 from __future__ import absolute_import
from gevent.event import Event # type: ignore
import gevent # type: ignore
class GeventScheduler(object):
def call(self, fn):
# print fn
gevent.spawn(fn)
def wait(self, promise, timeout=None):
e = Event()
def on_resolve_or_reject(_):
e.set()
promise._then(on_resolve_or_reject, on_resolve_or_reject)
waited = e.wait(timeout)
if not waited:
raise Exception("Timeout")
promise-2.3.0/promise/schedulers/immediate.py 0000664 0000000 0000000 00000001227 13576352764 0021345 0 ustar 00root root 0000000 0000000 from threading import Event
if False:
from ..promise import Promise
from typing import Callable, Any, Optional # flake8: noqa
class ImmediateScheduler(object):
def call(self, fn):
# type: (Callable) -> None
try:
fn()
except:
pass
def wait(self, promise, timeout=None):
# type: (Promise, Optional[float]) -> None
e = Event()
def on_resolve_or_reject(_):
# type: (Any) -> None
e.set()
promise._then(on_resolve_or_reject, on_resolve_or_reject)
waited = e.wait(timeout)
if not waited:
raise Exception("Timeout")
promise-2.3.0/promise/schedulers/thread.py 0000664 0000000 0000000 00000000663 13576352764 0020661 0 ustar 00root root 0000000 0000000 from threading import Thread, Event
class ThreadScheduler(object):
def call(self, fn):
thread = Thread(target=fn)
thread.start()
def wait(self, promise, timeout=None):
e = Event()
def on_resolve_or_reject(_):
e.set()
promise._then(on_resolve_or_reject, on_resolve_or_reject)
waited = e.wait(timeout)
if not waited:
raise Exception("Timeout")
promise-2.3.0/promise/utils.py 0000664 0000000 0000000 00000003130 13576352764 0016401 0 ustar 00root root 0000000 0000000 import functools
import inspect
import types
import warnings
import sys
def warn(msg):
# type: (str) -> None
warnings.simplefilter("always", DeprecationWarning) # turn off filter
warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
warnings.simplefilter("default", DeprecationWarning) # reset filter
class deprecated(object):
def __init__(self, reason, name=None):
if inspect.isclass(reason) or inspect.isfunction(reason):
raise TypeError("Reason for deprecation must be supplied")
self.reason = reason
self.name = name
def __call__(self, cls_or_func):
if inspect.isfunction(cls_or_func):
fmt = "Call to deprecated function or method {name} ({reason})."
elif inspect.isclass(cls_or_func):
fmt = "Call to deprecated class {name} ({reason})."
else:
raise TypeError(type(cls_or_func))
msg = fmt.format(name=self.name or cls_or_func.__name__, reason=self.reason)
@functools.wraps(cls_or_func)
def new_func(*args, **kwargs):
warn(msg)
return cls_or_func(*args, **kwargs)
return new_func
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY3:
string_types = (str,) # type: tuple
integer_types = (int,) # type: tuple
class_types = (type,) # type: tuple
text_type = str
binary_type = bytes
else:
string_types = (basestring,) # type: tuple
integer_types = (int, long) # type: tuple
class_types = (type, types.ClassType) # type: tuple
text_type = unicode
binary_type = str
promise-2.3.0/setup.cfg 0000664 0000000 0000000 00000000142 13576352764 0015032 0 ustar 00root root 0000000 0000000 [flake8]
exclude = tests,scripts,setup.py,docs,promise/utils.py,conftest.py
max-line-length = 120
promise-2.3.0/setup.py 0000664 0000000 0000000 00000003553 13576352764 0014734 0 ustar 00root root 0000000 0000000 import sys
from setuptools import setup, find_packages
if sys.version_info[0] < 3:
import __builtin__ as builtins
else:
import builtins
builtins.__SETUP__ = True
version = __import__("promise").get_version()
IS_PY3 = sys.hexversion >= 0x03000000
tests_require = [
"pytest>=2.7.3",
"pytest-cov",
"coveralls",
"futures",
"pytest-benchmark",
"mock",
]
if IS_PY3:
tests_require += ["pytest-asyncio"]
setup(
name="promise",
version=version,
description="Promises/A+ implementation for Python",
long_description=open("README.rst").read(),
url="https://github.com/syrusakbary/promise",
download_url="https://github.com/syrusakbary/promise/releases",
author="Syrus Akbary",
author_email="me@syrusakbary.com",
license="MIT",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Topic :: Software Development :: Libraries",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: Implementation :: PyPy",
"License :: OSI Approved :: MIT License",
],
keywords="concurrent future deferred promise",
packages=find_packages(exclude=["tests"]),
# PEP-561: https://www.python.org/dev/peps/pep-0561/
package_data={"promise": ["py.typed"]},
extras_require={"test": tests_require},
install_requires=[
"typing>=3.6.4; python_version < '3.5'",
"six"
],
tests_require=tests_require,
)
promise-2.3.0/tests/ 0000775 0000000 0000000 00000000000 13576352764 0014356 5 ustar 00root root 0000000 0000000 promise-2.3.0/tests/__init__.py 0000664 0000000 0000000 00000000000 13576352764 0016455 0 ustar 00root root 0000000 0000000 promise-2.3.0/tests/conftest.py 0000664 0000000 0000000 00000000414 13576352764 0016554 0 ustar 00root root 0000000 0000000 from sys import version_info
collect_ignore = []
if version_info[:2] < (3, 4):
collect_ignore.append("test_awaitable.py")
if version_info[:2] < (3, 5):
collect_ignore.append("test_awaitable_35.py")
collect_ignore.append("test_dataloader_awaitable_35.py")
promise-2.3.0/tests/test_awaitable.py 0000664 0000000 0000000 00000001126 13576352764 0017720 0 ustar 00root root 0000000 0000000 from asyncio import coroutine
from pytest import mark
from time import sleep
from promise import Promise
@mark.asyncio
@coroutine
def test_await():
yield from Promise.resolve(True)
@mark.asyncio
@coroutine
def test_await_time():
def resolve_or_reject(resolve, reject):
sleep(.1)
resolve(True)
p = Promise(resolve_or_reject)
assert p.get() is True
@mark.asyncio
@coroutine
def test_promise_coroutine():
@coroutine
def my_coro():
yield from Promise.resolve(True)
promise = Promise.resolve(my_coro())
assert isinstance(promise, Promise)
promise-2.3.0/tests/test_awaitable_35.py 0000664 0000000 0000000 00000001724 13576352764 0020233 0 ustar 00root root 0000000 0000000 from asyncio import sleep, Future, wait, FIRST_COMPLETED
from pytest import mark
from promise import Promise, is_thenable
@mark.asyncio
async def test_await():
assert await Promise.resolve(True)
@mark.asyncio
async def test_promisify_coroutine():
async def my_coroutine():
await sleep(.01)
return True
assert await Promise.resolve(my_coroutine())
@mark.asyncio
async def test_coroutine_is_thenable():
async def my_coroutine():
await sleep(.01)
return True
assert is_thenable(my_coroutine())
@mark.asyncio
async def test_promisify_future():
future = Future()
future.set_result(True)
assert await Promise.resolve(future)
@mark.asyncio
async def test_await_in_safe_promise():
async def inner():
@Promise.safe
def x():
promise = Promise.resolve(True).then(lambda x: x)
return promise
return await x()
result = await inner()
assert result == True
promise-2.3.0/tests/test_benchmark.py 0000664 0000000 0000000 00000005464 13576352764 0017732 0 ustar 00root root 0000000 0000000 from pytest import raises
import time
from promise import Promise, promisify, is_thenable
def test_benchmark_promise_creation(benchmark):
@benchmark
def create_promise(): # unnecessary function call
p = Promise()
def test_benchmark_promise_resolve(benchmark):
def create_promise():
return Promise.resolve(True)
result = benchmark(create_promise).get()
assert result == True
def test_benchmark_is_thenable_basic_type(benchmark):
def create_promise():
return is_thenable(True)
result = benchmark(create_promise)
assert result == False
def test_benchmark_is_thenable_custom_type(benchmark):
class MyType(object):
pass
my_type_instance = MyType()
def create_promise():
return is_thenable(my_type_instance)
result = benchmark(create_promise)
assert result == False
def test_benchmark_promise_creation_with_resolve(benchmark):
do_resolve = lambda resolve, reject: resolve(True)
def create_promise(): # unnecessary function call
p = Promise(do_resolve)
# p._wait()
return p
result = benchmark(create_promise).get()
assert result == True
def test_benchmark_promise_creation_with_reject(benchmark):
do_resolve = lambda resolve, reject: reject(Exception("Error"))
def create_promise(): # unnecessary function call
p = Promise(do_resolve)
# p._wait()
return p
with raises(Exception) as exc_info:
result = benchmark(create_promise).get()
assert str(exc_info.value) == "Error"
# def test_benchmark_promisify_promise(benchmark):
# instance = Promise()
# def create_promise(): # unnecessary function call
# return promisify(instance)
# result = benchmark(create_promise)
# assert isinstance(result, Promise)
def test_benchmark_promisify_custom_type(benchmark):
class CustomThenable(object):
pass
# def then(self, resolve, reject):
# return resolve(True)
instance = CustomThenable()
def create_promise(): # unnecessary function call
return Promise.resolve(instance)
result = benchmark(create_promise)
assert isinstance(result, Promise)
assert result.get() == instance
def test_benchmark_promise_all(benchmark):
values = range(1000)
def create_promise(): # unnecessary function call
return Promise.all(values)
result = benchmark(create_promise)
assert isinstance(result, Promise)
assert result.get() == list(range(1000))
def test_benchmark_promise_all_promise(benchmark):
values = [Promise.resolve(i) for i in range(100000)]
def create_promise(): # unnecessary function call
return Promise.all(values)
result = benchmark(create_promise)
assert isinstance(result, Promise)
assert result.get() == list(range(100000))
promise-2.3.0/tests/test_complex_threads.py 0000664 0000000 0000000 00000000745 13576352764 0021156 0 ustar 00root root 0000000 0000000 from time import sleep
from concurrent.futures import ThreadPoolExecutor
from promise import Promise
from operator import mul
executor = ThreadPoolExecutor(max_workers=40000)
def promise_factorial(n):
if n < 2:
return 1
sleep(.02)
a = executor.submit(promise_factorial, n - 1)
def promise_then(r):
return mul(r, n)
return Promise.resolve(a).then(promise_then)
def test_factorial():
p = promise_factorial(10)
assert p.get() == 3628800
promise-2.3.0/tests/test_dataloader.py 0000664 0000000 0000000 00000024615 13576352764 0020077 0 ustar 00root root 0000000 0000000 from pytest import raises
from promise import Promise, async_instance
from promise.dataloader import DataLoader
def id_loader(**options):
load_calls = []
resolve = options.pop("resolve", Promise.resolve)
def fn(keys):
load_calls.append(keys)
return resolve(keys)
identity_loader = DataLoader(fn, **options)
return identity_loader, load_calls
def test_build_a_simple_data_loader():
def call_fn(keys):
return Promise.resolve(keys)
identity_loader = DataLoader(call_fn)
promise1 = identity_loader.load(1)
assert isinstance(promise1, Promise)
value1 = promise1.get()
assert value1 == 1
def test_supports_loading_multiple_keys_in_one_call():
def call_fn(keys):
return Promise.resolve(keys)
identity_loader = DataLoader(call_fn)
promise_all = identity_loader.load_many([1, 2])
assert isinstance(promise_all, Promise)
values = promise_all.get()
assert values == [1, 2]
promise_all = identity_loader.load_many([])
assert isinstance(promise_all, Promise)
values = promise_all.get()
assert values == []
def test_batches_multiple_requests():
@Promise.safe
def do():
identity_loader, load_calls = id_loader()
promise1 = identity_loader.load(1)
promise2 = identity_loader.load(2)
p = Promise.all([promise1, promise2])
value1, value2 = p.get()
assert value1 == 1
assert value2 == 2
assert load_calls == [[1, 2]]
do().get()
def test_batches_multiple_requests_with_max_batch_sizes():
@Promise.safe
def do():
identity_loader, load_calls = id_loader(max_batch_size=2)
promise1 = identity_loader.load(1)
promise2 = identity_loader.load(2)
promise3 = identity_loader.load(3)
p = Promise.all([promise1, promise2, promise3])
value1, value2, value3 = p.get()
assert value1 == 1
assert value2 == 2
assert value3 == 3
assert load_calls == [[1, 2], [3]]
do().get()
def test_coalesces_identical_requests():
@Promise.safe
def do():
identity_loader, load_calls = id_loader()
promise1 = identity_loader.load(1)
promise2 = identity_loader.load(1)
assert promise1 == promise2
p = Promise.all([promise1, promise2])
value1, value2 = p.get()
assert value1 == 1
assert value2 == 1
assert load_calls == [[1]]
do().get()
def test_caches_repeated_requests():
@Promise.safe
def do():
identity_loader, load_calls = id_loader()
a, b = Promise.all([identity_loader.load("A"), identity_loader.load("B")]).get()
assert a == "A"
assert b == "B"
assert load_calls == [["A", "B"]]
a2, c = Promise.all(
[identity_loader.load("A"), identity_loader.load("C")]
).get()
assert a2 == "A"
assert c == "C"
assert load_calls == [["A", "B"], ["C"]]
a3, b2, c2 = Promise.all(
[
identity_loader.load("A"),
identity_loader.load("B"),
identity_loader.load("C"),
]
).get()
assert a3 == "A"
assert b2 == "B"
assert c2 == "C"
assert load_calls == [["A", "B"], ["C"]]
do().get()
def test_clears_single_value_in_loader():
@Promise.safe
def do():
identity_loader, load_calls = id_loader()
a, b = Promise.all([identity_loader.load("A"), identity_loader.load("B")]).get()
assert a == "A"
assert b == "B"
assert load_calls == [["A", "B"]]
identity_loader.clear("A")
a2, b2 = Promise.all(
[identity_loader.load("A"), identity_loader.load("B")]
).get()
assert a2 == "A"
assert b2 == "B"
assert load_calls == [["A", "B"], ["A"]]
do().get()
def test_clears_all_values_in_loader():
@Promise.safe
def do():
identity_loader, load_calls = id_loader()
a, b = Promise.all([identity_loader.load("A"), identity_loader.load("B")]).get()
assert a == "A"
assert b == "B"
assert load_calls == [["A", "B"]]
identity_loader.clear_all()
a2, b2 = Promise.all(
[identity_loader.load("A"), identity_loader.load("B")]
).get()
assert a2 == "A"
assert b2 == "B"
assert load_calls == [["A", "B"], ["A", "B"]]
do().get()
def test_does_not_replace_cache_map():
@Promise.safe
def do():
identity_loader, _ = id_loader()
a, b = Promise.all([identity_loader.load("A"), identity_loader.load("B")]).get()
assert a == "A"
assert b == "B"
cache_map = identity_loader._promise_cache
identity_loader.clear_all()
assert id(identity_loader._promise_cache) == id(cache_map)
do().get()
def test_allows_priming_the_cache():
@Promise.safe
def do():
identity_loader, load_calls = id_loader()
identity_loader.prime("A", "A")
a, b = Promise.all([identity_loader.load("A"), identity_loader.load("B")]).get()
assert a == "A"
assert b == "B"
assert load_calls == [["B"]]
do().get()
def test_does_not_prime_keys_that_already_exist():
@Promise.safe
def do():
identity_loader, load_calls = id_loader()
identity_loader.prime("A", "X")
a1 = identity_loader.load("A").get()
b1 = identity_loader.load("B").get()
assert a1 == "X"
assert b1 == "B"
identity_loader.prime("A", "Y")
identity_loader.prime("B", "Y")
a2 = identity_loader.load("A").get()
b2 = identity_loader.load("B").get()
assert a2 == "X"
assert b2 == "B"
assert load_calls == [["B"]]
do().get()
# Represents Errors
def test_resolves_to_error_to_indicate_failure():
@Promise.safe
def do():
def resolve(keys):
mapped_keys = [
key if key % 2 == 0 else Exception("Odd: {}".format(key))
for key in keys
]
return Promise.resolve(mapped_keys)
even_loader, load_calls = id_loader(resolve=resolve)
with raises(Exception) as exc_info:
even_loader.load(1).get()
assert str(exc_info.value) == "Odd: 1"
value2 = even_loader.load(2).get()
assert value2 == 2
assert load_calls == [[1], [2]]
do().get()
def test_can_represent_failures_and_successes_simultaneously():
@Promise.safe
def do():
def resolve(keys):
mapped_keys = [
key if key % 2 == 0 else Exception("Odd: {}".format(key))
for key in keys
]
return Promise.resolve(mapped_keys)
even_loader, load_calls = id_loader(resolve=resolve)
promise1 = even_loader.load(1)
promise2 = even_loader.load(2)
with raises(Exception) as exc_info:
promise1.get()
assert str(exc_info.value) == "Odd: 1"
value2 = promise2.get()
assert value2 == 2
assert load_calls == [[1, 2]]
do().get()
def test_caches_failed_fetches():
@Promise.safe
def do():
def resolve(keys):
mapped_keys = [Exception("Error: {}".format(key)) for key in keys]
return Promise.resolve(mapped_keys)
error_loader, load_calls = id_loader(resolve=resolve)
with raises(Exception) as exc_info:
error_loader.load(1).get()
assert str(exc_info.value) == "Error: 1"
with raises(Exception) as exc_info:
error_loader.load(1).get()
assert str(exc_info.value) == "Error: 1"
assert load_calls == [[1]]
do().get()
def test_caches_failed_fetches():
@Promise.safe
def do():
identity_loader, load_calls = id_loader()
identity_loader.prime(1, Exception("Error: 1"))
with raises(Exception) as exc_info:
identity_loader.load(1).get()
assert load_calls == []
do().get()
# It is resilient to job queue ordering
# def test_batches_loads_occuring_within_promises():
# @Promise.safe
# def do():
# identity_loader, load_calls = id_loader()
# values = Promise.all([
# identity_loader.load('A'),
# Promise.resolve(None).then(lambda v: Promise.resolve(None)).then(
# lambda v: identity_loader.load('B')
# )
# ]).get()
# assert values == ['A', 'B']
# assert load_calls == [['A', 'B']]
# do().get()
def test_catches_error_if_loader_resolver_fails():
@Promise.safe
def do():
def do_resolve(x):
raise Exception("AOH!")
a_loader, a_load_calls = id_loader(resolve=do_resolve)
with raises(Exception) as exc_info:
a_loader.load("A1").get()
assert str(exc_info.value) == "AOH!"
do().get()
def test_can_call_a_loader_from_a_loader():
@Promise.safe
def do():
deep_loader, deep_load_calls = id_loader()
a_loader, a_load_calls = id_loader(
resolve=lambda keys: deep_loader.load(tuple(keys))
)
b_loader, b_load_calls = id_loader(
resolve=lambda keys: deep_loader.load(tuple(keys))
)
a1, b1, a2, b2 = Promise.all(
[
a_loader.load("A1"),
b_loader.load("B1"),
a_loader.load("A2"),
b_loader.load("B2"),
]
).get()
assert a1 == "A1"
assert b1 == "B1"
assert a2 == "A2"
assert b2 == "B2"
assert a_load_calls == [["A1", "A2"]]
assert b_load_calls == [["B1", "B2"]]
assert deep_load_calls == [[("A1", "A2"), ("B1", "B2")]]
do().get()
def test_dataloader_clear_with_missing_key_works():
@Promise.safe
def do():
def do_resolve(x):
return x
a_loader, a_load_calls = id_loader(resolve=do_resolve)
assert a_loader.clear("A1") == a_loader
do().get()
def test_wrong_loader_return_type_does_not_block_async_instance():
@Promise.safe
def do():
def do_resolve(x):
return x
a_loader, a_load_calls = id_loader(resolve=do_resolve)
with raises(Exception):
a_loader.load("A1").get()
assert async_instance.have_drained_queues
with raises(Exception):
a_loader.load("A2").get()
assert async_instance.have_drained_queues
do().get()
promise-2.3.0/tests/test_dataloader_awaitable_35.py 0000664 0000000 0000000 00000005436 13576352764 0022417 0 ustar 00root root 0000000 0000000 from pytest import mark
from promise import Promise
from promise.dataloader import DataLoader
def id_loader(**options):
load_calls = []
resolve = options.pop("resolve", Promise.resolve)
def fn(keys):
load_calls.append(keys)
return resolve(keys)
identity_loader = DataLoader(fn, **options)
return identity_loader, load_calls
@mark.asyncio
async def test_await_dataloader():
identity_loader, load_calls = id_loader()
async def load_multiple(identity_loader):
one = identity_loader.load("load1")
two = identity_loader.load("load2")
return await Promise.all([one, two])
result = await load_multiple(identity_loader)
assert result == ["load1", "load2"]
assert load_calls == [["load1"], ["load2"]]
@mark.asyncio
async def test_await_dataloader_safe_promise():
identity_loader, load_calls = id_loader()
@Promise.safe
async def load_multiple(identity_loader):
one = identity_loader.load("load1")
two = identity_loader.load("load2")
return await Promise.all([one, two])
result = await load_multiple(identity_loader)
assert result == ["load1", "load2"]
assert load_calls == [["load1"], ["load2"]]
@mark.asyncio
async def test_await_dataloader_individual():
identity_loader, load_calls = id_loader()
async def load_one_then_two(identity_loader):
one = await identity_loader.load("load1")
two = await identity_loader.load("load2")
return [one, two]
result = await load_one_then_two(identity_loader)
assert result == ["load1", "load2"]
assert load_calls == [["load1"], ["load2"]]
@mark.asyncio
async def test_await_dataloader_individual_safe_promise():
identity_loader, load_calls = id_loader()
@Promise.safe
async def load_one_then_two(identity_loader):
one = await identity_loader.load("load1")
two = await identity_loader.load("load2")
return [one, two]
result = await load_one_then_two(identity_loader)
assert result == ["load1", "load2"]
assert load_calls == [["load1"], ["load2"]]
@mark.asyncio
async def test_await_dataloader_two():
identity_loader, load_calls = id_loader()
async def load_one_then_two(identity_loader):
one = await identity_loader.load("load1")
two = await identity_loader.load("load2")
return (one, two)
result12 = await Promise.all([load_one_then_two(identity_loader)])
@mark.asyncio
async def test_await_dataloader_two_safe_promise():
identity_loader, load_calls = id_loader()
@Promise.safe
async def load_one_then_two(identity_loader):
one = await identity_loader.load("load1")
two = await identity_loader.load("load2")
return (one, two)
result12 = await Promise.all([load_one_then_two(identity_loader)])
promise-2.3.0/tests/test_dataloader_extra.py 0000664 0000000 0000000 00000002762 13576352764 0021301 0 ustar 00root root 0000000 0000000 # from promise import Promise
# from promise.dataloader import DataLoader
# def id_loader(**options):
# load_calls = []
# def fn(keys):
# load_calls.append(keys)
# return Promise.resolve(keys)
# identity_loader = DataLoader(fn, **options)
# return identity_loader, load_calls
# def test_batches_multiple_requests():
# identity_loader, load_calls = id_loader()
# @Promise.safe
# def safe():
# promise1 = identity_loader.load(1)
# promise2 = identity_loader.load(2)
# return promise1, promise2
# promise1, promise2 = safe()
# value1, value2 = Promise.all([promise1, promise2]).get()
# assert value1 == 1
# assert value2 == 2
# assert load_calls == [[1, 2]]
# def test_batches_multiple_requests_two():
# identity_loader, load_calls = id_loader()
# @Promise.safe
# def safe():
# promise1 = identity_loader.load(1)
# promise2 = identity_loader.load(2)
# return Promise.all([promise1, promise2])
# p = safe()
# value1, value2 = p.get()
# assert value1 == 1
# assert value2 == 2
# assert load_calls == [[1, 2]]
# @Promise.safe
# def test_batches_multiple_requests_safe():
# identity_loader, load_calls = id_loader()
# promise1 = identity_loader.load(1)
# promise2 = identity_loader.load(2)
# p = Promise.all([promise1, promise2])
# value1, value2 = p.get()
# assert value1 == 1
# assert value2 == 2
# assert load_calls == [[1, 2]]
promise-2.3.0/tests/test_extra.py 0000664 0000000 0000000 00000034356 13576352764 0017125 0 ustar 00root root 0000000 0000000 # This exercises some capabilities above and beyond
# the Promises/A+ test suite
from time import sleep
from pytest import raises, fixture
from threading import Event
from promise import (
Promise,
is_thenable,
promisify,
promise_for_dict as free_promise_for_dict,
)
from concurrent.futures import Future
from threading import Thread
from .utils import assert_exception
class DelayedFulfill(Thread):
def __init__(self, d, p, v):
self.delay = d
self.promise = p
self.value = v
Thread.__init__(self)
def run(self):
sleep(self.delay)
self.promise.do_resolve(self.value)
class DelayedRejection(Thread):
def __init__(self, d, p, r):
self.delay = d
self.promise = p
self.reason = r
Thread.__init__(self)
def run(self):
sleep(self.delay)
self.promise.do_reject(self.reason)
class FakeThenPromise:
def __init__(self, raises=True):
self.raises = raises
def then(self, s=None, f=None):
if self.raises:
raise Exception("FakeThenPromise raises in 'then'")
def df(value, dtime):
p = Promise()
t = DelayedFulfill(dtime, p, value)
t.start()
return p
def dr(reason, dtime):
p = Promise()
t = DelayedRejection(dtime, p, reason)
t.start()
return p
# Static methods
def test_fulfilled():
p = Promise.fulfilled(4)
assert p.is_fulfilled
assert p.get() == 4
def test_rejected():
p = Promise.rejected(Exception("Static rejected"))
assert p.is_rejected
with raises(Exception) as exc_info:
p.get()
assert str(exc_info.value) == "Static rejected"
# Fulfill
def test_fulfill_self():
p = Promise()
with raises(TypeError) as excinfo:
p.do_resolve(p)
p.get()
# Exceptions
def test_exceptions():
def throws(v):
assert False
p1 = Promise()
p1.then(throws)
p1.do_resolve(5)
p2 = Promise()
p2.catch(throws)
p2.do_reject(Exception())
with raises(Exception) as excinfo:
p2.get()
def test_thrown_exceptions_have_stacktrace():
def throws(v):
assert False
p3 = Promise.resolve("a").then(throws)
with raises(AssertionError) as assert_exc:
p3.get()
assert assert_exc.traceback[-1].path.strpath == __file__
def test_thrown_exceptions_preserve_stacktrace():
def throws(v):
assert False
def after_throws(v):
pass
p3 = Promise.resolve("a").then(throws).then(after_throws)
with raises(AssertionError) as assert_exc:
p3.get()
assert assert_exc.traceback[-1].path.strpath == __file__
# WAIT
# def test_wait_when():
# p1 = df(5, 0.01)
# assert p1.is_pending
# p1._wait()
# assert p1.is_fulfilled
def test_wait_if():
p1 = Promise()
p1.do_resolve(5)
p1._wait()
assert p1.is_fulfilled
# def test_wait_timeout():
# p1 = df(5, 0.1)
# assert p1.is_pending
# with raises(Exception) as exc_info:
# p1._wait(timeout=0.05)
# assert str(exc_info.value) == "Timeout"
# assert p1.is_pending
# p1._wait()
# assert p1.is_fulfilled
# # GET
# def test_get_when():
# p1 = df(5, 0.01)
# assert p1.is_pending
# v = p1.get()
# assert p1.is_fulfilled
# assert 5 == v
def test_get_if():
p1 = Promise()
p1.do_resolve(5)
v = p1.get()
assert p1.is_fulfilled
assert 5 == v
# def test_get_timeout():
# p1 = df(5, 0.1)
# assert p1.is_pending
# with raises(Exception) as exc_info:
# p1._wait(timeout=0.05)
# assert str(exc_info.value) == "Timeout"
# assert p1.is_pending
# v = p1.get()
# assert p1.is_fulfilled
# assert 5 == v
# Promise.all
def test_promise_all_when():
p1 = Promise()
p2 = Promise()
pl = Promise.all([p1, p2])
assert p1.is_pending
assert p2.is_pending
assert pl.is_pending
p1.do_resolve(5)
p1._wait()
assert p1.is_fulfilled
assert p2.is_pending
assert pl.is_pending
p2.do_resolve(10)
p2._wait()
pl._wait()
assert p1.is_fulfilled
assert p2.is_fulfilled
assert pl.is_fulfilled
assert 5 == p1.get()
assert 10 == p2.get()
assert 5 == pl.get()[0]
assert 10 == pl.get()[1]
def test_promise_all_when_mixed_promises():
p1 = Promise()
p2 = Promise()
pl = Promise.all([p1, 32, p2, False, True])
assert p1.is_pending
assert p2.is_pending
assert pl.is_pending
p1.do_resolve(5)
p1._wait()
assert p1.is_fulfilled
assert p2.is_pending
assert pl.is_pending
p2.do_resolve(10)
p2._wait()
pl._wait()
assert p1.is_fulfilled
assert p2.is_fulfilled
assert pl.is_fulfilled
assert 5 == p1.get()
assert 10 == p2.get()
assert pl.get() == [5, 32, 10, False, True]
def test_promise_all_when_if_no_promises():
pl = Promise.all([10, 32, False, True])
assert pl.get() == [10, 32, False, True]
def test_promise_all_if():
p1 = Promise()
p2 = Promise()
pd1 = Promise.all([p1, p2])
pd2 = Promise.all([p1])
pd3 = Promise.all([])
pd3._wait()
assert p1.is_pending
assert p2.is_pending
assert pd1.is_pending
assert pd2.is_pending
assert pd3.is_fulfilled
p1.do_resolve(5)
p1._wait()
pd2._wait()
assert p1.is_fulfilled
assert p2.is_pending
assert pd1.is_pending
assert pd2.is_fulfilled
p2.do_resolve(10)
p2._wait()
pd1._wait()
pd2._wait()
assert p1.is_fulfilled
assert p2.is_fulfilled
assert pd1.is_fulfilled
assert pd2.is_fulfilled
assert 5 == p1.get()
assert 10 == p2.get()
assert 5 == pd1.get()[0]
assert 5 == pd2.get()[0]
assert 10 == pd1.get()[1]
assert [] == pd3.get()
# promise_for_dict
@fixture(params=[Promise.for_dict, free_promise_for_dict])
def promise_for_dict(request):
return request.param
def test_dict_promise_when(promise_for_dict):
p1 = Promise()
p2 = Promise()
d = {"a": p1, "b": p2}
pd1 = promise_for_dict(d)
pd2 = promise_for_dict({"a": p1})
pd3 = promise_for_dict({})
assert p1.is_pending
assert p2.is_pending
assert pd1.is_pending
assert pd2.is_pending
pd3._wait()
assert pd3.is_fulfilled
p1.do_resolve(5)
p1._wait()
pd2._wait()
assert p1.is_fulfilled
assert p2.is_pending
assert pd1.is_pending
assert pd2.is_fulfilled
p2.do_resolve(10)
p2._wait()
pd1._wait()
assert p1.is_fulfilled
assert p2.is_fulfilled
assert pd1.is_fulfilled
assert pd2.is_fulfilled
assert 5 == p1.get()
assert 10 == p2.get()
assert 5 == pd1.get()["a"]
assert 5 == pd2.get()["a"]
assert 10 == pd1.get()["b"]
assert {} == pd3.get()
def test_dict_promise_if(promise_for_dict):
p1 = Promise()
p2 = Promise()
d = {"a": p1, "b": p2}
pd = promise_for_dict(d)
assert p1.is_pending
assert p2.is_pending
assert pd.is_pending
p1.do_resolve(5)
p1._wait()
assert p1.is_fulfilled
assert p2.is_pending
assert pd.is_pending
p2.do_resolve(10)
p2._wait()
assert p1.is_fulfilled
assert p2.is_fulfilled
# pd._wait()
# assert pd.is_fulfilled
# assert 5 == p1.get()
# assert 10 == p2.get()
# assert 5 == pd.get()["a"]
# assert 10 == pd.get()["b"]
def test_done():
counter = [0]
r = Promise()
def inc(_):
counter[0] += 1
def dec(_):
counter[0] -= 1
def end(_):
r.do_resolve(None)
p = Promise()
p.done(inc, dec)
p.done(inc, dec)
p.done(end)
p.do_resolve(4)
Promise.wait(r)
assert counter[0] == 2
r = Promise()
counter = [0]
p = Promise()
p.done(inc, dec)
p.done(inc, dec)
p.done(None, end)
p.do_reject(Exception())
Promise.wait(r)
assert counter[0] == -2
def test_done_all():
counter = [0]
def inc(_):
counter[0] += 1
def dec(_):
counter[0] -= 1
p = Promise()
r = Promise()
p.done_all()
p.done_all([(inc, dec)])
p.done_all(
[
(inc, dec),
(inc, dec),
{"success": inc, "failure": dec},
lambda _: r.do_resolve(None),
]
)
p.do_resolve(4)
Promise.wait(r)
assert counter[0] == 4
p = Promise()
r = Promise()
p.done_all()
p.done_all([inc])
p.done_all([(inc, dec)])
p.done_all(
[
(inc, dec),
{"success": inc, "failure": dec},
(None, lambda _: r.do_resolve(None)),
]
)
p.do_reject(Exception("Uh oh!"))
Promise.wait(r)
assert counter[0] == 1
def test_then_all():
p = Promise()
handlers = [
((lambda x: x * x), (lambda r: 1)),
{"success": (lambda x: x + x), "failure": (lambda r: 2)},
]
results = (
p.then_all()
+ p.then_all([lambda x: x])
+ p.then_all([(lambda x: x * x, lambda r: 1)])
+ p.then_all(handlers)
)
p.do_resolve(4)
assert [r.get() for r in results] == [4, 16, 16, 8]
p = Promise()
handlers = [
((lambda x: x * x), (lambda r: 1)),
{"success": (lambda x: x + x), "failure": (lambda r: 2)},
]
results = (
p.then_all()
+ p.then_all([(lambda x: x * x, lambda r: 1)])
+ p.then_all(handlers)
)
p.do_reject(Exception())
assert [r.get() for r in results] == [1, 1, 2]
def test_do_resolve():
p1 = Promise(lambda resolve, reject: resolve(0))
assert p1.get() == 0
assert p1.is_fulfilled
def test_do_resolve_fail_on_call():
def raises(resolve, reject):
raise Exception("Fails")
p1 = Promise(raises)
assert not p1.is_fulfilled
assert str(p1.reason) == "Fails"
def test_catch():
p1 = Promise(lambda resolve, reject: resolve(0))
p2 = p1.then(lambda value: 1 / value).catch(lambda e: e).then(lambda e: type(e))
assert p2.get() == ZeroDivisionError
assert p2.is_fulfilled
def test_is_thenable_promise():
promise = Promise()
assert is_thenable(promise)
def test_is_thenable_then_object():
promise = FakeThenPromise()
assert not is_thenable(promise)
def test_is_thenable_future():
promise = Future()
assert is_thenable(promise)
def test_is_thenable_simple_object():
assert not is_thenable(object())
@fixture(params=[Promise.resolve])
def resolve(request):
return request.param
def test_resolve_promise(resolve):
promise = Promise()
assert resolve(promise) == promise
def test_resolve_then_object(resolve):
promise = FakeThenPromise(raises=False)
p = resolve(promise)
assert isinstance(p, Promise)
def test_resolve_future(resolve):
future = Future()
promise = resolve(future)
assert promise.is_pending
future.set_result(1)
assert promise.get() == 1
assert promise.is_fulfilled
def test_resolve_future_rejected(resolve):
future = Future()
promise = resolve(future)
assert promise.is_pending
future.set_exception(Exception("Future rejected"))
assert promise.is_rejected
assert_exception(promise.reason, Exception, "Future rejected")
def test_resolve_object(resolve):
val = object()
promised = resolve(val)
assert isinstance(promised, Promise)
assert promised.get() == val
def test_resolve_promise_subclass():
class MyPromise(Promise):
pass
p = Promise()
p.do_resolve(10)
m_p = MyPromise.resolve(p)
assert isinstance(m_p, MyPromise)
assert m_p.get() == p.get()
def test_promise_repr_pending():
promise = Promise()
assert repr(promise) == "".format(hex(id(promise)))
def test_promise_repr_pending():
val = {1: 2}
promise = Promise.fulfilled(val)
promise._wait()
assert repr(promise) == "".format(
hex(id(promise)), repr(val)
)
def test_promise_repr_fulfilled():
val = {1: 2}
promise = Promise.fulfilled(val)
promise._wait()
assert repr(promise) == "".format(
hex(id(promise)), repr(val)
)
def test_promise_repr_rejected():
err = Exception("Error!")
promise = Promise.rejected(err)
promise._wait()
assert repr(promise) == "".format(
hex(id(promise)), repr(err)
)
def test_promise_loop():
def by_two(result):
return result * 2
def executor(resolve, reject):
resolve(Promise.resolve(1).then(lambda v: Promise.resolve(v).then(by_two)))
p = Promise(executor)
assert p.get(.1) == 2
def test_resolve_future_like(resolve):
class CustomThenable(object):
def add_done_callback(self, f):
f(True)
def done(self):
return True
def exception(self):
pass
def result(self):
return True
instance = CustomThenable()
promise = resolve(instance)
assert promise.get() == True
def sum_function(a, b):
return a + b
def test_promisify_function_resolved(resolve):
promisified_func = promisify(sum_function)
result = promisified_func(1, 2)
assert isinstance(result, Promise)
assert result.get() == 3
def test_promisify_function_rejected(resolve):
promisified_func = promisify(sum_function)
result = promisified_func(None, None)
assert isinstance(result, Promise)
with raises(Exception) as exc_info_promise:
result.get()
with raises(Exception) as exc_info:
sum_function(None, None)
assert str(exc_info_promise.value) == str(exc_info.value)
def test_promises_with_only_then():
context = {"success": False}
error = RuntimeError("Ooops!")
promise1 = Promise(
lambda resolve, reject: context.update({"promise1_reject": reject})
)
promise2 = promise1.then(lambda x: None)
promise3 = promise1.then(lambda x: None)
context["promise1_reject"](error)
promise2._wait()
promise3._wait()
assert promise2.reason == error
assert promise3.reason == error
def test_promises_promisify_still_works_but_deprecated_for_non_callables():
x = promisify(1)
assert isinstance(x, Promise)
assert x.get() == 1
# def test_promise_loop():
# values = Promise.resolve([1, None, 2])
# def on_error(error):
# error
# def executor(resolve, reject):
# resolve(Promise.resolve(values).then(lambda values: Promise.all([Promise.resolve(values[0])]).catch(on_error)))
# p = Promise(executor)
# assert p.get(.1) == 2
promise-2.3.0/tests/test_issues.py 0000664 0000000 0000000 00000007152 13576352764 0017307 0 ustar 00root root 0000000 0000000 # This tests reported issues in the Promise package
from concurrent.futures import ThreadPoolExecutor
from promise import Promise
import time
import weakref
import gc
executor = ThreadPoolExecutor(max_workers=40000)
def test_issue_11():
# https://github.com/syrusakbary/promise/issues/11
def test(x):
def my(resolve, reject):
if x > 0:
resolve(x)
else:
reject(Exception(x))
return Promise(my)
promise_resolved = test(42).then(lambda x: x)
assert promise_resolved.get() == 42
promise_rejected = test(-42).then(lambda x: x, lambda e: str(e))
assert promise_rejected.get() == "-42"
def identity(x, wait):
if wait:
time.sleep(wait)
return x
def promise_with_wait(x, wait):
return Promise.resolve(identity(x, wait))
def test_issue_9():
no_wait = Promise.all(
[promise_with_wait(x1, None).then(lambda y: x1 * y) for x1 in (0, 1, 2, 3)]
).get()
wait_a_bit = Promise.all(
[promise_with_wait(x2, 0.05).then(lambda y: x2 * y) for x2 in (0, 1, 2, 3)]
).get()
wait_longer = Promise.all(
[promise_with_wait(x3, 0.1).then(lambda y: x3 * y) for x3 in (0, 1, 2, 3)]
).get()
assert no_wait == wait_a_bit
assert no_wait == wait_longer
@Promise.safe
def test_issue_9_safe():
no_wait = Promise.all(
[promise_with_wait(x1, None).then(lambda y: x1 * y) for x1 in (0, 1, 2, 3)]
).get()
wait_a_bit = Promise.all(
[promise_with_wait(x2, 0.05).then(lambda y: x2 * y) for x2 in (0, 1, 2, 3)]
).get()
wait_longer = Promise.all(
[promise_with_wait(x3, 0.1).then(lambda y: x3 * y) for x3 in (0, 1, 2, 3)]
).get()
assert no_wait == [0, 3, 6, 9]
assert no_wait == wait_a_bit
assert no_wait == wait_longer
def test_issue_26():
context = {"success": False}
promise1 = Promise(
lambda resolve, reject: context.update({"promise1_reject": reject})
)
promise1.then(lambda x: None)
promise1.then(lambda x: None)
context["promise1_reject"](RuntimeError("Ooops!"))
promise2 = Promise(
lambda resolve, reject: context.update({"promise2_resolve": resolve})
)
promise3 = promise2.then(lambda x: context.update({"success": True}))
context["promise2_resolve"](None)
# We wait so it works in asynchronous envs
promise3._wait(timeout=.1)
assert context["success"]
# def promise_in_executor(x, wait):
# return Promise.promisify(executor.submit(identity, x, wait))
# @Promise.safe
# def test_issue_9_extra():
# no_wait = Promise.all([promise_in_executor(x1, None).then(lambda y: x1*y) for x1 in (0,1,2,3)]).get()
# wait_a_bit = Promise.all([promise_in_executor(x2, 0.1).then(lambda y: x2*y) for x2 in (0,1,2,3)]).get()
# wait_longer = Promise.all([promise_in_executor(x3, 0.5).then(lambda y: x3*y) for x3 in (0,1,2,3)]).get()
# assert no_wait == [0, 3, 6, 9]
# assert no_wait == wait_a_bit
# assert no_wait == wait_longer
def test_issue_33():
def do(x):
v = Promise.resolve("ok").then(lambda x: x).get()
return v
p = Promise.resolve(None).then(do)
assert p.get() == "ok"
def test_issue_75():
def function_with_local_type():
class A:
pass
a = A()
assert a == Promise.resolve(a).get()
return weakref.ref(A)
weak_reference = function_with_local_type()
# The local type 'A' from the function is still kept alive by reference cycles.
gc.collect()
# Now the local type should have been garbage collected,
# such that the weak reference should be invalid.
assert not weak_reference()
promise-2.3.0/tests/test_promise_list.py 0000664 0000000 0000000 00000003155 13576352764 0020504 0 ustar 00root root 0000000 0000000 from pytest import raises
from promise import Promise
from promise.promise_list import PromiseList
def all(promises):
return PromiseList(promises, Promise).promise
def test_empty_promises():
all_promises = all([])
assert all_promises.get() == []
def test_bad_promises():
all_promises = all(None)
with raises(Exception) as exc_info:
all_promises.get()
assert str(exc_info.value) == "PromiseList requires an iterable. Received None."
def test_promise_basic():
all_promises = all([1, 2])
assert all_promises.get() == [1, 2]
def test_promise_mixed():
all_promises = all([1, 2, Promise.resolve(3)])
assert all_promises.get() == [1, 2, 3]
def test_promise_rejected():
e = Exception("Error")
all_promises = all([1, 2, Promise.reject(e)])
with raises(Exception) as exc_info:
all_promises.get()
assert str(exc_info.value) == "Error"
def test_promise_reject_skip_all_other_values():
e1 = Exception("Error1")
e2 = Exception("Error2")
p = Promise()
all_promises = all([1, Promise.reject(e1), Promise.reject(e2)])
with raises(Exception) as exc_info:
all_promises.get()
assert str(exc_info.value) == "Error1"
def test_promise_lazy_promise():
p = Promise()
all_promises = all([1, 2, p])
assert not all_promises.is_fulfilled
p.do_resolve(3)
assert all_promises.get() == [1, 2, 3]
def test_promise_contained_promise():
p = Promise()
all_promises = all([1, 2, Promise.resolve(None).then(lambda v: p)])
assert not all_promises.is_fulfilled
p.do_resolve(3)
assert all_promises.get() == [1, 2, 3]
promise-2.3.0/tests/test_spec.py 0000664 0000000 0000000 00000032201 13576352764 0016717 0 ustar 00root root 0000000 0000000 # Tests the spec based on:
# https://github.com/promises-aplus/promises-tests
from promise import Promise
from .utils import assert_exception
from threading import Event
class Counter:
"""
A helper class with some side effects
we can test.
"""
def __init__(self):
self.count = 0
def tick(self):
self.count += 1
def value(self):
return self.count
def test_3_2_1():
"""
Test that the arguments to 'then' are optional.
"""
p1 = Promise()
p2 = p1.then()
p3 = Promise()
p4 = p3.then()
p1.do_resolve(5)
p3.do_reject(Exception("How dare you!"))
def test_3_2_1_1():
"""
That that the first argument to 'then' is ignored if it
is not a function.
"""
results = {}
nonFunctions = [None, False, 5, {}, []]
def testNonFunction(nonFunction):
def foo(k, r):
results[k] = r
p1 = Promise.reject(Exception("Error: " + str(nonFunction)))
p2 = p1.then(nonFunction, lambda r: foo(str(nonFunction), r))
p2._wait()
for v in nonFunctions:
testNonFunction(v)
for v in nonFunctions:
assert_exception(results[str(v)], Exception, "Error: " + str(v))
def test_3_2_1_2():
"""
That that the second argument to 'then' is ignored if it
is not a function.
"""
results = {}
nonFunctions = [None, False, 5, {}, []]
def testNonFunction(nonFunction):
def foo(k, r):
results[k] = r
p1 = Promise.resolve("Error: " + str(nonFunction))
p2 = p1.then(lambda r: foo(str(nonFunction), r), nonFunction)
p2._wait()
for v in nonFunctions:
testNonFunction(v)
for v in nonFunctions:
assert "Error: " + str(v) == results[str(v)]
def test_3_2_2_1():
"""
The first argument to 'then' must be called when a promise is
fulfilled.
"""
c = Counter()
def check(v, c):
assert v == 5
c.tick()
p1 = Promise.resolve(5)
p2 = p1.then(lambda v: check(v, c))
p2._wait()
assert 1 == c.value()
def test_3_2_2_2():
"""
Make sure callbacks are never called more than once.
"""
c = Counter()
p1 = Promise.resolve(5)
p2 = p1.then(lambda v: c.tick())
p2._wait()
try:
# I throw an exception
p1.do_resolve(5)
assert False # Should not get here!
except AssertionError:
# This is expected
pass
assert 1 == c.value()
def test_3_2_2_3():
"""
Make sure fulfilled callback never called if promise is rejected
"""
cf = Counter()
cr = Counter()
p1 = Promise.reject(Exception("Error"))
p2 = p1.then(lambda v: cf.tick(), lambda r: cr.tick())
p2._wait()
assert 0 == cf.value()
assert 1 == cr.value()
def test_3_2_3_1():
"""
The second argument to 'then' must be called when a promise is
rejected.
"""
c = Counter()
def check(r, c):
assert_exception(r, Exception, "Error")
c.tick()
p1 = Promise.reject(Exception("Error"))
p2 = p1.then(None, lambda r: check(r, c))
p2._wait()
assert 1 == c.value()
def test_3_2_3_2():
"""
Make sure callbacks are never called more than once.
"""
c = Counter()
p1 = Promise.reject(Exception("Error"))
p2 = p1.then(None, lambda v: c.tick())
p2._wait()
try:
# I throw an exception
p1.do_reject(Exception("Error"))
assert False # Should not get here!
except AssertionError:
# This is expected
pass
assert 1 == c.value()
def test_3_2_3_3():
"""
Make sure rejected callback never called if promise is fulfilled
"""
cf = Counter()
cr = Counter()
p1 = Promise.resolve(5)
p2 = p1.then(lambda v: cf.tick(), lambda r: cr.tick())
p2._wait()
assert 0 == cr.value()
assert 1 == cf.value()
def test_3_2_5_1_when():
"""
Then can be called multiple times on the same promise
and callbacks must be called in the order of the
then calls.
"""
def add(l, v):
l.append(v)
p1 = Promise.resolve(2)
order = []
p2 = p1.then(lambda v: add(order, "p2"))
p3 = p1.then(lambda v: add(order, "p3"))
p2._wait()
p3._wait()
assert 2 == len(order)
assert "p2" == order[0]
assert "p3" == order[1]
def test_3_2_5_1_if():
"""
Then can be called multiple times on the same promise
and callbacks must be called in the order of the
then calls.
"""
def add(l, v):
l.append(v)
p1 = Promise.resolve(2)
order = []
p2 = p1.then(lambda v: add(order, "p2"))
p3 = p1.then(lambda v: add(order, "p3"))
p2._wait()
p3._wait()
assert 2 == len(order)
assert "p2" == order[0]
assert "p3" == order[1]
def test_3_2_5_2_when():
"""
Then can be called multiple times on the same promise
and callbacks must be called in the order of the
then calls.
"""
def add(l, v):
l.append(v)
p1 = Promise.reject(Exception("Error"))
order = []
p2 = p1.then(None, lambda v: add(order, "p2"))
p3 = p1.then(None, lambda v: add(order, "p3"))
p2._wait()
p3._wait()
assert 2 == len(order)
assert "p2" == order[0]
assert "p3" == order[1]
def test_3_2_5_2_if():
"""
Then can be called multiple times on the same promise
and callbacks must be called in the order of the
then calls.
"""
def add(l, v):
l.append(v)
p1 = Promise.reject(Exception("Error"))
order = []
p2 = p1.then(None, lambda v: add(order, "p2"))
p3 = p1.then(None, lambda v: add(order, "p3"))
p2._wait()
p3._wait()
assert 2 == len(order)
assert "p2" == order[0]
assert "p3" == order[1]
def test_3_2_6_1():
"""
Promises returned by then must be fulfilled when the promise
they are chained from is fulfilled IF the fulfillment value
is not a promise.
"""
p1 = Promise.resolve(5)
pf = p1.then(lambda v: v * v)
assert pf.get() == 25
p2 = Promise.reject(Exception("Error"))
pr = p2.then(None, lambda r: 5)
assert 5 == pr.get()
def test_3_2_6_2_when():
"""
Promises returned by then must be rejected when any of their
callbacks throw an exception.
"""
def fail(v):
raise AssertionError("Exception Message")
p1 = Promise.resolve(5)
pf = p1.then(fail)
pf._wait()
assert pf.is_rejected
assert_exception(pf.reason, AssertionError, "Exception Message")
p2 = Promise.reject(Exception("Error"))
pr = p2.then(None, fail)
pr._wait()
assert pr.is_rejected
assert_exception(pr.reason, AssertionError, "Exception Message")
def test_3_2_6_2_if():
"""
Promises returned by then must be rejected when any of their
callbacks throw an exception.
"""
def fail(v):
raise AssertionError("Exception Message")
p1 = Promise.resolve(5)
pf = p1.then(fail)
pf._wait()
assert pf.is_rejected
assert_exception(pf.reason, AssertionError, "Exception Message")
p2 = Promise.reject(Exception("Error"))
pr = p2.then(None, fail)
pr._wait()
assert pr.is_rejected
assert_exception(pr.reason, AssertionError, "Exception Message")
def test_3_2_6_3_when_fulfilled():
"""
Testing return of pending promises to make
sure they are properly chained.
This covers the case where the root promise
is fulfilled after the chaining is defined.
"""
p1 = Promise()
pending = Promise()
def p1_resolved(v):
return pending
pf = p1.then(p1_resolved)
assert pending.is_pending
assert pf.is_pending
p1.do_resolve(10)
pending.do_resolve(5)
pending._wait()
assert pending.is_fulfilled
assert 5 == pending.get()
pf._wait()
assert pf.is_fulfilled
assert 5 == pf.get()
p2 = Promise()
bad = Promise()
pr = p2.then(lambda r: bad)
assert bad.is_pending
assert pr.is_pending
p2.do_resolve(10)
bad._reject_callback(Exception("Error"))
bad._wait()
assert bad.is_rejected
assert_exception(bad.reason, Exception, "Error")
pr._wait()
assert pr.is_rejected
assert_exception(pr.reason, Exception, "Error")
def test_3_2_6_3_if_fulfilled():
"""
Testing return of pending promises to make
sure they are properly chained.
This covers the case where the root promise
is fulfilled before the chaining is defined.
"""
p1 = Promise()
p1.do_resolve(10)
pending = Promise()
pending.do_resolve(5)
pf = p1.then(lambda r: pending)
pending._wait()
assert pending.is_fulfilled
assert 5 == pending.get()
pf._wait()
assert pf.is_fulfilled
assert 5 == pf.get()
p2 = Promise()
p2.do_resolve(10)
bad = Promise()
bad.do_reject(Exception("Error"))
pr = p2.then(lambda r: bad)
bad._wait()
assert_exception(bad.reason, Exception, "Error")
pr._wait()
assert pr.is_rejected
assert_exception(pr.reason, Exception, "Error")
def test_3_2_6_3_when_rejected():
"""
Testing return of pending promises to make
sure they are properly chained.
This covers the case where the root promise
is rejected after the chaining is defined.
"""
p1 = Promise()
pending = Promise()
pr = p1.then(None, lambda r: pending)
assert pending.is_pending
assert pr.is_pending
p1.do_reject(Exception("Error"))
pending.do_resolve(10)
pending._wait()
assert pending.is_fulfilled
assert 10 == pending.get()
assert 10 == pr.get()
p2 = Promise()
bad = Promise()
pr = p2.then(None, lambda r: bad)
assert bad.is_pending
assert pr.is_pending
p2.do_reject(Exception("Error"))
bad.do_reject(Exception("Assertion"))
bad._wait()
assert bad.is_rejected
assert_exception(bad.reason, Exception, "Assertion")
pr._wait()
assert pr.is_rejected
assert_exception(pr.reason, Exception, "Assertion")
def test_3_2_6_3_if_rejected():
"""
Testing return of pending promises to make
sure they are properly chained.
This covers the case where the root promise
is rejected before the chaining is defined.
"""
p1 = Promise()
p1.do_reject(Exception("Error"))
pending = Promise()
pending.do_resolve(10)
pr = p1.then(None, lambda r: pending)
pending._wait()
assert pending.is_fulfilled
assert 10 == pending.get()
pr._wait()
assert pr.is_fulfilled
assert 10 == pr.get()
p2 = Promise()
p2.do_reject(Exception("Error"))
bad = Promise()
bad.do_reject(Exception("Assertion"))
pr = p2.then(None, lambda r: bad)
bad._wait()
assert bad.is_rejected
assert_exception(bad.reason, Exception, "Assertion")
pr._wait()
assert pr.is_rejected
assert_exception(pr.reason, Exception, "Assertion")
def test_3_2_6_4_pending():
"""
Handles the case where the arguments to then
are not functions or promises.
"""
p1 = Promise()
p2 = p1.then(5)
p1.do_resolve(10)
assert 10 == p1.get()
p2._wait()
assert p2.is_fulfilled
assert 10 == p2.get()
def test_3_2_6_4_fulfilled():
"""
Handles the case where the arguments to then
are values, not functions or promises.
"""
p1 = Promise()
p1.do_resolve(10)
p2 = p1.then(5)
assert 10 == p1.get()
p2._wait()
assert p2.is_fulfilled
assert 10 == p2.get()
def test_3_2_6_5_pending():
"""
Handles the case where the arguments to then
are values, not functions or promises.
"""
p1 = Promise()
p2 = p1.then(None, 5)
p1.do_reject(Exception("Error"))
assert_exception(p1.reason, Exception, "Error")
p2._wait()
assert p2.is_rejected
assert_exception(p2.reason, Exception, "Error")
def test_3_2_6_5_rejected():
"""
Handles the case where the arguments to then
are values, not functions or promises.
"""
p1 = Promise()
p1.do_reject(Exception("Error"))
p2 = p1.then(None, 5)
assert_exception(p1.reason, Exception, "Error")
p2._wait()
assert p2.is_rejected
assert_exception(p2.reason, Exception, "Error")
def test_chained_promises():
"""
Handles the case where the arguments to then
are values, not functions or promises.
"""
p1 = Promise(lambda resolve, reject: resolve(Promise.resolve(True)))
assert p1.get() == True
def test_promise_resolved_after():
"""
The first argument to 'then' must be called when a promise is
fulfilled.
"""
c = Counter()
def check(v, c):
assert v == 5
c.tick()
p1 = Promise()
p2 = p1.then(lambda v: check(v, c))
p1.do_resolve(5)
Promise.wait(p2)
assert 1 == c.value()
def test_promise_follows_indifentely():
a = Promise.resolve(None)
b = a.then(lambda x: Promise.resolve("X"))
e = Event()
def b_then(v):
c = Promise.resolve(None)
d = c.then(lambda v: Promise.resolve("B"))
return d
promise = b.then(b_then)
assert promise.get() == "B"
def test_promise_all_follows_indifentely():
promises = Promise.all(
[
Promise.resolve("A"),
Promise.resolve(None)
.then(Promise.resolve)
.then(lambda v: Promise.resolve(None).then(lambda v: Promise.resolve("B"))),
]
)
assert promises.get() == ["A", "B"]
promise-2.3.0/tests/test_thread_safety.py 0000664 0000000 0000000 00000006601 13576352764 0020614 0 ustar 00root root 0000000 0000000 from promise import Promise
from promise.dataloader import DataLoader
import threading
def test_promise_thread_safety():
"""
Promise tasks should never be executed in a different thread from the one they are scheduled from,
unless the ThreadPoolExecutor is used.
Here we assert that the pending promise tasks on thread 1 are not executed on thread 2 as thread 2
resolves its own promise tasks.
"""
event_1 = threading.Event()
event_2 = threading.Event()
assert_object = {'is_same_thread': True}
def task_1():
thread_name = threading.current_thread().getName()
def then_1(value):
# Enqueue tasks to run later.
# This relies on the fact that `then` does not execute the function synchronously when called from
# within another `then` callback function.
promise = Promise.resolve(None).then(then_2)
assert promise.is_pending
event_1.set() # Unblock main thread
event_2.wait() # Wait for thread 2
def then_2(value):
assert_object['is_same_thread'] = (thread_name == threading.current_thread().getName())
promise = Promise.resolve(None).then(then_1)
def task_2():
promise = Promise.resolve(None).then(lambda v: None)
promise.get() # Drain task queue
event_2.set() # Unblock thread 1
thread_1 = threading.Thread(target=task_1)
thread_1.start()
event_1.wait() # Wait for Thread 1 to enqueue promise tasks
thread_2 = threading.Thread(target=task_2)
thread_2.start()
for thread in (thread_1, thread_2):
thread.join()
assert assert_object['is_same_thread']
def test_dataloader_thread_safety():
"""
Dataloader should only batch `load` calls that happened on the same thread.
Here we assert that `load` calls on thread 2 are not batched on thread 1 as
thread 1 batches its own `load` calls.
"""
def load_many(keys):
thead_name = threading.current_thread().getName()
return Promise.resolve([thead_name for key in keys])
thread_name_loader = DataLoader(load_many)
event_1 = threading.Event()
event_2 = threading.Event()
event_3 = threading.Event()
assert_object = {
'is_same_thread_1': True,
'is_same_thread_2': True,
}
def task_1():
@Promise.safe
def do():
promise = thread_name_loader.load(1)
event_1.set()
event_2.wait() # Wait for thread 2 to call `load`
assert_object['is_same_thread_1'] = (
promise.get() == threading.current_thread().getName()
)
event_3.set() # Unblock thread 2
do().get()
def task_2():
@Promise.safe
def do():
promise = thread_name_loader.load(2)
event_2.set()
event_3.wait() # Wait for thread 1 to run `dispatch_queue_batch`
assert_object['is_same_thread_2'] = (
promise.get() == threading.current_thread().getName()
)
do().get()
thread_1 = threading.Thread(target=task_1)
thread_1.start()
event_1.wait() # Wait for thread 1 to call `load`
thread_2 = threading.Thread(target=task_2)
thread_2.start()
for thread in (thread_1, thread_2):
thread.join()
assert assert_object['is_same_thread_1']
assert assert_object['is_same_thread_2']
promise-2.3.0/tests/utils.py 0000664 0000000 0000000 00000000262 13576352764 0016070 0 ustar 00root root 0000000 0000000 def assert_exception(exception, expected_exception_cls, expected_message):
assert isinstance(exception, expected_exception_cls)
assert str(exception) == expected_message