Accede a los servicios agrupados en paquetes heredados para Python 3

En esta página, se describe cómo instalar y usar los servicios agrupados en paquetes heredados con el entorno de ejecución de Python 3 para el entorno estándar. Tu aplicación debe acceder a los servicios en paquetes a través del SDK de servicios de App Engine para Python 3.

Antes de comenzar

Instala el SDK de servicios de App Engine

Para instalar el SDK de servicios de App Engine, sigue estos pasos:

  1. Para incluir el SDK con tu app, agrega la siguiente línea a tu archivo requirements.txt:

    appengine-python-standard>=1.0.0
    

    Puedes encontrar el SDK en GitHub en el repositorio appengine-python-standard y en PyPI.

  2. Agrega el siguiente código a tu secuencia de comandos principal de Python. Este código crea el middleware WSGI que configura las variables necesarias para habilitar tus llamadas a la 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. Agrega la siguiente línea a tu archivo app.yaml antes de implementar tu app:

    app_engine_apis: true
    
  4. Para implementar la app, usa el comando gcloud app deploy.

Consideraciones sobre la migración

Debes tener en cuenta las siguientes consideraciones si migras al entorno de ejecución de Python 3 y tu app usa los servicios agrupados en paquetes heredados.

Prueba

Para probar localmente la funcionalidad de servicios en paquetes heredados en tu app de Python 3, usa el servidor de desarrollo local. Cuando ejecutas el comando dev_appserver.py, debes configurar el argumento --runtime_python_path para que incluya una ruta al intérprete de Python 3. Por ejemplo:

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

También puedes establecer el argumento en una lista separada por comas de pares [RUNTIME_ID]=[PYTHON_INTERPRETER_PATH]. Por ejemplo:

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

Compatibilidad con pickle

Los servicios compartidos, como Memcache, Cloud NDB y diferidos, usan el módulo pickle para serializar y compartir objetos de Python. Si tu entorno de App Engine usa Python 2 y 3, que son comunes durante una migración, debes asegurarte de que los objetos compartidos serializados escritos por una versión de Python se puedan reducir por la otra. Puedes encontrar asesoramiento sobre la implementación de la compatibilidad con serialización de versiones cruzadas en la guía.

De forma predeterminada, Python 3 usa protocolos de serialización que no son compatibles con Python 2. Esto puede causar fallas cuando la app intenta reconstituir un objeto de Python en un entorno de Python 2 que se escribió en un entorno de Python 3. Para evitar este problema, configura las siguientes variables de entorno en el archivo app.yaml de tu app de Python 3 según sea necesario:

  • Para las apps que usan Memcache, incluidas las que usan NDB, configura: MEMCACHE_USE_CROSS_COMPATIBLE_PROTOCOL: 'True'
  • En el caso de las apps que usan NDB para conectarse a Datastore, configura lo siguiente: NDB_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'
  • Para las apps que usan aplazamientos, configura lo siguiente: DEFERRED_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'

En Python 2, los objetos string contienen una secuencia de valores de byte de 8 bits. En Python 3, los objetos string contienen una secuencia de caracteres Unicode. De forma predeterminada, el pickle de Python 3 traduce una string de Python 2 a unicode mediante la interpretación de la string de Python 3 como ASCII. Esto puede generar errores para los valores fuera del rango de caracteres ASCII de 0 a 127. Memcache admite la anulación de esta asignación predeterminada.

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))

La codificación latin1 define una asignación para cada uno de los 256 valores posibles de cada byte en un string de Python 2. Esto evita los errores de decodificación. Sin embargo, si el string de Python 2 contiene datos reales de Unicode fuera del rango latin1, como los datos leídos de un archivo, cPickle no asignará los datos de forma correcta. Por lo tanto, es importante que actualices tu código de Python 2 para conservar los datos Unicode con objetos unicode y no objetos string, para los objetos que uses con Pickle. La guía de compatibilidad incluye detalles sobre las actualizaciones necesarias.

El método descrito antes para actualizar tu código de Python 2 que permite producir serializaciones compatibles de Python 3, aborda las serializaciones de corta duración, como las almacenadas en Memcache. Es posible que debas actualizar o reescribir las serializaciones de Python 2 de larga duración, como las almacenadas en Datastore como parte de la migración. Por ejemplo, la serialización escrita con google.appengine.ext.ndb.model.PickleProperty puede requerir una actualización.

Consulta la guía de compatibilidad para obtener más información sobre las limitaciones y los problemas menos habituales.

Frameworks web

webapp2 no se incluye ni admite en Python 3, por lo que cualquier aplicación debe volver a escribirse para usar cualquier marco de compatibilidad con WSGI (como Flask).

Una estrategia de migración recomendada es reemplazar primero el uso de webapp2 en tu app de Python 2.7 con Flask (o un framework web alternativo, como Django, Pyramid, Bottle o web.py) mientras se permanece en Python 2.7. Luego, cuando tu app actualizada sea estable, migra el código a Python 3 y, luego, impleméntalo y pruébalo con App Engine para Python 3.

Si quieres obtener ejemplos sobre cómo convertir las apps de Python 2.7 que usan webapp2 para usar el framework de Flask, puedes consultar estos recursos adicionales.

Usa controladores

Una aplicación de Python 3 solo puede tener una secuencia de comandos asociada, por lo que si tu app.yaml tiene varios controladores script que asignan URL a diferentes secuencias de comandos, deberás combinar esas secuencias de comandos en una que administre el enrutamiento de la URL.

En el siguiente ejemplo, se muestran las diferencias del controlador en el archivo app.yaml para los entornos de ejecución respectivos.

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

Tu aplicación de Python 3 debe controlar el enrutamiento de URL (por ejemplo, con los decoradores de Flask).

Si deseas usar varios controladores script con diferentes patrones de URL o si deseas usar otros atributos en tus controladores, cada controlador debe especificar script: auto.

También puedes anular el comportamiento de inicio predeterminado si especificas un campo entrypoint en el archivo app.yaml.

Consulta las descripciones generales de Blobstore, Diferido y Correo electrónico para obtener más información sobre cómo usar controladores específicos.

Seguridad de subprocesos

Se supone que las aplicaciones son seguras para los subprocesos. Se deben realizar llamadas a la API en el subproceso de la solicitud. Esto puede generar errores de seguridad si usas una API de servicios agrupados en paquetes heredados cuando se inicia la aplicación.

Si deseas obtener más información, consulta Errores de seguridad que surgen al usar servicios agrupados en paquetes heredados para Python.

Usa la recuperación de URL

Si deseas usar la recuperación de URL para Python, debes llamar de forma explícita a la biblioteca de recuperación de URL.

Si tu app de Python 3 usa la API de URL Fetch, se agregará el encabezado de solicitud X-Appengine-Inbound-Appid cuando la app envíe una solicitud a otra app de App Engine. Esto permite que la app receptora verifique la identidad de la app que realiza la llamada. Para obtener más información, consulta Migra solicitudes salientes.

Ejemplo (ndb de App Engine)

A continuación, se muestra una app básica de Python 2 que registra las visitas a páginas mediante ndb de App Engine para acceder a Datastore. Su complemento es una aplicación equivalente de Python 3 en la que Flask reemplazó el uso de webapp2, y se implementaron los cambios necesarios descritos antes para acceder a los servicios agrupados en 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)

Ambas apps se pueden encontrar en el repositorio de código abierto del contenido de migración de App Engine en Python (muestras de código, videos y codelabs), en particular en las carpetas mod0 y mod1b, respectivamente.