Discreet Blog 10.7.2006

Daily Link 2006-07-10

SQLite 3 使ってみました

今週のアスパラガス フォームの POST データをデータベースにストアする例。最近流行の SQLite を使っ てみました。もちろん DB_DIVER と DB_ARGS を変更すれば、他のデータベー スでも動きます。

データベースのクエリ実行している render() メソッドで、直接 Response オ ブジェクトを返さずに Deferred オブジェクトを返してるところがポイント。

# A rewritten version of databaseblog.py
# from "Twisted Network Programming Essentials" Example 4-6.
http://www.oreilly.com/catalog/twistedadn/

from twisted.enterprise import adbapi, util as dbutil
from twisted.web2 import http, http_headers, responsecode, resource, server
from twisted.web2.channel import HTTPFactory
from twisted.web2.error import ERROR_MESSAGES

DB_DRIVER = "pysqlite2.dbapi2"
DB_ARGS = {"database": "microblog"}
ENCODING = "utf-8"
CTYPE_HTML ={
    
'content-type': http_headers.MimeType('text', 'html',
                                          
{'charset': '%s' % ENCODING})}
def uEncode(data):
    
if isinstance(data, unicode):
        
return data.encode(ENCODING)
    
else:
        
return data

class FoundResponse(http.StatusResponse):
    
def __init__(self, location):
        
super(FoundResponse, self).__init__(
            
responsecode.FOUND,
            
ERROR_MESSAGES[responsecode.FOUND] % {"location": location})
        
self.headers.setHeader("location", location)

class NewPage(resource.Resource):
    
def render(self, request):
        
html = """
        <html>
        <head><title>New Post<title></head>
        <body>
          <h1>New Post</h1>
          <form action='save' method='post'>
          Title: <input type='text' name='title' /><br />
          Body: <br />
          <textarea cols='70' name='body'></textarea><br />
          <input type='submit' value='Save' />
          </form>
        </body>
        </html>
        """

        
return http.Response(responsecode.OK, CTYPE_HTML, html)

class SavePage(resource.PostableResource):
    
def __init__(self, dbConnection):
        
super(SavePage, self).__init__()
        
self.db = dbConnection

    
def render(self, request):
        
title = request.args['title'][0]
        
body = request.args['body'][0]
        
query = "insert into posts (title, body) values (%s, %s)" % (
            
dbutil.quote(title, "text"),
            
dbutil.quote(body, "text"))
        
d = self.db.runQuery(query)
        
d.addCallback(self._saved)
        
d.addErrback(self._saveFailed)
        
return d

    
def _saved(self, result):
        
return FoundResponse("/")

    
def _saveFailed(self, failure):
        
return http.StatusResponse(
            
responsecode.INTERNAL_SERVER_ERROR,
            
"Error saving record: %s" % failure.getErrorMessage())

class HomePage(resource.Resource):
    
addSlash = True
    
child_new = NewPage()

    
def __init__(self, dbConnection):
        
super(HomePage, self).__init__()
        
self.db = dbConnection
        
self.child_save = SavePage(dbConnection)

    
def render(self, request):
        
query = "select title, body from posts order by post_id desc"
        
d = self.db.runQuery(query)
        
d.addCallback(self._gotPosts)
        
d.addErrback(self._dbError)
        
return d

    
def _gotPosts(self, results):
        
html = """
        <html>
        <head><title>MicroBlog</title></head>
        <body>
          <h1>MicroBlog</h1>
          <i>Like a blog, but less useful</i>
          <p><a href='/new'>New Post</a></p>
         """

        
for title, body in results:
            
html += "<h2>%s</h2>" % uEncode(title)
            
html += uEncode(body)
        
html += """
        </body>
        </html>
        """

        
return http.Response(responsecode.OK, CTYPE_HTML, html)

    
def _dbError(self, failure):
        
return http.StatusResponse(
            
responsecode.INTERNAL_SERVER_ERROR,
            
"Error fetching posts: %s" % failure.getErrorMessage())

if __name__ == "__main__":
    
from twisted.internet import reactor
    
dbConnection = adbapi.ConnectionPool(DB_DRIVER, **DB_ARGS)
    
site = server.Site(HomePage(dbConnection))
    
reactor.listenTCP(8000, HTTPFactory(site))
    
reactor.run()

データベーステーブルはこんなの。

create table posts (post_id int primary key,
                    title str not null,
                    body);

SQLite 3 って入力テキストは UTF-8 決め打ちみたいね。select 結果が Unicode で返ってくるので、エンコード処理 uEncode() を付け加えています。

FoundResponse というのはリダイレクト用の Response サブクラスで 302 Found レスポンスを返します。リダイレクト用のクラスはほかに twisted.web2.http.RedirectResponse というのがあるけど、こっちは 301 Moved Permanently を返すやつです。条件によってリダイレクト先が変わる場面では 302 を使います。

posted at 18:40:16    #

ストリームへの直接書き込み

twisted.web の Request オブジェクトにはレスポンスを直接書き込める write() メソッドというのがあったのだけど、Web2 では ProducerStream を 使ってストリームの書き込みをおこないます。

以下は ProducerStream を使って ColorRoot の render() を書き直した例。

def render(self, request):
    
from twisted.web2 import stream
    
s = stream.ProducerStream()
    
s.write("""
    <html>
    <head>
      <title>Colors</title>
      <link type='text/css' href='/styles.css' rel='Stylesheet' />
    </head>
    <body>
    <h1>Colors</h1>
    To see a clor, enter a url like
    <a href='/color/ff0000'>/color/ff0000</a>.<br />
    Colors viewed so far:
    <ul>"""
)
    
for color in self.requestedColors:
        
s.write("<li><a href='%s' style='color: #%s'>%s</a></li>" % (
            
color, color, color))
    
s.write("""
    </ul>
    </body>
    </html>
    """
)
    
s.finish()
    
return Response(
        
responsecode.OK,
        
{'content-type': http_headers.MimeType('text', 'html')}, s)

ストリームへの書き込みが終了時に finish() メソッドを呼び出すのを忘れな いように。

この方式を使うと、データの書き込み終了を待たずにレスポンスの出力が開始 されます。上の例なんかはその意味ほとんどないけど、やたら長い出力をだら だら流し続けるときなんかに向いてます。

posted at 18:38:08    #
7月 2006
      1 2
3 4 5 6 7 8 9
10111213141516
17181920212223
24252627282930
31      
6月
2006
 8月
2006

Yasushi Iwata のウェブログです。

XML-Image Letterimage

© 2006-2010, Yasushi Iwata