fc2ブログ

[Google App Engine for Python] deployできないディレクトリ名

久しぶりにPythonプログラムをGAE/Pにアップしたのだが、ローカル開発環境ではちゃんと動くのに、appspot.com上では「.pyファイルが見つからない」とエラーが出た。
しばらくハマったのだが、app.yamlに
  script: html5/pre3dHowTo/pre3dHowToTop.py
と書いたのが原因だった。
途中に数字のあるディレクトリのファイルはdeployできないようだ。
  script: html5/predHowTo3/pre3dHowToTop.py
だとちゃんとdeployできる。

静的ファイルは、途中に数字のあるディレクトリのファイルでもdeployできる。
  static_dir: html5/pre3d

GAE/Pの問題と言うよりは、Google App Engine Launcher の問題だな。
Windows以外の環境ではどうなんだろう。
スポンサーサイト



[Google App Engine] Python SDK で SSL を使って deploy

今日は Google Books Viewer カスタマイズ版の使い方 をアップしたのだが、GAEにdeployするとき毎回WARNINGが出る。

WARNINGをちゃんと読んでみたら、「SSLを使ってdeployしなさい」みたいなことが書かれていた。
詳しいことは Do tools like appcfg use SSL (HTTPS)? を参照とあったので、そこのリンク先からssl1.15をダウンロードしたのだが、セットアップがよくわからない。

How to install Python ssl module on Windows? によると、セットアップするにはbuildする必要があって、buildするにはVisual Studio 2003が必要とのこと。
わざわざVisual Studioを入れる気にならない。
gnuwin32を使う方法も書かれていたが、手間がかかりそうだ。

更に調べたところ、コンパイル済のWindowsインストーラーがあるようだが、リンクが切れていた。
諦めかけたが、インストーラーを再アップしているサイトを発見。
【001】appcfg.py をプロキシ(proxy)経由で使用する方法
ここのAttachmentsに ssl-1.15.win32-py2.5.exe がある。インストールはexeを実行するだけ。

sslインストール直後にdeployしたら、うまくいかなかった。
Google App Engine Launcherを再起動したら、ちゃんとdeployできた。WARNINGも消えた。
めでたしめでたし。

[Google App Engine for Python]静的なテキストファイルの文字コードを指定する

昨日、積み上げ棒グラフを表示するJavaScriptライブラリを公開したのだが、ReadmeをテキストファイルとしてGAEにアップした。
ReadmeはUTF-8で記述したので、文字コードを指定しないと、ブラウザによっては文字化けしてしまう。
GAE/Pで静的なテキストファイルの文字コードを指定する方法を調べたので、忘れないうちに書いておく。

プログラムの出力であれば response.headers['Content-Type'] で文字コードを設定すればいいのだが、静的ファイルの場合はapp.yamlで文字コードを指定する。
  - url: /txt
    static_dir: txt
    mime_type: text/plain; charset=UTF-8
とapp.yamlに記述すれば、/txt以下のファイルに対して、HTTPレスポンスヘッダーに Content-Type text/plain; charset=UTF-8 が付加される。
拡張子にかかわらず、対象となる全てのファイルのHTTPレスポンスヘッダーに付加されるので注意。

static_dirではなく、static_filesを指定することで、対象となるファイルを正規表現で指定できる。
  - url: /fordl/(.+\.txt)
    static_files: fordl/\1
    upload: fordl/(.+\.txt)
    mime_type: text/plain; charset=UTF-8
  - url: /fordl
    static_dir: fordl
こうすれば、/fordl以下の.txtのみ、HTTPレスポンスヘッダーに Content-Type text/plain; charset=UTF-8 が付加される。
URLは上から順に評価されるため、記述順序に注意。

文字コードを考慮してファイル名をつければ、異なる文字コードのファイルを混在させることも可能。
  - url: /fordl/(.*utf.*\.txt)
    static_files: fordl/\1
    upload: fordl/(.*utf.*\.txt)
    mime_type: text/plain; charset=UTF-8
  - url: /fordl/(.*sjis\.txt)
    static_files: fordl/\1
    upload: fordl/(.*sjis\.txt)
    mime_type: text/plain; charset=Shift_JIS
  - url: /fordl
    static_dir: fordl

