API diferida para Python 3

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

Descripción general

Antes, el paquete diferido google.appengine.ext.deferred dependía del marco de trabajo de aplicaciones web en Python 2. Dado que el marco de trabajo de aplicaciones web se quitó en el SDK de servicios de App Engine para Python 3, debes realizar algunos cambios cuando actualices tu app de Python 2 a Python 3.

Habilita la API diferida

A fin de habilitar la API diferida para Python 3, ya no necesitas configurar builtins.deferred como on en el archivo app.yaml. En su lugar, para habilitar la API, debes pasar use_deferred=True en la llamada a wrap_wsgi_app().

Similitudes y diferencias

De forma predeterminada, la API diferida para Python 3 usa la misma URL /_ah/queue/deferred y la misma cola predeterminada que usó en Python 2. Ten en cuenta que para las aplicaciones que migran a Cloud Tasks, la cola predeterminada no se creó automáticamente y la biblioteca de tareas diferidas no está disponible.

Si tu app usa el extremo /_ah/queue/deferred predeterminado, el uso de deferred.defer() en Python 3 permanece igual que el de Python 2. Si la app usa una URL personalizada para la ejecución de tareas diferidas, debes realizar algunos cambios, ya que se quitó la clase TaskHandler del módulo deferred de Python 2 en la versión de Python 3 de esta API.

A fin de establecer una URL personalizada para la ejecución de tareas diferidas, la app puede anular el método post o run_from_request en la clase deferred.Handler (antes deferred.TaskHandler en Python 2) y pasar el parámetro environ, que representa un diccionario que contiene parámetros de solicitud WSGI. Luego, se puede llamar al método post desde el extremo personalizado (como se muestra en las muestras de Python 3).

El uso de extremo a extremo de la API diferida de Python 3, como el enrutamiento de solicitudes y el acceso al diccionario environ, depende del framework web al que migra la app. Compara los cambios de código realizados del ejemplo de Python 2 a los ejemplos de Python 3 en las siguientes secciones.

Ejemplos de Python 3

En el siguiente ejemplo, se muestra cómo ejecutar una tarea diferida que usa un extremo predeterminado y un extremo personalizado en una aplicación de Flask y una de Django.

Flask

import os

from flask import Flask, request
from google.appengine.api import wrap_wsgi_app
from google.appengine.ext import deferred
from google.appengine.ext import ndb

my_key = os.environ.get("GAE_VERSION", "Missing")

app = Flask(__name__)
app.wsgi_app = wrap_wsgi_app(app.wsgi_app, use_deferred=True)

class Counter(ndb.Model):
    count = ndb.IntegerProperty(indexed=False)

def do_something_later(key, amount):
    entity = Counter.get_or_insert(key, count=0)
    entity.count += amount
    entity.put()

@app.route("/counter/increment")
def increment_counter():
    # Use default URL and queue name, no task name, execute ASAP.
    deferred.defer(do_something_later, my_key, 10)

    # Use default URL and queue name, no task name, execute after 1 minute.
    deferred.defer(do_something_later, my_key, 10, _countdown=60)

    # Providing non-default task queue arguments
    deferred.defer(do_something_later, my_key, 10, _url="/custom/path", _countdown=120)

    return "Deferred counter increment."

@app.route("/counter/get")
def view_counter():
    counter = Counter.get_or_insert(my_key, count=0)
    return str(counter.count)

@app.route("/custom/path", methods=["POST"])
def custom_deferred():
    print("Executing deferred task.")
    # request.environ contains the WSGI `environ` dictionary (See PEP 0333)
    return deferred.Handler().post(request.environ)

Django

import os

from django.conf import settings
from django.core.wsgi import get_wsgi_application
from django.http import HttpResponse
from django.urls import path
from google.appengine.api import wrap_wsgi_app
from google.appengine.ext import deferred
from google.appengine.ext import ndb

my_key = os.environ.get("GAE_VERSION", "Missing")

class Counter(ndb.Model):
    count = ndb.IntegerProperty(indexed=False)

def do_something_later(key, amount):
    entity = Counter.get_or_insert(key, count=0)
    entity.count += amount
    entity.put()

def increment_counter(request):
    # Use default URL and queue name, no task name, execute ASAP.
    deferred.defer(do_something_later, my_key, 10)

    # Use default URL and queue name, no task name, execute after 1 minute.
    deferred.defer(do_something_later, my_key, 10, _countdown=60)

    # Providing non-default task queue arguments
    deferred.defer(do_something_later, my_key, 10, _url="/custom/path", _countdown=120)

    return HttpResponse("Deferred counter increment.")

def view_counter(request):
    counter = Counter.get_or_insert(my_key, count=0)
    return HttpResponse(str(counter.count))

def custom_deferred(request):
    print("Executing deferred task.")
    # request.environ contains the WSGI `environ` dictionary (See PEP 0333)
    response, status, headers = deferred.Handler().post(request.environ)
    return HttpResponse(response, status=status.value)

urlpatterns = (
    path("counter/get", view_counter, name="view_counter"),
    path("counter/increment", increment_counter, name="increment_counter"),
    path("custom/path", custom_deferred, name="custom_deferred"),
)

settings.configure(
    DEBUG=True,
    SECRET_KEY="thisisthesecretkey",
    ROOT_URLCONF=__name__,
    MIDDLEWARE_CLASSES=(
        "django.middleware.common.CommonMiddleware",
        "django.middleware.csrf.CsrfViewMiddleware",
        "django.middleware.clickjacking.XFrameOptionsMiddleware",
    ),
    ALLOWED_HOSTS=["*"],
)

app = wrap_wsgi_app(get_wsgi_application(), use_deferred=True)

Sin ningún framework

import os
import re

from google.appengine.api import wrap_wsgi_app
from google.appengine.ext import deferred
from google.appengine.ext import ndb

my_key = os.environ.get("GAE_VERSION", "Missing")

class Counter(ndb.Model):
    count = ndb.IntegerProperty(indexed=False)

def do_something_later(key, amount):
    entity = Counter.get_or_insert(key, count=0)
    entity.count += amount
    entity.put()

def IncrementCounter(environ, start_response):
    # Use default URL and queue name, no task name, execute ASAP.
    deferred.defer(do_something_later, my_key, 10)

    # Use default URL and queue name, no task name, execute after 1 minute.
    deferred.defer(do_something_later, my_key, 10, _countdown=60)

    # Providing non-default task queue arguments
    deferred.defer(do_something_later, my_key, 10, _url="/custom/path", _countdown=120)

    start_response("200 OK", [("Content-Type", "text/html")])
    return [b"Deferred counter increment."]

def ViewCounter(environ, start_response):
    counter = Counter.get_or_insert(my_key, count=0)
    start_response("200 OK", [("Content-Type", "text/html")])
    return [str(counter.count).encode("utf-8")]

class CustomDeferredHandler(deferred.Handler):
    """Deferred task handler that adds additional logic."""

    def post(self, environ):
        print("Executing deferred task.")
        return super().post(environ)

routes = {
    "counter/increment": IncrementCounter,
    "counter/get": ViewCounter,
    "custom/path": CustomDeferredHandler(),
}

class WSGIApplication:
    def __call__(self, environ, start_response):
        path = environ.get("PATH_INFO", "").lstrip("/")
        for regex, handler in routes.items():
            match = re.search(regex, path)
            if match is not None:
                return handler(environ, start_response)

        start_response("404 Not Found", [("Content-Type", "text/plain")])
        return [b"Not found"]

app = wrap_wsgi_app(WSGIApplication(), use_deferred=True)

Muestras de código

Para ver las muestras de código completas de esta guía, consulta GitHub.