访问适用于 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)
    

    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 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 的应用,请设置: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。如果值超出 0 到 127 的 ASCII 字符范围,这可能会导致错误。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 代码,以便为您 pickle 的对象使用 unicode 对象(而非 string 对象)保存 Unicode 数据。兼容性指南包含所需更新的详细信息。

上述更新 Python 2 代码以生成与 Python 3 兼容的序列化的方法针对短期有效的序列化,例如存储在 Memcache 中的序列化。您可能需要在迁移过程中更新或重写长期有效的 Python 2 序列化,例如存储在 Datastore 中的序列化。例如,使用 google.appengine.ext.ndb.model.PickleProperty 编写的序列化可能需要升级。

如需详细了解限制和不太常见的问题,请参阅兼容性指南

Web 框架

Python 3 中不捆绑也不支持 webapp2,如需使用任何 WSGI 兼容框架(如 Flask),应用需要重写。

建议使用以下迁移策略。首先,在 Python 2.7 中,将 Python 2.7 应用中的 webapp2 替换为 Flask(或其他 Web 框架,例如 DjangoPyramidBottleweb.py)。然后,在更新后的应用稳定后,将代码迁移到 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: 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 应用必须处理网址路由(例如,使用 Flask 修饰器)。

如果您要使用多个具有不同网址格式的 script 处理程序,或者要在处理程序中使用其他属性,则每个处理程序必须指定 script: auto

您也可以通过在 app.yaml 文件中指定 entrypoint 字段来替换默认启动行为。

如需详细了解如何使用特定处理程序,请参阅 Blobstore 概览、延迟概览和邮件概览。

线程安全(性)

应用被视为是线程安全的。 必须在请求线程上进行 API 调用。 如果您在应用启动时使用旧版捆绑服务 API,则可能会导致安全错误。

如需了解详情,请参阅使用 Python 版旧版捆绑服务时的安全错误

使用 URL Fetch

如需使用 Python 3 的网址提取,您需要明确调用网址提取库。

如果您的 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 迁移内容的开源代码库中找到(代码示例、视频codelab),具体来说分别在 mod0mod1b 文件夹中。