Skip to content

Commit 6a11dd4

Browse files
committed
Better example for Tornado; fix pytest-dev#25; require task context for transaction run; bump version
1 parent d8dda8b commit 6a11dd4

File tree

6 files changed

+282
-75
lines changed

6 files changed

+282
-75
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 0.5.2
4+
5+
- Fix #25: starting transaction before connecting raise `AttributeError` exception about `_task_data`
6+
- **Require** transactions to be run within task context
7+
38
## 0.5.1
49

510
- Fix: #23, running not in the context of a task

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ Contents
135135

136136
peewee_async/api
137137
peewee_async/api_older
138+
peewee_async/tornado
138139
peewee_async/examples
139140

140141
Indices and tables

docs/peewee_async/examples.rst

Lines changed: 3 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,7 @@
1-
Usage examples
2-
==============
3-
4-
Using with Tornado
5-
------------------
6-
7-
.. code-block:: python
8-
9-
import tornado.gen
10-
import tornado.web
11-
from tornado.platform.asyncio import AsyncIOMainLoop
12-
import peewee
13-
import asyncio
14-
import peewee_async
15-
16-
# Set up asincio loop for Tornado
17-
AsyncIOMainLoop().install()
18-
loop = asyncio.get_event_loop()
19-
20-
# Create application
21-
application = tornado.web.Application(debug=True)
22-
application.listen(port=8888)
23-
24-
# Set up database connection
25-
database = peewee_async.PooledPostgresqlDatabase('test')
26-
application.db = database
27-
28-
# Define model, handler and run application:
29-
30-
31-
class TestNameModel(peewee.Model):
32-
name = peewee.CharField()
33-
class Meta:
34-
database = database
35-
36-
37-
class TestHandler(tornado.web.RequestHandler):
38-
@tornado.gen.coroutine
39-
def post(self):
40-
name = self.get_argument('name')
41-
obj = yield from peewee_async.create_object(TestNameModel, name=name)
42-
self.write({'id': obj.id, 'name': obj.name})
43-
44-
@tornado.gen.coroutine
45-
def get(self):
46-
obj_id = self.get_argument('id')
47-
try:
48-
obj = yield from peewee_async.get_object(TestNameModel, TestNameModel.id == obj_id)
49-
self.write({'id': obj.id, 'name': obj.name})
50-
except TestNameModel.DoesNotExist:
51-
raise tornado.web.HTTPError(404, "Object not found!")
52-
53-
54-
application.add_handlers('', [
55-
(r"/test", TestHandler)
56-
])
57-
58-
59-
# Create database table
60-
TestNameModel.create_table(True)
61-
database.close()
62-
63-
# Set up async connection and run application server
64-
loop.run_until_complete(application.db.connect_async())
65-
loop.run_forever()
1+
More examples
2+
=============
663

4+
**TODO:** update examples to high-level API.
675

686
Using both sync and async calls
697
-------------------------------

