Python 3 の以前のバンドル サービスにアクセスする

このページでは、スタンダード環境で Python 3 ランタイムを使用して、以前のバンドル サービスをインストールして使用する方法について説明します。アプリを使用すると Python 3 用 App Engine サービス SDK を介してバンドル サービスにアクセスする必要があります。

始める前に

App Engine サービス SDK のインストール

App Engine Services SDK をインストールする手順は次のとおりです。

  1. requirements.txt ファイルに次の行を追加して、SDK をアプリに組み込みます。

    appengine-python-standard>=1.0.0
    

    SDK は、GitHub の appengine-python-standard リポジトリと PyPI で入手できます。

  2. メインの Python スクリプトに次のコードを追加します。このコードは、API 呼び出しを有効にするために必要な変数を設定する WSGI ミドルウェアを作成します。

    Flask

    from flask import Flask
    from google.appengine.api import wrap_wsgi_app
    
    app = Flask(__name__)
    app.wsgi_app = wrap_wsgi_app(app.wsgi_app)
    

    Django

    from DJANGO_PROJECT_NAME.wsgi import application
    from google.appengine.api import wrap_wsgi_app
    
    app = wrap_wsgi_app(application)
    

    Pyramid

    from pyramid.config import Configurator
    from google.appengine.api import wrap_wsgi_app
    
    config = Configurator()
    # make configuration settings
    app = config.make_wsgi_app()
    app = wrap_wsgi_app(app)
    

    WSGI

    import google.appengine.api
    
    def app(environ, start_response):
        start_response('200 OK', [('Content-Type', 'text/plain')])
        yield b'Hello world!\n'
    
    app = google.appengine.api.wrap_wsgi_app(app)
    
  3. アプリをデプロイする前に、次の行を app.yaml に追加します。

    app_engine_apis: true
    
  4. アプリをデプロイするには、gcloud app deploy コマンドを使用します。

移行に関する考慮事項

アプリで以前のバンドル サービスを使用している場合に、Python 3 ランタイムに移行するには、次の考慮事項を把握しておいてください。

テスト

以前のバンドル サービスの機能をローカルの Python 3 アプリでテストするには、ローカル開発用サーバーを使用します。dev_appserver.py コマンドを実行するときは、--runtime_python_path 引数を設定して、Python 3 インタープリタのパスを含める必要があります。例:

   python3 CLOUD_SDK_ROOT/bin/dev_appserver.py --runtime_python_path=/usr/bin/python3

[RUNTIME_ID]=[PYTHON_INTERPRETER_PATH] ペアのカンマ区切りのリストに引数を設定することもできます。例:

   python3 CLOUD_SDK_ROOT/bin/dev_appserver.py --runtime_python_path="python27=/user/bin/python2.7,python3=/usr/bin/python3"

pickle の互換性

Memcache、Cloud NDB、deferred などの共有サービスは、pickle モジュールを使用して Python オブジェクトをシリアル化して共有します。App Engine 環境で Python 2 と Python 3 の両方が使用されているケースは移行中によく見られるものですが、この場合、一方のバージョンの Python で記述された共有シリアル化オブジェクトをもう一方のバージョンで再構成できる必要があります。バージョン間の pickle 互換性の実装に関するガイダンスについては、ガイドをご覧ください。

デフォルトでは、Python 3 は Python 2 でサポートされていない pickle プロトコルを使用します。この結果、Python 3 環境で作成された Python オブジェクトを Python 2 環境で再構築しようとすると、エラーが発生する可能性があります。この問題を回避するには、必要に応じて Python 3 アプリの app.yaml ファイルで次の環境変数を設定します。

  • Memcache を使用するアプリ(NDB を使用するアプリを含む)の場合は、MEMCACHE_USE_CROSS_COMPATIBLE_PROTOCOL: 'True' を設定します。
  • NDB を使用して Datastore に接続するアプリの場合は、NDB_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True' を設定します。
  • deferred を使用するアプリの場合、DEFERRED_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True' を設定します。

Python 2 では、string オブジェクトは 8 ビットバイト値のシーケンスを保持します。Python 3 では、string オブジェクトは Unicode 文字のシーケンスを保持します。デフォルトでは、Python 3 pickle は Python 3 string を ASCII として解釈することで、Python 2 string を Unicode に変換します。これにより、ASCII 文字範囲 0 ~ 127 の範囲外の値にエラーが発生する可能性があります。Memcache では、このデフォルト マッピングのオーバーライドがサポートされています。

from google.appengine.api import memcache
import six.moves.cPickle as pickle

def _unpickle_factory(file):
    return pickle.Unpickler(file, encoding='latin1')

memcache.setup_client(memcache.Client(unpickler=_unpickle_factory))

latin1 エンコードは、Python 2 の string の各バイトの 256 個の可能な値それぞれに対するマッピングを定義します。これにより、デコードエラーを防ぐことができます。ただし、Python 2 の string に、latin1 の範囲外の実際の Unicode データ(ファイルから読み取られたデータなど)が含まれている場合、cPickle はデータを正しくマッピングしません。したがって、pickle するオブジェクトの場合は、string オブジェクトではなく unicode オブジェクトを使用して Unicode データを保持するように Python 2 コードを更新することが重要です。必要なアップデートについて詳しくは、互換性ガイドをご覧ください。

