API Deferred pour Python 3

Cette page explique comment utiliser l'API Deferred, l'un des anciens services groupés, avec l'environnement d'exécution Python 3 pour l'environnement standard. Votre application peut accéder aux services groupés via le SDK des services App Engine pour Python 3.

Aperçu

Auparavant, le package différée google.appengine.ext.deferred dépendait du framework webapp dans Python 2. Étant donné que l'environnement logiciel webapp a été supprimé dans le SDK des services App Engine pour Python 3, vous devez apporter quelques modifications lors de la mise à niveau de votre application Python 2 vers Python 3.

Activer l'API Deferred

Pour activer l'API Deferred pour Python 3, vous n'avez plus besoin de définir builtins.deferred sur on dans le fichier app.yaml. Pour activer l'API, vous devez transmettre use_deferred=True dans l'appel à wrap_wsgi_app().

Similitudes et différences

Par défaut, l'API Deferred pour Python 3 utilise la même URL /_ah/queue/deferred et la même file d'attente par défaut que dans Python 2. Notez que pour les applications qui migrent vers Cloud Tasks, la file d'attente par défaut n'est pas créé automatiquement et la bibliothèque de tâches différées n'est pas disponible.

Si votre application utilise le point de terminaison /_ah/queue/deferred par défaut, l'utilisation de deferred.defer() dans Python 3 reste identique à Python 2. Si votre application utilise une URL personnalisée pour l'exécution de tâches différées, vous devez apporter des modifications, car la classe TaskHandler du module deferred pour Python 2 a été supprimée dans la version Python 3 de cette API.

Pour définir une URL personnalisée pour l'exécution de tâches différées, l'application peut remplacer la méthode post ou run_from_request dans la classe deferred.Handler (anciennement deferred.TaskHandler dans Python 2) et transmettre le paramètre environ qui représente un dictionnaire contenant des paramètres de requête WSGI. La méthode post peut ensuite être appelée à partir du point de terminaison personnalisé (comme illustré dans les exemples Python 3).

L'utilisation de bout en bout de l'API Deferred Python 3, telle que le routage des requêtes et l'accès au dictionnaire environ, dépend du framework Web vers lequel l'application migre. Comparez les modifications de code apportées à partir de l'exemple Python 2 aux exemples Python 3 des sections suivantes.

Exemples Python 3

L'exemple suivant montre comment exécuter une tâche différée à l'aide d'un point de terminaison par défaut, ainsi que d'un point de terminaison personnalisé dans une application Flask et une application 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)

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

Exemples de code

Pour afficher les exemples de code complets de ce guide, consultez GitHub.