以上。

[Google App Engine for Python] webappでDjango1.2のテンプレートを使う

GAE/Pでプログラミングしていたら、
「Djangoのデフォルトのバージョンが変わる予定だから、 use_library() でバージョンを指定してね」
というWARNINGがログに出ていた。
自分が作成したプログラムのDjangoテンプレートを0.96から1.2へ変更したので、やり方をまとめる。

1. Djangoのバージョンを指定する
webappフレームワークでDjangoテンプレートを使用する方法は、テンプレートの使用を参照。
Djangoのバージョンを指定する方法は、Third-party Python Libraries に記述されている。
日本語訳には(現時点では)記述されていない。

App Engine SDKには、Django0.96とDjango1.2が標準で入っている。
以下のようにしてバージョンを指定する。
  from google.appengine.dist import use_library
  use_library('django', '1.2')
  from google.appengine.ext.webapp import template
以下の点に注意すること
・import template の前に use_library() すること
・アプリケーション内でDjangoのバージョンを統一すること
他のプログラムがimport templateした後、use_library()で別のバージョンを指定するとエラーになる。

2. Django1.2のテンプレートに関するドキュメント
Django1.2のテンプレートでは、
  {% if var1 != var2 and var1 != var3 %}
    var1 is not var2 and var1 is not var3<br>
    {% if var1 == 1 and var2 > 1 or var3 == 0 %}
      (var1 equals 1 and var2 is larger than 3) or var3 equals 0<br>
    {% endif %}
  {% endif %}
のような記述ができる。

Djangoテンプレートの日本語訳ドキュメントは、Django1.0しか見つけられなかった。
Django テンプレート言語
組み込みタグ/フィルタリファレンス

Django1.2のテンプレートに関するドキュメント(英語)は以下
The Django template language
Built-in template tags and filters
Built-in template tags and filters を 'New in Django' と 'Changed in Django' でページ内検索すれば、Django1.1以降の変更点がわかる。

3. Django0.96からDjango1.2へ変更する場合の注意点
Django 1.0でHTMLの自動エスケープが導入されている。
< > ' " & が勝手にエスケープされるため、HTML文をテンプレートに渡しているプログラムは動きがおかしくなる。
自動エスケープをオフにする方法は、自動エスケープを切るにはを参照。

以上。
自分はログを見てたまたま気づいたけど、GAEのDjangoデフォルトバージョンが変わったとたんに動かなくなるケースがありそう。開発者が放置しているアプリとか。

Google App Engine で音声ファイルを使用したときに発生した問題

去年作ったジャンプゲームをGAEのページへ移行した。
横スクロールジャンプゲーム | 無職のHTML5 Canvas
音が出るゲームなのだが、音声ファイル絡みで問題が発生した。

1. 音声ファイルだと認識されない
deployした時に、MIMEタイプが不明と表示され、再生できなかった。
以下のように app.yaml に設定することで、ちゃんと認識された。
- url: /sound/(.+\.ogg)
  static_files: sound/\1
  upload: sound/(.+\.ogg)
  mime_type: audio/ogg
- url: /sound/(.+\.mp3)
  static_files: sound/\1
  upload: sound/(.+\.mp3)
  mime_type: audio/mpeg

2. Google Chromeでの再生がおかしい
Firefoxでは問題無いのだが、Google ChromeではAudioオブジェクトplay()後に再度play()すると音が出ない。
最近バージョンアップしたChromeの問題かと思ったが、移行前の@PAGESの方ではChromeでもちゃんと音が出る。
Google App Engine特有の現象だろうか。よくわからない。
play()前にload()したら音が出たので、webkit の場合はload()するようにプログラムを修正して回避。

3. Outgoing Bandwidth(送信帯域幅)が大きくなる
Google App Engineでは、送信帯域幅(レスポンスデータ量)は 1GB/1日 まで無料
ゲームで使用していたBGMが500KBだったので、テストしただけでOutgoing Bandwidth(送信帯域幅)が大きくなった。
BGMを再生しないようにプログラムを修正した。
また、レスポンスデータ量を減らすために default_expiration を設定した。
適切な値がわからないので、とりあえず 30d にした。

