Acceder a servicios agrupados antiguos en Python 3

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

Antes de empezar

Instalar el SDK de servicios de App Engine

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

  1. Incluye el SDK en tu aplicación añadiendo 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. Añade el siguiente código a tu secuencia de comandos principal de Python. Este código crea un middleware de WSGI que define las variables necesarias para habilitar las 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)
    

    Pirámide

    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. Añade la siguiente línea al archivo app.yaml antes de implementar tu aplicación:

    app_engine_apis: true
    
  4. Para desplegar tu aplicación, usa el comando gcloud app deploy.

Consideraciones sobre la migración

Debes tener en cuenta lo siguiente si vas a migrar al tiempo de ejecución de Python 3 y tu aplicación usa servicios empaquetados antiguos.

Pruebas

Para probar localmente la función de los servicios agrupados antiguos en tu aplicación Python 3, usa el servidor de desarrollo local. Al ejecutar el comando dev_appserver.py, debe definir el argumento --runtime_python_path para incluir 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 definir el argumento como una lista de pares [RUNTIME_ID]=[PYTHON_INTERPRETER_PATH] separados por comas. 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 deferred, usan el módulo pickle para serializar y compartir objetos de Python. Si tu entorno de App Engine usa tanto Python 2 como Python 3, lo cual es habitual durante una migración, debes asegurarte de que los objetos serializados compartidos escritos por una versión de Python se puedan reconstituir con la otra. Puedes consultar las directrices para implementar la compatibilidad entre versiones de pickle en la guía.

De forma predeterminada, Python 3 usa protocolos de serialización que no son compatibles con Python 2. Esto puede provocar errores cuando tu aplicación intente reconstituir un objeto de Python en un entorno de Python 2 que se haya escrito en un entorno de Python 3. Para evitar este problema, define las siguientes variables de entorno en el archivo app.yaml de tu aplicación Python 3 según sea necesario:

  • En las aplicaciones que usan Memcache, incluidas las que usan NDB, define lo siguiente: MEMCACHE_USE_CROSS_COMPATIBLE_PROTOCOL: 'True'
  • En las aplicaciones que usan NDB para conectarse a Datastore, define lo siguiente: NDB_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'
  • En el caso de las aplicaciones que usan la opción de diferir, define lo siguiente: DEFERRED_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'

En Python 2, los objetos string contienen una secuencia de valores de bytes de 8 bits. En Python 3, los objetos string contienen una secuencia de caracteres Unicode. De forma predeterminada, Python 3 pickle traduce un string de Python 2 a Unicode interpretando el string de Python 3 como ASCII. Esto puede provocar errores en los valores que estén fuera del intervalo de caracteres ASCII de 0 a 127. Memcache permite anular 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 de un string de Python 2. De esta forma, se evitan errores de decodificación. Sin embargo, si tu Python 2 string contiene datos Unicode reales fuera del intervalo latin1, como datos leídos de un archivo, cPickle no asignará los datos correctamente. Por lo tanto, es importante que actualices tu código de Python 2 para que contenga datos Unicode con objetos unicode y no con objetos string, en el caso de los objetos que serialices. En la guía de compatibilidad se incluyen detalles sobre las actualizaciones necesarias.

El método descrito anteriormente para actualizar el código de Python 2 y generar serializaciones compatibles con Python 3 se aplica a serializaciones de corta duración, como las que se almacenan en Memcache. Es posible que tengas que actualizar o reescribir serializaciones de Python 2 de larga duración, como las almacenadas en Datastore, como parte de tu migración. Por ejemplo, es posible que la serialización escrita con google.appengine.ext.ndb.model.PickleProperty requiera 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 se admite en Python 3, por lo que cualquier aplicación debe reescribirse para usar cualquier framework compatible con WSGI (como Flask).

Una estrategia de migración recomendada es sustituir primero el uso de webapp2 en tu aplicación Python 2.7 por Flask (o un framework web alternativo, como Django, Pyramid, Bottle o web.py) y seguir usando Python 2.7. Después, cuando la aplicación actualizada sea estable, migra el código a Python 3 e impleméntalo y pruébalo con App Engine para Python 3.

Para ver ejemplos de cómo convertir aplicaciones de Python 2.7 que usan webapp2 para usar el framework Flask, puedes consultar estos recursos adicionales.

Usar 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 URLs a diferentes secuencias de comandos, tendrás que combinar esas secuencias de comandos en una que gestione el enrutamiento de URLs.

En el siguiente ejemplo se muestran las diferencias de los controladores en el archivo app.yaml de los respectivos tiempos de ejecución.

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

Tu aplicación Python 3 debe gestionar el enrutamiento de URLs (por ejemplo, con decoradores de Flask).

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

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

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

Seguridad de los hilos

Se da por hecho que las aplicaciones son seguras para subprocesos. Las llamadas a la API deben hacerse en el hilo de la solicitud. Si usas una API de servicios agrupados antigua cuando se inicia la aplicación, se pueden producir errores de seguridad.

Para obtener más información, consulta Errores de seguridad al usar servicios empaquetados antiguos para Python.

Cómo utilizar la extracción de URL

Para usar URL Fetch para Python, debes llamar explícitamente a la biblioteca URL Fetch.

Si tu aplicación de Python 3 usa la API URL Fetch, el encabezado de solicitud X-Appengine-Inbound-Appid se añade cuando tu aplicación envía una solicitud a otra aplicación de App Engine. De esta forma, la aplicación receptora puede verificar la identidad de la aplicación que llama. Para obtener más información, consulta Migrar solicitudes salientes.

Ejemplo (App Engine ndb)

A continuación, se muestra una aplicación básica de Python 2 que registra las visitas a una página mediante App Engine ndb para acceder a Datastore. Su aplicación equivalente es una aplicación de Python 3 en la que webapp2 usage se ha sustituido por Flask y se han implementado los cambios necesarios descritos anteriormente para acceder a los servicios empaquetados 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 aplicaciones se encuentran en el repositorio de código abierto del contenido de migración de Python App Engine (ejemplos de código, vídeos y codelabs), concretamente en las carpetas mod0 y mod1b, respectivamente.