docs/peewee_async/tornado.rst

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
Using async peewee with Tornado
2+
===============================
3+
4+
`Tornado`_ is a mature and powerful asynchronous web framework. It provides its own event loop, but there's an option to run Tornado on asyncio event loop. And that's exactly what we need!
5+
6+
.. _Tornado: http://www.tornadoweb.org
7+
8+
The complete working example is provided below. And here are some general notes:
9+
10+
1. **Be aware of current asyncio event loop!**
11+
12+
In the provided example we use the default event loop everywhere, and that's OK. But if you see your application got silently stuck, that's most probably that some task is started on different loop and will never complete as long as that loop is not running.
13+
14+
2. Tornado request handlers **does not** start asyncio tasks by default.
15+
16+
The ``CreateHandler`` demostrates that, ``current_task()`` returns ``None`` until taks is run explicitly.
17+
18+
3. Transactions **must** run within task context.
19+
20+
All transaction operations have to be done within task. So if you need to run a transaction from Tornado handler, you have to wrap your call into task with ``create_task()`` or ``ensure_future()``.
21+
22+
**Also note:** if you spawn an extra task during transaction, it will run outside of transaction.
23+
24+
.. code-block:: python
25+
26+
import tornado.web
27+
import logging
28+
import peewee
29+
import asyncio
30+
import peewee_async
31+
32+
# Set up database and manager
33+
database = peewee_async.PooledPostgresqlDatabase('test')
34+
35+
# Define model
36+
class TestNameModel(peewee.Model):
37+
name = peewee.CharField()
38+
class Meta:
39+
database = database
40+
41+
def __str__(self):
42+
return self.name
43+
44+
# Create table, add some instances
45+
TestNameModel.create_table(True)
46+
TestNameModel.get_or_create(id=1, defaults={'name': "TestNameModel id=1"})
47+
TestNameModel.get_or_create(id=2, defaults={'name': "TestNameModel id=2"})
48+
TestNameModel.get_or_create(id=3, defaults={'name': "TestNameModel id=3"})
49+
database.close()
50+
51+
# Set up Tornado application on asyncio
52+
from tornado.platform.asyncio import AsyncIOMainLoop
53+
AsyncIOMainLoop().install()
54+
app = tornado.web.Application(debug=True)
55+
app.listen(port=8888)
56+
app.objects = peewee_async.Manager(database)
57+
58+
# Add handlers
59+
class RootHandler(tornado.web.RequestHandler):
60+
"""Accepts GET and POST methods.
61+
62+
POST: create new instance, `name` argument is required
63+
GET: get instance by id, `id` argument is required
64+
"""
65+
async def post(self):
66+
name = self.get_argument('name')
67+
obj = await self.application.objects.create(TestNameModel, name=name)
68+
self.write({
69+
'id': obj.id,
70+
'name': obj.name
71+
})
72+
73+
async def get(self):
74+
obj_id = self.get_argument('id', None)
75+
76+
if not obj_id:
77+
self.write("Please provide the 'id' query argument, i.e. ?id=1")
78+
return
79+
80+
try:
81+
obj = await self.application.objects.get(TestNameModel, id=obj_id)
82+
self.write({
83+
'id': obj.id,
84+
'name': obj.name,
85+
})
86+
except TestNameModel.DoesNotExist:
87+
raise tornado.web.HTTPError(404, "Object not found!")
88+
89+
class CreateHandler(tornado.web.RequestHandler):
90+
async def get(self):
91+
loop = asyncio.get_event_loop()
92+
task1 = asyncio.Task.current_task()
93+
task2 = loop.create_task(self.get_or_create())
94+
obj = await task2
95+
self.write({
96+
'task1': task1 and id(task1),
97+
'task2': task2 and id(task2),
98+
'obj': str(obj),
99+
'text': "'task1' should be null, "
100+
"'task2' should be not null, "
101+
"'obj' should be newly created object",
102+
})
103+
104+
async def get_or_create(self):
105+
async with self.application.objects.atomic():
106+
obj, created = await self.application.objects.get_or_create(
107+
TestNameModel, id=100,
108+
defaults={'name': "TestNameModel id=100"})
109+
return obj
110+
111+
app.add_handlers('', [
112+
(r"/", RootHandler),
113+
(r"/create/", CreateHandler),
114+
])
115+
116+
# Setup verbose logging
117+
log = logging.getLogger('')
118+
log.addHandler(logging.StreamHandler())
119+
log.setLevel(logging.DEBUG)
120+
121+
# Run loop
122+
print("""Run application server http://127.0.0.1:8888
123+
124+
Try GET urls:
125+
http://127.0.0.1:8888?id=1
126+
http://127.0.0.1:8888?id=2
127+
http://127.0.0.1:8888?id=3
128+
129+
Try POST with name=<some text> data:
130+
http://127.0.0.1:8888
131+
132+
^C to stop server""")
133+
loop = asyncio.get_event_loop()
134+
try:
135+
loop.run_forever()
136+
except KeyboardInterrupt:
137+
print(" server stopped")

