Merge pull request #2 from meejah/master
Polish
Tobias Oberstein
9 years ago
|
0 |
include LICENSE
|
|
1 |
include doc/*.rst
|
|
2 |
include doc/conf.py
|
|
3 |
include doc/Makefile
|
4 | 4 |
test:
|
5 | 5 |
tox
|
6 | 6 |
|
|
7 |
coverage:
|
|
8 |
-rm test/.coverage
|
|
9 |
# can we exclude just the flake8 environment?
|
|
10 |
tox -e py27-twisted,pypy-twisted,py34-twisted,py34-asyncio,py27-asyncio,pypy-asyncio
|
|
11 |
cd test && coverage combine
|
|
12 |
cd test && coverage html
|
|
13 |
cd test && coverage report --show-missing
|
|
14 |
|
7 | 15 |
docs:
|
8 | 16 |
cd doc && make html
|
9 | 17 |
|
0 | 0 |
txaio: Twisted/asyncio helper
|
1 | 1 |
=============================
|
2 | 2 |
|
3 | |
``txaio`` is a helper library for writing code that runs on both
|
4 | |
Twisted and asyncio.
|
|
3 |
``txaio`` is a helper library for writing code that runs unmodified on
|
|
4 |
both Twisted and asyncio.
|
5 | 5 |
|
6 | |
This is like `six`_, but for wrapping over differences between Twisted
|
7 | |
and asyncio so one can write code that runs unmodified on both (aka
|
8 | |
"source code compatibility").
|
9 | |
|
10 | |
.. _six: http://pythonhosted.org/six/
|
|
6 |
This is like `six <http://pythonhosted.org/six/>`_, but for wrapping
|
|
7 |
over differences between Twisted and asyncio so one can write code
|
|
8 |
that runs unmodified on both (*aka* "source code compatibility"). In
|
|
9 |
other words: your users can choose if they want asyncio **or** Twisted
|
|
10 |
as a dependency.
|
11 | 11 |
|
12 | 12 |
Note that, with this approach, user code runs under the native
|
13 | 13 |
event loop of either Twisted or asyncio. This is different from
|
14 | 14 |
attaching either one's event loop to the other using some event
|
15 | 15 |
loop adapter.
|
16 | 16 |
|
17 | |
Brief Summary
|
18 | |
-------------
|
|
17 |
**Support:** either asyncio *or* Twisted on any of Python 2.7, 3.4 and
|
|
18 |
PyPy.
|
|
19 |
|
|
20 |
|
|
21 |
How txaio Works
|
|
22 |
---------------
|
19 | 23 |
|
20 | 24 |
Instead of directly importing, instantiating and using ``Deferred``
|
21 | 25 |
(for Twisted) or ``Future`` (for asyncio) objects, ``txaio`` provides
|
|
0 |
from __future__ import print_function
|
|
1 |
import txaio
|
|
2 |
|
|
3 |
def cb(value):
|
|
4 |
print("Callback:", value)
|
|
5 |
return value # should always return input arg
|
|
6 |
|
|
7 |
def eb(fail):
|
|
8 |
# fail will implement txaio.IFailedPromise
|
|
9 |
print("Errback:", fail)
|
|
10 |
# fail.printTraceback()
|
|
11 |
return fail # should always return input arg
|
|
12 |
|
|
13 |
f0 = txaio.create_future()
|
|
14 |
f1 = txaio.create_future()
|
|
15 |
txaio.add_callbacks(f0, cb, eb)
|
|
16 |
txaio.add_callbacks(f1, cb, eb)
|
|
17 |
|
|
18 |
# ...
|
|
19 |
|
|
20 |
txaio.reject(f0, RuntimeError("it failed"))
|
|
21 |
# or can just "txaio.reject(f0)" if inside an except: block
|
|
22 |
txaio.resolve(f1, "The answer is: 42")
|
|
23 |
|
|
24 |
if txaio.using_asyncio:
|
|
25 |
# for twisted, we don't need to enter the event-loop for this
|
|
26 |
# simple example (since all results are already available), but
|
|
27 |
# you'd simply use reactor.run()/.stop() or task.react() as normal
|
|
28 |
import asyncio
|
|
29 |
asyncio.get_event_loop().run_until_complete(f1)
|
0 | |
from __future__ import print_function
|
1 | |
import txaio.aio as txaio
|
2 | |
|
3 | |
# note: all the code below is *identical* to the use_asyncio.py
|
4 | |
# example, except for the import line above and event-loop running
|
5 | |
|
6 | |
def cb(value):
|
7 | |
print("Callback:", value)
|
8 | |
|
9 | |
def eb(fail):
|
10 | |
# fail will implement txaio.IFailedPromise
|
11 | |
print("Errback:", fail)
|
12 | |
fail.printTraceback()
|
13 | |
|
14 | |
f = txaio.create_future()
|
15 | |
txaio.add_future_callbacks(f, cb, eb)
|
16 | |
|
17 | |
# ...other things happen...
|
18 | |
|
19 | |
def do_something():
|
20 | |
if False:
|
21 | |
return "at least it's something"
|
22 | |
raise RuntimeError("sadness")
|
23 | |
|
24 | |
try:
|
25 | |
answer = do_something()
|
26 | |
fail = None
|
27 | |
except Exception:
|
28 | |
fail = txaio.create_failure()
|
29 | |
|
30 | |
if fail:
|
31 | |
txaio.reject_future(f, fail)
|
32 | |
else:
|
33 | |
txaio.resolve_future(f, answer)
|
34 | |
|
35 | |
# Arrange for our event-loop to run.
|
36 | |
try:
|
37 | |
import asyncio
|
38 | |
except ImportError:
|
39 | |
import trollius as asyncio
|
40 | |
loop = asyncio.get_event_loop()
|
41 | |
loop.stop()
|
42 | |
loop.run_forever()
|
0 | |
from __future__ import print_function
|
1 | |
import txaio.tx as txaio
|
2 | |
|
3 | |
# note: all the code below is *identical* to the use_asyncio.py
|
4 | |
# example, except for the import line above and event-loop running
|
5 | |
|
6 | |
def cb(value):
|
7 | |
print("Callback:", value)
|
8 | |
|
9 | |
def eb(fail):
|
10 | |
# fail will implement txaio.IFailedPromise
|
11 | |
print("Errback:", fail)
|
12 | |
fail.printTraceback()
|
13 | |
|
14 | |
f = txaio.create_future()
|
15 | |
txaio.add_future_callbacks(f, cb, eb)
|
16 | |
|
17 | |
# ...other things happen...
|
18 | |
|
19 | |
def do_something():
|
20 | |
if False:
|
21 | |
return "at least it's something"
|
22 | |
raise RuntimeError("sadness")
|
23 | |
|
24 | |
try:
|
25 | |
answer = do_something()
|
26 | |
fail = None
|
27 | |
except Exception:
|
28 | |
fail = txaio.create_failure()
|
29 | |
|
30 | |
if fail:
|
31 | |
txaio.reject_future(f, fail)
|
32 | |
else:
|
33 | |
txaio.resolve_future(f, answer)
|
34 | |
|
35 | |
# note that in this simple example (because Twisted will resolve
|
36 | |
# callbacks immediately when results are already available) we don't
|
37 | |
# actually need to enter the event-loop. In a "real" program you'd
|
38 | |
# have to arrange to call ``reactor.run()`` or ``react()`` at some
|
39 | |
# point.
|
20 | 20 |
import sys
|
21 | 21 |
from setuptools import setup, find_packages
|
22 | 22 |
|
23 | |
# XXX FIXME
|
24 | |
verstr = '0.0.0'
|
25 | |
docstr = 'FIXME'
|
|
23 |
verstr = "1.0.0"
|
|
24 |
docstr = """
|
|
25 |
``txaio`` is a helper library for writing code that runs unmodified on
|
|
26 |
both Twisted and asyncio.
|
|
27 |
|
|
28 |
This is like `six <http://pythonhosted.org/six/>`_, but for wrapping
|
|
29 |
over differences between Twisted and asyncio so one can write code
|
|
30 |
that runs unmodified on both (*aka* "source code compatibility"). In
|
|
31 |
other words: your users can choose if they want asyncio **or** Twisted
|
|
32 |
as a dependency.
|
|
33 |
|
|
34 |
Note that, with this approach, user code runs under the native
|
|
35 |
event loop of either Twisted or asyncio. This is different from
|
|
36 |
attaching either one's event loop to the other using some event
|
|
37 |
loop adapter.
|
|
38 |
"""
|
26 | 39 |
|
27 | 40 |
setup (
|
28 | |
name='taxio',
|
|
41 |
name='txaio',
|
29 | 42 |
version=verstr,
|
30 | 43 |
description='compatibility API between asyncio/Twisted/Trollius',
|
31 | 44 |
long_description=docstr,
|
32 | 45 |
author='Tavendo GmbH',
|
33 | |
author_email='FIXME',
|
34 | |
url='FIXME',
|
|
46 |
author_email='autobahnws@googlegroups.com',
|
|
47 |
url='https://github.com/tavendo/txaio',
|
35 | 48 |
platforms=('Any'),
|
36 | 49 |
install_requires=[
|
37 | 50 |
'six'
|
38 | 51 |
],
|
39 | 52 |
extras_require={
|
40 | 53 |
'dev': [
|
41 | |
'pytest>=2.6.4', # FIXME
|
42 | |
'pytest-cov>=1.8.1', # FIXME
|
|
54 |
'pytest>=2.6.4', # MIT
|
|
55 |
'pytest-cov>=1.8.1', # MIT
|
43 | 56 |
'pep8>=1.6.2', # MIT
|
44 | 57 |
|
45 | 58 |
'Sphinx>=1.2.3', # BSD
|
46 | 59 |
'alabaster>=0.6.3', # BSD
|
47 | 60 |
],
|
48 | 61 |
'twisted': [
|
49 | |
'twisted',
|
|
62 |
'twisted', # MIT
|
50 | 63 |
]
|
51 | 64 |
},
|
52 | 65 |
packages=['txaio'],
|
53 | |
# include_package_data=True,
|
54 | |
# data_files=[('.', ['LICENSE'])],
|
55 | 66 |
zip_safe=False,
|
56 | 67 |
# http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
57 | 68 |
#
|
|
0 |
import pytest
|
0 | 1 |
import txaio
|
1 | 2 |
|
2 | 3 |
from util import run_once
|
|
23 | 24 |
|
24 | 25 |
txaio.add_callbacks(f, cb, errback)
|
25 | 26 |
|
|
27 |
run_once()
|
|
28 |
|
|
29 |
assert len(results) == 1
|
|
30 |
assert len(errors) == 0
|
|
31 |
assert results[0] == 42
|
|
32 |
assert calls[0] == ((1, 2, 3), dict(key='word'))
|
|
33 |
|
|
34 |
|
|
35 |
def test_as_future_coroutine():
|
|
36 |
'''
|
|
37 |
call a coroutine (asyncio)
|
|
38 |
'''
|
|
39 |
pytest.importorskip('asyncio')
|
|
40 |
# can import asyncio on python3.4, but might still be using
|
|
41 |
# twisted
|
|
42 |
if not txaio.using_asyncio:
|
|
43 |
return
|
|
44 |
|
|
45 |
errors = []
|
|
46 |
results = []
|
|
47 |
calls = []
|
|
48 |
|
|
49 |
from asyncio import coroutine
|
|
50 |
|
|
51 |
@coroutine
|
|
52 |
def method(*args, **kw):
|
|
53 |
calls.append((args, kw))
|
|
54 |
return 42
|
|
55 |
f = txaio.as_future(method, 1, 2, 3, key='word')
|
|
56 |
|
|
57 |
def cb(x):
|
|
58 |
results.append(x)
|
|
59 |
|
|
60 |
def errback(f):
|
|
61 |
errors.append(f)
|
|
62 |
|
|
63 |
txaio.add_callbacks(f, cb, errback)
|
|
64 |
|
|
65 |
run_once()
|
26 | 66 |
run_once()
|
27 | 67 |
|
28 | 68 |
assert len(results) == 1
|
0 | 0 |
import pytest
|
1 | 1 |
import txaio
|
2 | 2 |
from txaio.testutil import replace_loop
|
|
3 |
|
|
4 |
|
|
5 |
def test_default_reactor():
|
|
6 |
"""
|
|
7 |
run the code that defaults txaio.config.loop
|
|
8 |
"""
|
|
9 |
pytest.importorskip('twisted')
|
|
10 |
|
|
11 |
assert txaio.config.loop is None
|
|
12 |
txaio.call_later(1, lambda: None)
|
|
13 |
|
|
14 |
from twisted.internet import reactor
|
|
15 |
assert txaio.config.loop is reactor
|
3 | 16 |
|
4 | 17 |
|
5 | 18 |
def test_call_later():
|
58 | 58 |
assert 'it failed' in tb.getvalue()
|
59 | 59 |
assert errors[0].getErrorMessage() == 'it failed'
|
60 | 60 |
assert 'it failed' in str(errors[0])
|
|
61 |
|
|
62 |
|
|
63 |
def test_errback_plain_exception():
|
|
64 |
'''
|
|
65 |
reject a future with just an Exception
|
|
66 |
'''
|
|
67 |
f = txaio.create_future()
|
|
68 |
exception = RuntimeError("it failed")
|
|
69 |
errors = []
|
|
70 |
|
|
71 |
def err(f):
|
|
72 |
errors.append(f)
|
|
73 |
txaio.add_callbacks(f, None, err)
|
|
74 |
txaio.reject(f, exception)
|
|
75 |
|
|
76 |
run_once()
|
|
77 |
|
|
78 |
assert len(errors) == 1
|
|
79 |
assert isinstance(errors[0], txaio.IFailedFuture)
|
|
80 |
assert exception == errors[0].value
|
|
81 |
assert type(exception) == errors[0].type
|
|
82 |
tb = StringIO()
|
|
83 |
errors[0].printTraceback(file=tb)
|
|
84 |
assert 'RuntimeError' in tb.getvalue()
|
|
85 |
assert 'it failed' in tb.getvalue()
|
|
86 |
assert errors[0].getErrorMessage() == 'it failed'
|
|
87 |
assert 'it failed' in str(errors[0])
|
|
88 |
|
|
89 |
|
|
90 |
def test_errback_illegal_args():
|
|
91 |
'''
|
|
92 |
non-Exception/Failures should be rejected
|
|
93 |
'''
|
|
94 |
f = txaio.create_future()
|
|
95 |
try:
|
|
96 |
txaio.reject(f, object())
|
|
97 |
assert "should have raised exception."
|
|
98 |
except RuntimeError:
|
|
99 |
pass
|
61 | 100 |
|
62 | 101 |
|
63 | 102 |
def test_errback_reject_no_args():
|
10 | 10 |
six
|
11 | 11 |
mock
|
12 | 12 |
pytest
|
13 | |
pytest-cov
|
|
13 |
coverage
|
14 | 14 |
py{26,27,34,py}-twisted: twisted
|
15 | 15 |
py{26,27,34,py}-twisted: pytest-twisted
|
16 | 16 |
py33-asyncio: asyncio
|
|
18 | 18 |
py26-asyncio: trollius
|
19 | 19 |
pypy-asyncio: trollius
|
20 | 20 |
changedir=test
|
|
21 |
# this is so that our combined coverage files all have the same paths
|
|
22 |
# for the txaio code; thanks ionelmc on #python
|
|
23 |
usedevelop=true
|
21 | 24 |
commands =
|
22 | |
py.test -s --basetemp={envtmpdir} --cov txaio --cov-report term-missing
|
23 | |
# coverage report --show-missing
|
24 | |
# coverage html
|
|
25 |
coverage run -p --source=txaio {envbindir}/py.test -s --basetemp={envtmpdir}
|
25 | 26 |
|
26 | 27 |
[testenv:flake8]
|
27 | 28 |
deps =
|
75 | 75 |
try:
|
76 | 76 |
from txaio.aio import * # noqa
|
77 | 77 |
using_asyncio = True
|
78 | |
except ImportError:
|
|
78 |
except ImportError: # pragma: no cover
|
|
79 |
# pragma: no cover
|
79 | 80 |
raise ImportError("Neither asyncio nor Twisted found.")
|
134 | 134 |
elif isinstance(error, Exception):
|
135 | 135 |
error = FailedFuture(type(error), error, None)
|
136 | 136 |
else:
|
137 | |
assert isinstance(error, IFailedFuture)
|
|
137 |
if not isinstance(error, IFailedFuture):
|
|
138 |
raise RuntimeError("reject requires an IFailedFuture or Exception")
|
138 | 139 |
future.set_exception(error.value)
|
139 | 140 |
|
140 | 141 |
|
59 | 59 |
elif isinstance(error, Exception):
|
60 | 60 |
error = Failure(error)
|
61 | 61 |
else:
|
62 | |
assert isinstance(error, IFailedFuture)
|
|
62 |
if not isinstance(error, Failure):
|
|
63 |
raise RuntimeError("reject requires a Failure or Exception")
|
63 | 64 |
future.errback(error)
|
64 | 65 |
|
65 | 66 |
|