API Deferred para Python 3

Nesta página, descrevemos como usar a API Deferred, um dos serviços no pacote legados, com o ambiente de execução do Python 3 para o ambiente padrão. O aplicativo pode acessar os serviços incluídos pelo SDK de serviços do App Engine para Python 3.

Visão geral

Antes, o pacote adiado google.appengine.ext.deferred dependia do framework do webapp no Python 2. Como o framework do webapp foi removida no SDK de serviços do App Engine para Python 3, você precisa fazer algumas alterações ao fazer upgrade do aplicativo Python 2 para Python 3.

Como ativar a API Deferred

Para ativar a API Deferred para Python 3, não é mais necessário definir builtins.deferred como on no arquivo app.yaml. Para ativar a API, você precisa transmitir use_deferred=True na chamada para wrap_wsgi_app().

Semelhanças e diferenças

Por padrão, a API Deferred para Python 3 usa o mesmo URL /_ah/queue/deferred e a mesma fila padrão que no Python 2. Para aplicativos que estão migrando para o Cloud Tasks, a fila padrão não é criada automaticamente e a biblioteca de tarefas adiadas não está disponível.

Se o aplicativo usar o endpoint /_ah/queue/deferred padrão, o uso de deferred.defer() no Python 3 permanecerá igual ao Python 2. Se o aplicativo usar um URL personalizado para a execução de tarefas adiadas, será necessário fazer algumas alterações, já que a classe TaskHandler no módulo deferred do Python 2 foi removida na versão do Python 3 dessa API.

Para definir um URL personalizado para execução de tarefas adiadas, o app pode modificar o método post ou run_from_request na classe deferred.Handler (anteriormente deferred.TaskHandler no Python 2) e transmitir o parâmetro environ, que representa um dicionário que contém parâmetros de solicitação WSGI. O método post pode ser chamado a partir do endpoint personalizado, conforme mostrado nas amostras do Python 3.

O uso completo da API Deferred do Python 3, como o encaminhamento de solicitações e o acesso ao dicionário environ, depende do framework da Web para o qual o aplicativo está migrando. Compare as alterações de código feitas no exemplo do Python 2 com os do Python 3 nas seções a seguir.

Exemplos do Python 3

O exemplo a seguir mostra como executar uma tarefa adiada usando um endpoint padrão e um endpoint personalizado em um aplicativo Flask e 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)

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

Amostras de código

Para ver os exemplos de código completos deste guia, consulte o GitHub.