存取 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 3 環境編寫的 Python 2 環境中重建 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 解讀為 ASCII,進而將其轉譯為 Unicode。string這可能會導致值超出 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 範圍以外的實際萬用字元資料 (例如從檔案讀取的資料),cPickle 就無法正確對應資料。因此,請務必更新 Python 2 程式碼,以便使用 unicode 物件 (而非 string 物件) 儲存您要醃製的物件。相容性指南會詳細說明所需的更新。

先前所述的更新 Python 2 程式碼方法,可產生與 Python 3 相容的序列化,解決短暫序列化問題,例如儲存在 Memcache 中的序列化。您可能需要更新或重寫長期存在的 Python 2 序列化作業,例如在遷移作業中儲存在 Datastore 中的序列化作業。舉例來說,使用 google.appengine.ext.ndb.model.PickleProperty 編寫的序列化作業可能需要升級。

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

網路架構

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

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

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

使用處理常式

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 欄位,藉此覆寫預設的啟動行為。

如要進一步瞭解如何使用特定處理常式,請參閱 Blobstore延遲Mail 的總覽。

執行緒安全性

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

如需更多資訊,請參閱「使用 Python 的舊版套裝服務時發生安全性錯誤」。

使用網址擷取

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

如果 Python 3 應用程式使用網址擷取 API,當應用程式向其他 App Engine 應用程式傳送要求時,系統會新增 X-Appengine-Inbound-Appid 要求標頭,讓接收應用程式驗證呼叫應用程式的身分。詳情請參閱「遷移外送要求」。

範例 (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 遷移內容的開放原始碼存放區中找到 (程式碼範例、影片codelabs),具體來說,分別位於 mod0mod1b 資料夾中。