存取 Python 3 的舊版套裝組合服務

本頁面說明如何搭配標準環境的 Python 3 執行階段,安裝及使用舊版套裝服務。應用程式必須透過 Python 3 適用的 App Engine 服務 SDK 存取套裝組合服務。

事前準備

安裝 App Engine 服務 SDK

如要安裝 App Engine 服務 SDK,請按照下列步驟操作:

  1. requirements.txt 檔案中新增下列程式碼,將 SDK 納入應用程式:

    appengine-python-standard>=1.0.0
    

    您可以在 GitHub 的 appengine-python-standard 存放區和 PyPI 找到 SDK。

  2. 在主要 Python 指令碼中新增下列程式碼。這段程式碼會建立 WSGI 中介軟體,設定啟用 API 呼叫所需的變數。

    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)
    

    金字塔

    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 和延遲) 會使用 pickle 模組,序列化及共用 Python 物件。如果您的 App Engine 環境同時使用 Python 2 和 Python 3 (遷移期間很常見),請務必確保一個 Python 版本編寫的共用序列化物件,可以由另一個版本重組。如需導入跨版本 Pickle 相容性的指南,請參閱這份指南

Python 3 預設使用的封存通訊協定,Python 2 並不支援。 如果應用程式嘗試在 Python 2 環境中重構以 Python 3 環境編寫的 Python 物件,可能會導致失敗。如要避免這個問題,請視需要為 Python 3 應用程式的 app.yaml 檔案設定下列環境變數

  • 如果應用程式使用 Memcache (包括使用 NDB 的應用程式),請設定: MEMCACHE_USE_CROSS_COMPATIBLE_PROTOCOL: 'True'
  • 如果應用程式使用 NDB 連線至 Datastore,請設定: NDB_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'
  • 如果是使用延遲設定的應用程式: DEFERRED_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'

在 Python 2 中,string 物件會保留 8 位元位元組值的序列。在 Python 3 中,string 物件會保留一連串的 Unicode 字元。根據預設,Python 3 pickle 會將 Python 2 string 轉譯為 Unicode,方法是將 Python 3 string 解譯為 ASCII。如果值超出 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 就無法正確對應資料。因此,請務必更新 Python 2 程式碼,以保留含有 unicode 物件的 Unicode 資料,而非 string 物件,以供您 Pickle 的物件使用。如需必要更新的詳細資料,請參閱相容性指南

先前說明的方法是將 Python 2 程式碼更新為產生與 Python 3 相容的序列化,適用於短期序列化,例如儲存在 Memcache 中的序列化。您可能需要更新或重寫長期存在的 Python 2 序列化,例如儲存在 Datastore 中的序列化 (這是遷移作業的一部分)。舉例來說,使用 google.appengine.ext.ndb.model.PickleProperty 撰寫的序列化可能需要升級。

如要進一步瞭解限制和較不常見的問題,請參閱相容性指南

網路架構

Python 3 不會隨附或支援 webapp2,因此任何應用程式都必須重新編寫,才能使用任何與 WSGI 相容的架構 (例如 Flask)。

建議的遷移策略是先在 Python 2.7 應用程式中,將 webapp2 的使用方式替換為 Flask (或替代的網路架構,例如 DjangoPyramidBottleweb.py),同時維持使用 Python 2.7。接著,在更新後的應用程式穩定運作後,將程式碼遷移至 Python 3,並使用 Python 3 適用的 App Engine 部署及測試。

如需將使用 webapp2 的 Python 2.7 應用程式轉換為使用 Flask 架構的範例,請參閱這些額外資源

使用處理常式

Python 3 應用程式只能有一個相關聯的指令碼,因此如果 app.yaml 有多個 script 處理常式將網址對應至不同指令碼,您需要將這些指令碼合併為一個,負責處理網址路徑。

以下範例顯示各個執行階段的 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: python313
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 應用程式必須處理網址路徑 (例如使用 Flask 裝飾器)。

如要使用多個具有不同網址模式的 script 處理常式,或在處理常式中使用其他屬性,每個處理常式都必須指定 script: auto

您也可以在 app.yaml 檔案中指定 entrypoint 欄位,覆寫預設啟動行為。

如要進一步瞭解如何使用特定處理常式,請參閱 BlobstoreDeferredMail 總覽。

執行緒安全

系統會假設應用程式具備執行緒安全特性。API 呼叫必須在要求執行緒上進行。如果應用程式啟動時使用舊版套裝服務 API,可能會導致安全性錯誤。

詳情請參閱「使用 Python 舊版套裝服務時發生安全性錯誤」。

使用網址擷取

如要使用 Python 適用的網址擷取服務,您必須明確呼叫網址擷取程式庫。

如果 Python 3 應用程式使用 URL Fetch API,當應用程式傳送要求至其他 App Engine 應用程式時,系統會加入 X-Appengine-Inbound-Appid 要求標頭。接收端應用程式可藉此驗證呼叫端應用程式的身分。詳情請參閱「遷移外送要求」。

範例 (App Engine ndb)

以下是基本的 Python 2 應用程式,使用 App Engine ndb 存取 Datastore,記錄網頁造訪次數。這個應用程式的對應版本是 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 遷移內容的開放原始碼存放區 (程式碼範例、影片codelabs),分別位於 mod0mod1b 資料夾中。