pax_global_header00006660000000000000000000000064135763527640014534gustar00rootroot0000000000000052 comment=ad8ebe68cd9c9686793e3449457f175a0fe43226 promise-2.3.0/000077500000000000000000000000001357635276400132145ustar00rootroot00000000000000promise-2.3.0/.coveragerc000066400000000000000000000001131357635276400153300ustar00rootroot00000000000000[run] omit = promise/compat.py,promise/iterate_promise.py,promise/utils.py promise-2.3.0/.gitignore000066400000000000000000000015241357635276400152060ustar00rootroot00000000000000# 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.yml000066400000000000000000000006661357635276400153350ustar00rootroot00000000000000language: 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/LICENSE000066400000000000000000000020671357635276400142260ustar00rootroot00000000000000The 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.in000066400000000000000000000001011357635276400147420ustar00rootroot00000000000000global-exclude tests/* recursive-exclude tests * include LICENSE promise-2.3.0/README.md000066400000000000000000000131071357635276400144750ustar00rootroot00000000000000# 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.rst000066400000000000000000000137501357635276400147110ustar00rootroot00000000000000Promise ======= 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.py000066400000000000000000000017621357635276400154210ustar00rootroot00000000000000# 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/000077500000000000000000000000001357635276400146725ustar00rootroot00000000000000promise-2.3.0/promise/__init__.py000066400000000000000000000015511357635276400170050ustar00rootroot00000000000000from .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_.py000066400000000000000000000075671357635276400165370ustar00rootroot00000000000000# 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.py000066400000000000000000000015531357635276400165330ustar00rootroot00000000000000try: 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.py000066400000000000000000000251451357635276400173530ustar00rootroot00000000000000from 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.py000066400000000000000000000004501357635276400204360ustar00rootroot00000000000000# 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.py000066400000000000000000000671471357635276400167410ustar00rootroot00000000000000from 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.py000066400000000000000000000110461357635276400177570ustar00rootroot00000000000000from 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.typed000066400000000000000000000000001357635276400163570ustar00rootroot00000000000000promise-2.3.0/promise/pyutils/000077500000000000000000000000001357635276400164035ustar00rootroot00000000000000promise-2.3.0/promise/pyutils/__init__.py000066400000000000000000000000001357635276400205020ustar00rootroot00000000000000promise-2.3.0/promise/pyutils/version.py000066400000000000000000000047131357635276400204470ustar00rootroot00000000000000from __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/000077500000000000000000000000001357635276400170335ustar00rootroot00000000000000promise-2.3.0/promise/schedulers/__init__.py000066400000000000000000000000001357635276400211320ustar00rootroot00000000000000promise-2.3.0/promise/schedulers/asyncio.py000066400000000000000000000010001357635276400210410ustar00rootroot00000000000000from __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.py000066400000000000000000000007661357635276400207060ustar00rootroot00000000000000from __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.py000066400000000000000000000012271357635276400213450ustar00rootroot00000000000000from 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.py000066400000000000000000000006631357635276400206610ustar00rootroot00000000000000from 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.py000066400000000000000000000031301357635276400164010ustar00rootroot00000000000000import 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.cfg000066400000000000000000000001421357635276400150320ustar00rootroot00000000000000[flake8] exclude = tests,scripts,setup.py,docs,promise/utils.py,conftest.py max-line-length = 120 promise-2.3.0/setup.py000066400000000000000000000035531357635276400147340ustar00rootroot00000000000000import 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/000077500000000000000000000000001357635276400143565ustar00rootroot00000000000000promise-2.3.0/tests/__init__.py000066400000000000000000000000001357635276400164550ustar00rootroot00000000000000promise-2.3.0/tests/conftest.py000066400000000000000000000004141357635276400165540ustar00rootroot00000000000000from 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.py000066400000000000000000000011261357635276400177200ustar00rootroot00000000000000from 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.py000066400000000000000000000017241357635276400202330ustar00rootroot00000000000000from 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.py000066400000000000000000000054641357635276400177320ustar00rootroot00000000000000from 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.py000066400000000000000000000007451357635276400211560ustar00rootroot00000000000000from 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.py000066400000000000000000000246151357635276400200770ustar00rootroot00000000000000from 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.py000066400000000000000000000054361357635276400224170ustar00rootroot00000000000000from 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.py000066400000000000000000000027621357635276400213010ustar00rootroot00000000000000# 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.py000066400000000000000000000343561357635276400171250ustar00rootroot00000000000000# 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.py000066400000000000000000000071521357635276400173070ustar00rootroot00000000000000# 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.py000066400000000000000000000031551357635276400205040ustar00rootroot00000000000000from 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.py000066400000000000000000000322011357635276400167170ustar00rootroot00000000000000# 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.py000066400000000000000000000066011357635276400206140ustar00rootroot00000000000000from 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.py000066400000000000000000000002621357635276400160700ustar00rootroot00000000000000def assert_exception(exception, expected_exception_cls, expected_message): assert isinstance(exception, expected_exception_cls) assert str(exception) == expected_message