これからもいろいろと問題が出てきそうだ。
新しいものを作りたい気もするが、移行作業を続けよう。

Google App Engine の cronジョブ スケジュールフォーマットでちょっとハマる

先日GAE/Pでリリースした計算特訓ページに、ログアウトしたユーザーや期限切れの回答データを削除するcronジョブを追加した。

時間指定で毎日動くようにスケジューリングしようと思い、設定方法を調べた。
公式ドキュメントに書かれていたスケジュールフォーマットは
  ("every"|ordinal) (days) "of" (monthspec) (time)
で、『「days」には曜日をカンマ区切りのリストで指定します(「"mon","tuesday"」など。』と書かれていたので、
毎日午前3時だと "every of 03:00" かなと思ったがエラーになる。

いろいろやってもエラーになるので、検索して調べたら正解は "every day 03:00"。
of は必須じゃないの? day なんてキーワードどこにも書いてないんだけど。と思ったのだが、
公式ドキュメント英語版を見たらスケジュールフォーマットは
  ("every"|ordinal) (days) ["of" (monthspec)] (time)
で、『 "every day" is equivalent to "every mon,tue,wed,thu,fri,sat,sun" 』と書かれていた。

ちゃんと英語版をチェックしなきゃいけないのか。Task Queueの記述が無かったり、日本語ドキュメントは古いなと思っていたのだが。
せめて英語版と同期が取れていないページは「この訳は最新版ではありません」の注意書きを表示して欲しいな。
Googleの技術力を持ってすればさほどコストをかけずにできると思うのだが。

[Google App Engine for Python] webappフレームワーク使い方まとめ

GAE のwebappフレームワークについて、使い方を忘れないうちにまとめる。

1. リクエストのマッピング
URLから.pyファイルにマッピングするのはapp.yamlだが、.pyファイル内のクラスにマッピングするのはWSGIApplicationクラス。
WSGIApplicationはリクエストを受け取ると、RequestHandlerクラスのインスタンスを生成し、リクエストのHTTPアクションに応じたメソッドを実行する。
WSGIApplicationクラスの使用方法は、アプリケーションの実行を参照。


2. リクエストデータの取得
詳細は Requestクラス を参照。

・GET/POSTパラメータの取得
getval = self.request.get('パラメータ名')
パラメータが存在しない場合は、空文字列('')を返す。

・Cookie値の取得
self.request.cookies から取得する。
cookieID = 'cid' # Cookie名
# Cookie名が存在しない場合は空文字列を取得
cookieval = self.request.cookies.get(cookieID, '')
self.request.cookiesにはマップ型の操作が可能。

・リモートユーザーのIPアドレスの取得
getip = self.request.remote_addr


3. レスポンスの作成
詳細はResponseクラスを参照。

・レスポンスの記述
self.response.out.write('出力HTML')

・テンプレートを使用してレスポンスを記述
Djangoテンプレートを使用可能。使い方はテンプレートの使用を参照。
Djangoテンプレートの構文などはDjangoテンプレート言語を参照。(webappフレームワークのDjangoはバージョン0.96なので注意)
<2011/2/17 追記>
Django1.2も標準で含まれており、デフォルトのバージョンも0.96から変わる予定。
Djangoのバージョン指定方法は webappでDjango1.2のテンプレートを使う を参照。
<追記ここまで>
テンプレートの継承機能を使うと、共通部分をまとめて記述/修正できて便利。

・レスポンスのHTTPヘッダー
self.response.headers に設定する。
これはwsgiref.headers.Headersクラスのインスタンス。
ajaxのレスポンスでは以下のようにヘッダーを変更する。
self.response.headers['Content-Type'] = 'text/xml ;charset=UTF-8'
HTTPヘッダーの制限事項は、許可されないHTTPレスポンスヘッダーを参照。

・Cookieの書き込み、削除
レスポンスのHTTPヘッダーに直接書き込む。
# Cookieに書き込む
cookieID = 'cid' # Cookie名
cookieval = 'valueforCookie' # Cookieにセットする値
# Cookieの有効期限に約1年後を設定
expirdt = (datetime.datetime.utcnow() + datetime.timedelta(365))
expirdtstr = expirdt.strftime('%a, %d-%b-%Y %H:%M:%S UTC')
self.response.headers.add_header('Set-Cookie', '%s=%s; expires=%s' %
(cookieID, cookieval, expirdtstr))

# Cookieから削除する
cookieID = 'cid' # Cookie名
cookieval = 'valueforCookie' # Cookieにセットする値
# Cookieの有効期限を昨日の日付にする
expirdt = (datetime.datetime.utcnow() - datetime.timedelta(1))
expirdtstr = expirdt.strftime('%a, %d-%b-%Y %H:%M:%S UTC')
self.response.headers.add_header('Set-Cookie', '%s=%s; expires=%s' %
(cookieID, cookieval, expirdtstr))

・リダイレクト、ステータスコードの設定
リダイレクト、ヘッダー、ステータス コードを参照。
self.error(code) は、それ以前に記述したレスポンスを消去するので注意。
消去しない場合は、self.response.set_status(code) で設定。

4. その他
セッション機能は公式にサポートしていない。
Googleアカウント前提の場合、@login_required が便利。ただしGETリクエスト専用。

以上。
公式ドキュメントは大した分量ではないので、時間がある方はひと通り目を通したほうがいいと思います。

[Google App Engine] クエリの ORDER BY 制限を回避する

Google App Engine の仕様に苦しめられたので、今日自分がやったことを簡単にまとめる。

1. クエリの制限
Google App Engine のデータストアでは、実行できるクエリに制限がある。

主な制限は以下のとおり
・不等式が使用できるのは1つのプロパティ(DBで言うところの列名)に限られる
# これはOK
SELECT * FROM Test WHERE date = DATE('2010-12-01') AND cost >= 100
# これは BadFilterError 例外が発生する
SELECT * FROM Test WHERE date >= DATE('2010-12-01') AND cost >= 100

・不等式を使用したクエリで ORDER BY を使用する場合、ORDER BY の先頭は不等式を使用したプロパティにする必要がある
# これはOK
SELECT * FROM Test WHERE date >= DATE('2010-12-01') ORDER BY date
# これは BadArgumentError 例外が発生する
SELECT * FROM Test WHERE date >= DATE('2010-12-01') ORDER BY item

自分が作っているプログラムで、「月単位で item の cost をまとめる」みたいなことをする必要があった。
普通のDBでは、以下のようなプログラムにすればよい。
from google.appengine.ext import db
# test データモデル
class Test(db.Model):
  item = db.StringProperty(required=True)
  cost = db.IntegerProperty(default=0)
  date = db.DateProperty(required=True)
(中略)
# 月単位で item の cost をまとめる処理
query = db.GqlQuery("SELECT * FROM Test WHERE date >= DATE('2010-12-01') AND date < DATE('2011-01-01') ORDER BY item")
for entity in query:
  # entity.item が前の値と同じなら entity.cost を加算
しかし Google App Engine では、上記の ORDER BY 制限のため BadArgumentError 例外が発生する。

2. ORDER BY 制限を回避する
ORDER BY の制限だけなら、
query = db.GqlQuery("SELECT * FROM Test WHERE item != '' AND date >= DATE('2010-12-01') AND date < DATE('2011-01-01') ORDER BY item")
で回避できそうだが、これは上記の「不等式が使用できるのは1つのプロパティ」に反するので BadFilterError 例外が発生する。

仕方なくリストを使ってソートした。
# クエリから ORDER BY を削除
query = db.GqlQuery("SELECT * FROM Test WHERE date >= DATE('2010-12-01') AND date < DATE('2011-01-01')")
entityL = [] # ソートするための暫定リスト
for entity in query:
  # 値として有り得ない文字列を使って、値を結合
  entityL.append(entity.item + '$$$$' + str(entity.cost))
entityL.sort()
for test in entityL:
  # 文字列を分割して item と cost を取得
  testSep = test.split('$$$$',1)
  item = testSep[0]
  cost = int(testSep[1])
  # item が前の値と同じなら cost を加算
エンティティ数が少ないからこれで何とかなったが、数が多い場合は処理時間や負荷が心配になる。

クエリで不等式を使わないよう、プロパティを追加する方法も考えられる。
# month プロパティを追加
class Test(db.Model):
  item = db.StringProperty(required=True)
  cost = db.IntegerProperty(default=0)
  date = db.DateProperty(required=True)
  month = db.StringProperty()
(中略)
# データ登録時に年月を設定
test.month = '%4d%2d' % (test.date.year, test.date.month)
test.put()

この場合、クエリで ORDER BY が使用できるので以下のように簡潔になる。
query = db.GqlQuery("SELECT * FROM Test WHERE month = '201012' ORDER BY item")
for entity in query:
  # entity.item が前の値と同じなら entity.cost を加算

ここらへんを考慮してデータ設計しなきゃいけないってことだな。
Google App Engine のデータストアには他にもいろいろ考慮すべき点がありそうだから、ちゃんと調べるか。

Google App Engine Python 開発用サーバーで Ajax

Python 開発用サーバーでとても個人的なプログラムを作り始めてから、気がつくと10日も経っている。

今日は、画面遷移しなくていいところを Ajax でデータ送受信するように修正した。
ブラウザ側は jQuery、サーバー側は Python の webapp フレームワーク

jQuery + PHP でちょっと Ajax をやったことがあるので、プログラミングのやり方はだいたいわかっていたはずだが、結構時間がかかった。
< > & " を &lt; &gt; &amp; &quot; にしないとXMLパースエラーになることを今回初めて知った。(以前のAjaxの勉強で見たかもしれないが忘れてた)
でもスペースを &nbsp; にすると、そんなものは定義されていないとエラーになる。

そこら辺をクリアしてちゃんと Ajaxでデータ送受信を行うことができたが、Google Chrome と IE でテストしたらXMLパースエラーになった。
http header の設定がおかしかったのが原因だが、かなりいいかげんな header でもちゃんとパースしてくれる Firefox はすごい。
以下のように http header を設定したら Google Chrome と IE でもちゃんとデータ受信できた。
self.response.headers['Content-Type'] = 'text/xml ;charset=UTF-8'

Python と JavaScript を同時にコーディングしてると混乱する。JavaScript でセミコロン付け忘れること度々。セミコロン忘れてもエラーにならないからすぐに気づかないし。
あと if の条件式に ( ) を付け忘れることがかなりあった。これはエラーになるからすぐわかる。

そろそろ個人的なプログラムにはケリをつけて、公開できそうなものを作り始めようかな。
でも相変わらず作るもののイメージが湧かない。

Google App Engine Python 開発用サーバー のログ出力でハマる

引き続き、Google App Engine の Python 開発用サーバーでとても個人的なプログラムを組んでいる。

デバッグの方法がよくわからないので、ログに出力することにしたが、日本語が出力されない。
調べたら、Google App Engine Launcher で UnicodeDecodeError が出ていた。
teststr = u'テスト'
logging.debug('debug msg: %s',teststr.encode('cp932'))
のようにWindows向けにエンコードしたらちゃんと出力された。

次にログ出力フォーマットを変更。
Google App エンジン/Python - ログの書式設定の変更 - efreedom
を参考にして
logging.getLogger().handlers[0].setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s','%m-%d %H:%M:%S'))
とフォーマットを変更した。
表示される時刻はアメリカ時間のようだ。日本時間にする方法は見つけられなかった。

いろいろいじってたら、DEBUGレベルのログが出力されなくなった。
ソースをいじってリロードしても出力されない。
試行錯誤の結果、import logging の直後に setLevel を記述していたのが原因とわかった。
リロードしても webapp.WSGIApplication が実行されるだけで、プログラムの先頭部分は実行されない。
一人でハマっていると、簡単なことに気づかないから困る。
run_wsgi_app(application) の直前に setLevel を記述することで解決した。

Pythonの基礎がなんとなくわかってきた気がするので、そろそろPython チュートリアルを読んで理解を深めようと思う。
プロフィール

himax64

Author: 南西
30代後半の無職です。
就活もせずダラダラ生きてます。
作ったもの

最新記事
人気記事
検索フォーム
カテゴリ
月別アーカイブ
最新コメント
最新トラックバック
RSSリンクの表示
QRコード
QRコード
カウンター