examples/tornado_sample.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""
2+
Usage example for `peewee-async`_ with `Tornado`_ web framewotk.
3+
4+
Asynchronous interface for `peewee`_ ORM powered by `asyncio`_:
5+
https://github.com/05bit/peewee-async
6+
7+
.. _peewee-async: https://github.com/05bit/peewee-async
8+
.. _Tornado: http://www.tornadoweb.org
9+
10+
Licensed under The MIT License (MIT)
11+
12+
Copyright (c) 2016, Alexey Kinëv <[email protected]>
13+
14+
"""
15+
import tornado.web
16+
import logging
17+
import peewee
18+
import asyncio
19+
import peewee_async
20+
21+
# Set up database and manager
22+
database = peewee_async.PooledPostgresqlDatabase('test')
23+
24+
# Define model
25+
class TestNameModel(peewee.Model):
26+
name = peewee.CharField()
27+
class Meta:
28+
database = database
29+
30+
def __str__(self):
31+
return self.name
32+
33+
# Create table, add some instances
34+
TestNameModel.create_table(True)
35+
TestNameModel.get_or_create(id=1, defaults={'name': "TestNameModel id=1"})
36+
TestNameModel.get_or_create(id=2, defaults={'name': "TestNameModel id=2"})
37+
TestNameModel.get_or_create(id=3, defaults={'name': "TestNameModel id=3"})
38+
database.close()
39+
40+
# Set up Tornado application on asyncio
41+
from tornado.platform.asyncio import AsyncIOMainLoop
42+
AsyncIOMainLoop().install()
43+
app = tornado.web.Application(debug=True)
44+
app.listen(port=8888)
45+
app.objects = peewee_async.Manager(database)
46+
47+
# Add handlers
48+
class RootHandler(tornado.web.RequestHandler):
49+
"""Accepts GET and POST methods.
50+
51+
POST: create new instance, `name` argument is required
52+
GET: get instance by id, `id` argument is required
53+
"""
54+
async def post(self):
55+
name = self.get_argument('name')
56+
obj = await self.application.objects.create(TestNameModel, name=name)
57+
self.write({
58+
'id': obj.id,
59+
'name': obj.name
60+
})
61+
62+
async def get(self):
63+
obj_id = self.get_argument('id', None)
64+
65+
if not obj_id:
66+
self.write("Please provide the 'id' query argument, i.e. ?id=1")
67+
return
68+
69+
try:
70+
obj = await self.application.objects.get(TestNameModel, id=obj_id)
71+
self.write({
72+
'id': obj.id,
73+
'name': obj.name,
74+
})
75+
except TestNameModel.DoesNotExist:
76+
raise tornado.web.HTTPError(404, "Object not found!")
77+
78+
class CreateHandler(tornado.web.RequestHandler):
79+
async def get(self):
80+
loop = asyncio.get_event_loop()
81+
task1 = asyncio.Task.current_task()
82+
task2 = loop.create_task(self.get_or_create())
83+
obj = await task2
84+
self.write({
85+
'task1': task1 and id(task1),
86+
'task2': task2 and id(task2),
87+
'obj': str(obj),
88+
'text': "'task1' should be null, "
89+
"'task2' should be not null, "
90+
"'obj' should be newly created object",
91+
})
92+
93+
async def get_or_create(self):
94+
async with self.application.objects.atomic():
95+
obj, created = await self.application.objects.get_or_create(
96+
TestNameModel, id=100,
97+
defaults={'name': "TestNameModel id=100"})
98+
return obj
99+
100+
app.add_handlers('', [
101+
(r"/", RootHandler),
102+
(r"/create/", CreateHandler),
103+
])
104+
105+
# Setup verbose logging
106+
log = logging.getLogger('')
107+
log.addHandler(logging.StreamHandler())
108+
log.setLevel(logging.DEBUG)
109+
110+
# Run loop
111+
print("""Run application server http://127.0.0.1:8888
112+
113+
Try GET urls:
114+
http://127.0.0.1:8888?id=1
115+
http://127.0.0.1:8888?id=2
116+
http://127.0.0.1:8888?id=3
117+
118+
Try POST with name=<some text> data:
119+
http://127.0.0.1:8888
120+
121+
^C to stop server""")
122+
loop = asyncio.get_event_loop()
123+
try:
124+
loop.run_forever()
125+
except KeyboardInterrupt:
126+
print(" server stopped")

0 commit comments

Comments
 (0)