前述の Python 2 コードの更新方法で Python 3 互換のシリアル化を生成することで、有効期間の短いシリアル化(Memcache に保存されているものなど)に対処します。有効期間が長い Python 2 のシリアル化(移行の一部として Datastore に保存されたものなど)の更新または書き換えが必要になる場合があります。たとえば、google.appengine.ext.ndb.model.PickleProperty を使用して記述されたシリアル化では、アップグレードが必要になる場合があります。

制限事項とあまり一般的でない問題の詳細については、互換性に関するガイドをご覧ください。

ウェブ フレームワーク

webapp2 は Python 3 ではバンドルまたはサポートされないため、すべてのアプリケーションは、WSGI 互換のフレームワーク(Flask など)を利用するように書き換えを行う必要があります。

おすすめの移行戦略は、Python 2.7 を維持したまま、最初に Python 2.7 アプリの webapp2 の使用を Flask(または DjangoPyramidBottleweb.py などの別のウェブ フレームワーク)に置き換えることです。その後、更新したアプリが安定したら、コードを Python 3 に移行し、Python 3 向けの App Engine を使用してデプロイとテストを行います。

webapp2 を使用する Python 2.7 アプリを Flask フレームワークの使用に変換する方法の例については、こちらの参考情報をご覧ください。

ハンドラの使用

Python 3 アプリには 1 つのスクリプトを関連付けることができます。このため、app.yaml に複数の script ハンドラがあり、URL を別のスクリプトにマッピングしている場合は、それらのスクリプトを 1 つのスクリプトに結合して URL ルーティングを処理する必要があります。

次の例は、各ランタイムの app.yaml ファイルのハンドラの違いを示しています。

Python 2

runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /
  script: home.app

- url: /index\.html
  script: home.app

- url: /stylesheets
  static_dir: stylesheets

- url: /(.*\.(gif|png|jpg))$
  static_files: static/\1
  upload: static/.*\.(gif|png|jpg)$

- url: /admin/.*
  script: admin.app
  login: admin

- url: /.*
  script: not_found.app

Python 3

runtime: python312
app_engine_apis: true

handlers:
- url: /stylesheets
  static_dir: stylesheets

- url: /(.*\.(gif|png|jpg))$
  static_files: static/\1
  upload: static/.*\.(gif|png|jpg)$

- url: /admin/.*
  script: auto
  login: admin

Python 3 アプリは、URL ルーティングを処理する必要があります(例: Flask デコレータを使用して)。

異なる URL パターンで複数の script ハンドラを使用する場合や、ハンドラ内で他の属性を使用する場合は、各ハンドラに script: auto を指定する必要があります。

app.yaml ファイルで entrypoint フィールドを指定すると、デフォルトの起動動作をオーバーライドすることもできます。

特定のハンドラの使用方法について詳しくは、Blobstore遅延メールの概要をご覧ください。

スレッドセーフ

アプリはスレッドセーフであると想定されます。API 呼び出しはリクエスト スレッドで実行する必要があります。 アプリの起動時に以前のバンドル サービス API を使用すると、セキュリティ エラーが発生する可能性があります。

詳細については、Python 用の以前のバンドル サービスを使用する場合のセキュリティ エラーをご覧ください。

URL 取得の使用

Python 用の URL 取得を使用するには、URL 取得ライブラリを明示的に呼び出す必要があります。

Python 3 アプリで URL Fetch API を使用する場合、アプリが別の App Engine アプリにリクエストを送信するときに X-Appengine-Inbound-Appid リクエスト ヘッダーが追加されます。これにより、受信側アプリは呼び出し元アプリの ID を検証できます。詳細については、送信リクエストの移行をご覧ください。

例(App Engine ndb

App Engine ndb を使用して Datastore にアクセスするページ訪問を登録する基本的な Python 2 アプリを以下に示します。コンパニオンは Python 3 と同等のアプリです。webapp2 の使用は Flask に置き換えられており、Python 3 でバンドルされたサービスにアクセスするために、上記の必要な変更が実装されています。

Python 2(webapp2

import os
import webapp2
from google.appengine.ext import ndb
from google.appengine.ext.webapp import template

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

class MainHandler(webapp2.RequestHandler):
    'main application (GET) handler'
    def get(self):
        store_visit(self.request.remote_addr, self.request.user_agent)
        visits = fetch_visits(10)
        tmpl = os.path.join(os.path.dirname(__file__), 'index.html')
        self.response.out.write(template.render(tmpl, {'visits': visits}))

app = webapp2.WSGIApplication([
    ('/', MainHandler),
], debug=True)

Python 3(Flask)

from flask import Flask, render_template, request
from google.appengine.api import wrap_wsgi_app
from google.appengine.ext import ndb

app = Flask(__name__)
app.wsgi_app = wrap_wsgi_app(app.wsgi_app)


class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)


@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)
    return render_template('index.html', visits=visits)

どちらのアプリも、Python App Engine 移行コンテンツのオープンソース リポジトリ(コードサンプル、動画Codelab)にあります。具体的には、それぞれ mod0 フォルダと mod1b フォルダにあります。