Codebase list python-txaio / 90c21cf
Merge pull request #2 from meejah/master Polish Tobias Oberstein 9 years ago
14 changed file(s) with 180 addition(s) and 110 deletion(s). Raw diff Collapse all Expand all
0 include LICENSE
1 include doc/*.rst
2 include doc/conf.py
3 include doc/Makefile
44 test:
55 tox
66
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
715 docs:
816 cd doc && make html
917
00 txaio: Twisted/asyncio helper
11 =============================
22
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.
55
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.
1111
1212 Note that, with this approach, user code runs under the native
1313 event loop of either Twisted or asyncio. This is different from
1414 attaching either one's event loop to the other using some event
1515 loop adapter.
1616
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 ---------------
1923
2024 Instead of directly importing, instantiating and using ``Deferred``
2125 (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
-43
examples/use_asyncio.py less more
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
-40
examples/use_twisted.py less more
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.
2020 import sys
2121 from setuptools import setup, find_packages
2222
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 """
2639
2740 setup (
28 name='taxio',
41 name='txaio',
2942 version=verstr,
3043 description='compatibility API between asyncio/Twisted/Trollius',
3144 long_description=docstr,
3245 author='Tavendo GmbH',
33 author_email='FIXME',
34 url='FIXME',
46 author_email='autobahnws@googlegroups.com',
47 url='https://github.com/tavendo/txaio',
3548 platforms=('Any'),
3649 install_requires=[
3750 'six'
3851 ],
3952 extras_require={
4053 '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
4356 'pep8>=1.6.2', # MIT
4457
4558 'Sphinx>=1.2.3', # BSD
4659 'alabaster>=0.6.3', # BSD
4760 ],
4861 'twisted': [
49 'twisted',
62 'twisted', # MIT
5063 ]
5164 },
5265 packages=['txaio'],
53 # include_package_data=True,
54 # data_files=[('.', ['LICENSE'])],
5566 zip_safe=False,
5667 # http://pypi.python.org/pypi?%3Aaction=list_classifiers
5768 #
0 import pytest
01 import txaio
12
23 from util import run_once
2324
2425 txaio.add_callbacks(f, cb, errback)
2526
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()
2666 run_once()
2767
2868 assert len(results) == 1
00 import pytest
11 import txaio
22 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
316
417
518 def test_call_later():
5858 assert 'it failed' in tb.getvalue()
5959 assert errors[0].getErrorMessage() == 'it failed'
6060 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
61100
62101
63102 def test_errback_reject_no_args():
1010 six
1111 mock
1212 pytest
13 pytest-cov
13 coverage
1414 py{26,27,34,py}-twisted: twisted
1515 py{26,27,34,py}-twisted: pytest-twisted
1616 py33-asyncio: asyncio
1818 py26-asyncio: trollius
1919 pypy-asyncio: trollius
2020 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
2124 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}
2526
2627 [testenv:flake8]
2728 deps =
7575 try:
7676 from txaio.aio import * # noqa
7777 using_asyncio = True
78 except ImportError:
78 except ImportError: # pragma: no cover
79 # pragma: no cover
7980 raise ImportError("Neither asyncio nor Twisted found.")
134134 elif isinstance(error, Exception):
135135 error = FailedFuture(type(error), error, None)
136136 else:
137 assert isinstance(error, IFailedFuture)
137 if not isinstance(error, IFailedFuture):
138 raise RuntimeError("reject requires an IFailedFuture or Exception")
138139 future.set_exception(error.value)
139140
140141
5959 elif isinstance(error, Exception):
6060 error = Failure(error)
6161 else:
62 assert isinstance(error, IFailedFuture)
62 if not isinstance(error, Failure):
63 raise RuntimeError("reject requires a Failure or Exception")
6364 future.errback(error)
6465
6566