Archive for 1月 3rd, 2011

仕事がいっぱい→TaskQueueを使おう

1月 3rd, 2011 by admin

2,3か月前に作成したサーバの監視アプリのソースコードを紹介します。5分間隔で数百件程度のURLにリクエストを投げ、そのレスポンスタイムを取得するだけの単純なアプリなので、機能としては単純なのですが、性能面の制約に苦しみました。今でこそ、CronやTaskQueueから実行したリクエストは、最大で10分間実行できますが、当時は30秒制限があったため、容易に制限時間を超えてしまったのです。平均1秒だとしても30秒の時間制限内には到底処理が終わりません。いろいろ考えた末、Cron関数内で5URLずつまとめてTaskQueueに投げ込むことにしました。その結果、当時の厳しい実行時間制約(30秒)の中、無事に要件を満たすことができました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Cron(webapp.RequestHandler):
  def get(self):
    size = 5
    sites = db.GqlQuery("SELECT * FROM Site ORDER BY date DESC")
    for i in range(0, sites.count(), size): # i = 0, 5, 10, 15, ......
      params = {}
      for j in range(0, size): #j = 0, 1, 2, 3, 4
        if i + j >= sites.count():
          break
        params["url"+str(j)] = sites[i+j].url
      taskqueue.add(url='/worker', params = params) # taskqueueにタスクを追加

class Worker(webapp.RequestHandler):
  def post(self):
    if(int(self.request.headers.environ['HTTP_X_APPENGINE_TASKRETRYCOUNT']) > 0):
      return # エラー発生時の、リトライ防止
    for key in self.request.arguments():
      try:
        url = self.request.get(key)
        fetchAndLog(url) # レスポンスタイムの測定と記録。5秒を超えると例外を投げる。
      except:
        pass

URLの表記を45文字までに制限しました

1月 3rd, 2011 by admin

python-blog-systemで、投稿内に長いURLを記入した際に、45文字目までしか表示しないように変更しました。

変更前

1
2
3
import re
def linkURLs(str):
  return re.sub(r'([^"]|^)(https?|ftp)(://[\w:;/.?%#&=+-]+)', r'\1<a href="\2\3">\2\3</a>', str)

subの第2引数を正規表現で指定しています。

変更後

1
2
3
4
5
import re
def urlReplacer(match, limit = 45):
  return '<a href="%s" target="_blank">%s</a>' % (match.group(), match.group()[:limit] + ('...' if len(match.group()) > limit else ''))
def linkURLs(str):
  return re.sub(r'([^"]|^)(https?|ftp)(://[\w:;/.?%#&=+-]+)', urlReplacer, str)

subの第2引数を、コールバックで指定している。URLが45文字を超えた場合は、45文字目までで打ち切り、末尾に…を付加しています。

コメント削除機能を実装

1月 3rd, 2011 by admin

python-blog-systemにコメント削除機能を実装しました。コメント投稿時に入力したパスワードを指定した場合のみ、コメントの削除が実行されます。投稿時にパスワードを指定しなかった場合は、削除できないようにしました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#削除ボタンの表示部分
      delbutton = u"""
        <div style="float:right">
          <form method="post" name="form" action="/deleteComment/%(key)s">
          <input type="text" name="delpass" class="delcommentpassword"/>
          <input type="button" onclick="if(confirm('本当に削除しますか?'))form.submit()" value="削除" class="delcommentbutton"/>
        </div>"""
% {"key":comment.key()}

#実際の削除処理
class DeleteCommentHandler(AuthHandler):
  def post(self, key):
    comment = Comment.get(key)
    entry_key = comment.entry.key() # コメント先の記事に戻るために、削除前に記事IDを取得
    if self.request.get("delpass") == comment.delpass and comment.delpass != "":
      db.delete(comment)
    self.redirect('/entry/%s' % entry_key)

TypeError: ‘Key’ object is unsubscriptable

1月 3rd, 2011 by admin

tuple、list、dictなどの要素指定できるオブジェクト以外に対して、要素指定でアクセスしようとすると、”TypeError: ‘Key’ object is unsubscriptable”となるようです。下記のようなコードを実行して、このエラーを発生させてしまいました。

1
"Key is %(key)s" % something.key()

名前指定で文字列を置換する場合は、dictで値を渡しましょう。

1
"Key is %(key)s"  % {"key" : something.key()}

発生したエラー

1
2
3
4
5
6
7
8
Traceback (most recent call last):
  File "C:\Program Files\Google\google_appengine\google\appengine\ext\webapp\__init__.py", line 515, in __call__
    handler.get(*groups)
  File "C:\python\relation\main.py", line 137, in get
    printEntry(self, entry, commentDetail = True)
  File "C:\python\relation\main.py", line 164, in printEntry
    </div>""" % comment.key()
TypeError: 'Key' object is unsubscriptable

1対多のリレーション

1月 3rd, 2011 by admin

現在作成中のブログシステムで1対多のデータ構造を構築したので紹介しておく。1つの投稿(Entry)に対して、コメント(Comment)は複数設定できる。こういうデータ構造は、Commentオブジェクトに、Entryオブジェクトへの参照(db.ReferenceProperty)を持たせることで実現できる。collection_name=’comments’と指定しているため、EntryからCommentを取得する場合は、entry.commentsと記述すればよい。そして時系列に並べるためにorder(‘datetime’)でソートしている。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for comment in entry.comments.order('datetime'):
  pass

class Entry(db.Model):
  title = db.StringProperty(default = "")
  body = db.TextProperty(default = "")
  tags = db.ListProperty(db.Key)
  datetime = db.DateTimeProperty(auto_now_add = True)
  @property
  def formattedDatetimeInJST(self):
    return (self.datetime + datetime.timedelta(hours=9)).strftime("%Y-%m-%d %H:%M:%S")
  def tagStr(self):
    return " ".join([Tag.get(x).tag for x in self.tags])

class Comment(db.Model):
  comment = db.TextProperty(default = "")
  entry = db.ReferenceProperty(Entry, collection_name = 'comments')
  user = db.UserProperty()
  datetime = db.DateTimeProperty(auto_now_add = True)
  delpass = db.TextProperty()
  nickname = db.TextProperty()