Skip to content

Commit

Permalink
Better example for Tornado; fix #25; require task context for transac…
Browse files Browse the repository at this point in the history
…tion run; bump version
  • Loading branch information
rudyryk committed May 2, 2016
1 parent d8dda8b commit 6a11dd4
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 75 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.5.2

- Fix #25: starting transaction before connecting raise `AttributeError` exception about `_task_data`
- **Require** transactions to be run within task context

## 0.5.1

- Fix: #23, running not in the context of a task
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ Contents

peewee_async/api
peewee_async/api_older
peewee_async/tornado
peewee_async/examples

Indices and tables
Expand Down
68 changes: 3 additions & 65 deletions docs/peewee_async/examples.rst
Original file line number Diff line number Diff line change
@@ -1,69 +1,7 @@
Usage examples
==============

Using with Tornado
------------------

.. code-block:: python
import tornado.gen
import tornado.web
from tornado.platform.asyncio import AsyncIOMainLoop
import peewee
import asyncio
import peewee_async
# Set up asincio loop for Tornado
AsyncIOMainLoop().install()
loop = asyncio.get_event_loop()
# Create application
application = tornado.web.Application(debug=True)
application.listen(port=8888)
# Set up database connection
database = peewee_async.PooledPostgresqlDatabase('test')
application.db = database
# Define model, handler and run application:
class TestNameModel(peewee.Model):
name = peewee.CharField()
class Meta:
database = database
class TestHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def post(self):
name = self.get_argument('name')
obj = yield from peewee_async.create_object(TestNameModel, name=name)
self.write({'id': obj.id, 'name': obj.name})
@tornado.gen.coroutine
def get(self):
obj_id = self.get_argument('id')
try:
obj = yield from peewee_async.get_object(TestNameModel, TestNameModel.id == obj_id)
self.write({'id': obj.id, 'name': obj.name})
except TestNameModel.DoesNotExist:
raise tornado.web.HTTPError(404, "Object not found!")
application.add_handlers('', [
(r"/test", TestHandler)
])
# Create database table
TestNameModel.create_table(True)
database.close()
# Set up async connection and run application server
loop.run_until_complete(application.db.connect_async())
loop.run_forever()
More examples
=============

**TODO:** update examples to high-level API.

Using both sync and async calls
-------------------------------
Expand Down
137 changes: 137 additions & 0 deletions docs/peewee_async/tornado.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
Using async peewee with Tornado
===============================

