主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

スポンサーサイト

                
tags:
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Python: tornadoとMongoDB(motor)でブログを作るための一歩

                
 前回にPythonのWebフレームワークあさりをして、tornadoを使ってみようかと考えた。
Pythonの軽量Webフレームワークあさり

 探してみたら電子書籍のみだけど、オライリーから書籍も出ている。
http://www.oreilly.co.jp/books/9784873115764/

 ノンブロッキングという特徴がデータベースアクセスの時などにコールバック関数を必要とし、コードを特異なものにしそうなのでちょいと試してみる。とりあえずブログの基礎機能部分を作ってみる。記事をポストするためのフォームを含んだHTMLをクライアントからのGETリクエストに返し、フォーム入力内容(記事)をPOSTで受け取ってデータベースへ書き込みをする。データベースに書き込まれた記事を数件読み出し、クライアントにブログページとして成形して返す。この機能をまずは作る。

OS: Windows 7
Python 2.7,
Tornado 3.2,
MongoDB 2.4.9,
motor -tornadoで使えるMongoDBドライバ
motorはこれを書いている時点で1.2がstableで2.0がlatestだった。2.0でだいぶ便利な書き方ができるようになっていたので、githubからlatestを選んだ(参考)。

 前述の機能を実装したコード。
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import datetime
import os
from math import ceil

import tornado.ioloop
import tornado.web
import tornado.gen
import tornado.concurrent
from tornado.template import Template
import motor


class PageHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
per_page = 3
p = self.get_query_argument("p", default="0")
skip = int(p) * per_page
docs = yield db.blog.find(skip=skip).sort("date" ,direction=-1).to_list(length=per_page)
num = yield db.blog.find().count()
if num:
html = """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>articles</title>
</head>
<body>
<h1>my blog</h1>
{% for article in articles %}
<div>
<h2>{{ escape(article["title"]) }}</h2><br>
date: {{ article["date"].strftime("%Y-%m-%d") }}, author: {{ article["author"] }}<br><br>
{{ escape(article["body"]) }}<br><br>
</div>
{% end %}
{% for page in pages %}
<a href="/pa?p={{ page }}">{{ page }}</a>
{% end %}
</body>
</html>
"""
pages = range(int(ceil(float(num) / per_page)))
t = Template(html)
rendered = t.generate(articles = docs, pages = pages)
self.write(rendered)
self.finish()
else:
raise tornado.web.HTTPError(404)


class PostHandler(tornado.web.RequestHandler):
def get(self):
html = """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>post</title>
</head>
<body>
<form method="post" action="/po"><br>
author:<input name="author" /><br>
title:<input name="title" /><br>
body:<textarea name="body"></textarea><br>
tags:<input name="tags" /><br>
<input type="submit" value="post" /><br>
</form>
</body>
</html>
"""
self.write(html)

@tornado.gen.coroutine # @gen.engine: obsolete?
def post(self):
data = {"title":self.get_argument("title"),
"body":self.get_argument("body"),
"date":datetime.datetime.now(),
"author":self.get_argument("author"),
"tags":self.get_argument("tags").split(" ")
}
try:
result = yield db.blog.insert(data)
self.redirect("/pa")
except Exception, err:
raise tornado.web.HTTPError(500)


class FooHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
db.blog.find().count(callback=self._count1)
db.blog.find({"author":"John"}).count(callback=self._count2)

def _count1(self, result, err):
if err:
raise tornado.web.HTTPError(500, err)
else:
self.write("articles: " + str(result) + "<br>")
##self.finish()

def _count2(self, result, err):
if err:
raise tornado.web.HTTPError(500)
else:
self.write("John's articles: " + str(result))
self.finish()

class NewFooHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
f1 = db.blog.find().count()
f2 = db.blog.find({"author":"John"}).count()
try:
result1, result2 = yield [f1, f2]
except Exception, err:
raise tornado.web.HTTPError(500)
self.write("articles: " + str(result1) + "<br>")
self.write("John's articles: " + str(result2))

db = motor.MotorClient("localhost", 27017).test_db

if __name__ == "__main__":
application = tornado.web.Application([(r"/pa", PageHandler),
(r"/po", PostHandler),
(r"/foo", FooHandler),
(r"/new-foo", NewFooHandler),
], debug=True)
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()

MongoDBを立ち上げてから"python [filename].py"で実行できる。

 motorのバージョンが2.0になる前までは、データベースから引き出すデータを扱うにはコールバック関数を使って書かなければならなかった。だからデータベースへ問い合わせを複数件おこないたい場合などは、コールバック関数が乱立してコードが見た目としてちょっと特殊になった。/fooへのリクエストにレスポンスを返すクラスFooHandlerでそれをやっているが、tornadoとコードが似ると言われるwebpyやwebappを使ってきた人でもなんだこりゃだろう。
 motorのバージョン2.0からはgen.coroutineとyieldを組み合わせて使うことによってだいぶスッキリと書けるようになった。
class FooHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
db.blog.find().count(callback=self._count1)
db.blog.find({"author":"John"}).count(callback=self._count2)

def _count1(self, result, err):
if err:
raise tornado.web.HTTPError(500, err)
else:
self.write("articles: " + str(result) + "<br>")
##self.finish()

def _count2(self, result, err):
if err:
raise tornado.web.HTTPError(500, err)
else:
self.write("John's articles: " + str(result))
self.finish()

class NewFooHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
f1 = db.blog.find().count()
f2 = db.blog.find({"author":"John"}).count()
try:
result1, result2 = yield [f1, f2]
except Exception, err:
raise tornado.web.HTTPError(500)
self.write("articles: " + str(result1) + "<br>")
self.write("John's articles: " + str(result2))


 これを応用して記事のポストや、記事データをHTMLに成形して返せるようになる。tornadoにはテンプレートもあるし、他にも役立つツールがそろっている。上に載せたコードはブログと呼ぶにはまだ機能が全然足りていない。個人認証が必要だし、認証管理でセキュアにクッキーを扱う必要がある。そのあたりもすぐに使えるようになっている。
tornadoドキュメント - 概要

 tornado。便利で面白そうなフレームワークのようだ。
            

コメントの投稿

非公開コメント

プロフィール

Matoba

Author:Matoba

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。