`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!

.. _Tornado: http://www.tornadoweb.org

The complete working example is provided below. And here are some general notes:

1. **Be aware of current asyncio event loop!**

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.

2. Tornado request handlers **does not** start asyncio tasks by default.

The ``CreateHandler`` demostrates that, ``current_task()`` returns ``None`` until taks is run explicitly.

3. Transactions **must** run within task context.

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()``.

**Also note:** if you spawn an extra task during transaction, it will run outside of transaction.

.. code-block:: python
import tornado.web
import logging
import peewee
import asyncio
import peewee_async
# Set up database and manager
database = peewee_async.PooledPostgresqlDatabase('test')
# Define model
class TestNameModel(peewee.Model):
name = peewee.CharField()
class Meta:
database = database
def __str__(self):
return self.name
# Create table, add some instances
TestNameModel.create_table(True)
TestNameModel.get_or_create(id=1, defaults={'name': "TestNameModel id=1"})
TestNameModel.get_or_create(id=2, defaults={'name': "TestNameModel id=2"})
TestNameModel.get_or_create(id=3, defaults={'name': "TestNameModel id=3"})
database.close()
# Set up Tornado application on asyncio
from tornado.platform.asyncio import AsyncIOMainLoop
AsyncIOMainLoop().install()
app = tornado.web.Application(debug=True)
app.listen(port=8888)
app.objects = peewee_async.Manager(database)
# Add handlers
class RootHandler(tornado.web.RequestHandler):
"""Accepts GET and POST methods.
POST: create new instance, `name` argument is required
GET: get instance by id, `id` argument is required
"""
async def post(self):
name = self.get_argument('name')
obj = await self.application.objects.create(TestNameModel, name=name)
self.write({
'id': obj.id,
'name': obj.name
})
async def get(self):
obj_id = self.get_argument('id', None)
if not obj_id:
self.write("Please provide the 'id' query argument, i.e. ?id=1")
return
try:
obj = await self.application.objects.get(TestNameModel, id=obj_id)
self.write({
'id': obj.id,
'name': obj.name,
})
except TestNameModel.DoesNotExist:
raise tornado.web.HTTPError(404, "Object not found!")
class CreateHandler(tornado.web.RequestHandler):
async def get(self):
loop = asyncio.get_event_loop()
task1 = asyncio.Task.current_task()
task2 = loop.create_task(self.get_or_create())
obj = await task2
self.write({
'task1': task1 and id(task1),
'task2': task2 and id(task2),
'obj': str(obj),
'text': "'task1' should be null, "
"'task2' should be not null, "
"'obj' should be newly created object",
})
async def get_or_create(self):
async with self.application.objects.atomic():
obj, created = await self.application.objects.get_or_create(
TestNameModel, id=100,
defaults={'name': "TestNameModel id=100"})
return obj
app.add_handlers('', [
(r"/", RootHandler),
(r"/create/", CreateHandler),
])
# Setup verbose logging
log = logging.getLogger('')
log.addHandler(logging.StreamHandler())
log.setLevel(logging.DEBUG)
# Run loop
print("""Run application server http://127.0.0.1:8888
Try GET urls:
http://127.0.0.1:8888?id=1
http://127.0.0.1:8888?id=2
http://127.0.0.1:8888?id=3
Try POST with name=<some text> data:
http://127.0.0.1:8888
^C to stop server""")
loop = asyncio.get_event_loop()
try:
loop.run_forever()
except KeyboardInterrupt:
print(" server stopped")
126 changes: 126 additions & 0 deletions examples/tornado_sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""
Usage example for `peewee-async`_ with `Tornado`_ web framewotk.
Asynchronous interface for `peewee`_ ORM powered by `asyncio`_:
https://github.com/05bit/peewee-async
.. _peewee-async: https://github.com/05bit/peewee-async
.. _Tornado: http://www.tornadoweb.org
Licensed under The MIT License (MIT)
Copyright (c) 2016, Alexey Kinëv <rudy@05bit.com>
"""
import tornado.web
import logging
import peewee
import asyncio
import peewee_async

# Set up database and manager
database = peewee_async.PooledPostgresqlDatabase('test')

# Define model
class TestNameModel(peewee.Model):
name = peewee.CharField()
class Meta:
database = database

def __str__(self):
return self.name

# Create table, add some instances
TestNameModel.create_table(True)
TestNameModel.get_or_create(id=1, defaults={'name': "TestNameModel id=1"})
TestNameModel.get_or_create(id=2, defaults={'name': "TestNameModel id=2"})
TestNameModel.get_or_create(id=3, defaults={'name': "TestNameModel id=3"})
database.close()

# Set up Tornado application on asyncio
from tornado.platform.asyncio import AsyncIOMainLoop
AsyncIOMainLoop().install()
app = tornado.web.Application(debug=True)
app.listen(port=8888)
app.objects = peewee_async.Manager(database)

# Add handlers
class RootHandler(tornado.web.RequestHandler):
"""Accepts GET and POST methods.
POST: create new instance, `name` argument is required
GET: get instance by id, `id` argument is required
"""
async def post(self):
name = self.get_argument('name')
obj = await self.application.objects.create(TestNameModel, name=name)
self.write({
'id': obj.id,
'name': obj.name
})

async def get(self):
obj_id = self.get_argument('id', None)

if not obj_id:
self.write("Please provide the 'id' query argument, i.e. ?id=1")
return

try:
obj = await self.application.objects.get(TestNameModel, id=obj_id)
self.write({
'id': obj.id,
'name': obj.name,
})
except TestNameModel.DoesNotExist:
raise tornado.web.HTTPError(404, "Object not found!")

class CreateHandler(tornado.web.RequestHandler):
async def get(self):
loop = asyncio.get_event_loop()
task1 = asyncio.Task.current_task()
task2 = loop.create_task(self.get_or_create())
obj = await task2
self.write({
'task1': task1 and id(task1),
'task2': task2 and id(task2),
'obj': str(obj),
'text': "'task1' should be null, "
"'task2' should be not null, "
"'obj' should be newly created object",
})

async def get_or_create(self):
async with self.application.objects.atomic():
obj, created = await self.application.objects.get_or_create(
TestNameModel, id=100,
defaults={'name': "TestNameModel id=100"})
return obj

app.add_handlers('', [
(r"/", RootHandler),
(r"/create/", CreateHandler),
])

# Setup verbose logging
log = logging.getLogger('')
log.addHandler(logging.StreamHandler())
log.setLevel(logging.DEBUG)

# Run loop
print("""Run application server http://127.0.0.1:8888
Try GET urls:
http://127.0.0.1:8888?id=1
http://127.0.0.1:8888?id=2
http://127.0.0.1:8888?id=3
Try POST with name=<some text> data:
http://127.0.0.1:8888
^C to stop server""")
loop = asyncio.get_event_loop()
try:
loop.run_forever()
except KeyboardInterrupt:
print(" server stopped")

0 comments on commit 6a11dd4

Please sign in to comment.