Deferred API für Python 3

Auf dieser Seite wird beschrieben, wie Sie die Deferred API, einen der gebündelten Legacy-Dienste, mit der Python 3-Laufzeit für die Standardumgebung verwenden. Ihre Anwendung kann über das App Engine Services SDK für Python 3 auf die gebündelten Dienste zugreifen.

Überblick

Bisher war das Deferred-Paket google.appengine.ext.deferred vom Web-App-Framework in Python 2 abhängig. Da das Web-App-Framework im App Engine Services SDK für Python 3 entfernt wurde, müssen Sie beim Upgrade Ihrer Python 2-Anwendung auf Python 3 einige Änderungen vornehmen.

Deferred API aktivieren

Wenn Sie die Deferred API für Python 3 aktivieren möchten, müssen Sie builtins.deferred in der Datei app.yaml nicht mehr auf on setzen. Stattdessen müssen Sie use_deferred=True im Aufruf von wrap_wsgi_app() übergeben, um die API zu aktivieren.

Gemeinsamkeiten und Unterschiede

Standardmäßig verwendet die Deferred API für Python 3 dieselbe URL /_ah/queue/deferred und dieselbe Standardwarteschlange wie in Python 2. Beachten Sie, dass für Anwendungen, die zu Cloud Tasks migrieren, die Standardwarteschlange nicht automatisch erstellt wird und die Bibliothek für zurückgestellte Aufgaben nicht verfügbar ist.

Wenn Ihre Anwendung den Standard-Endpunkt /_ah/queue/deferred verwendet, bleibt die Verwendung von deferred.defer() in Python 3 mit der in Python 2 identisch. Wenn Ihre Anwendung eine benutzerdefinierte URL zur Ausführung zurückgestellter Aufgaben verwendet, müssen Sie einige Änderungen vornehmen, da die TaskHandler-Klasse im deferred-Modul für Python 2 in der Python 3-Version dieser API entfernt wurde.

Zum Festlegen einer benutzerdefinierten URL für die Ausführung von zurückgestellten Aufgaben kann die Anwendung entweder die post- oder die run_from_request-Methode in der deferred.Handler-Klasse (früher deferred.TaskHandler in Python 2) überschreiben und den environ-Parameter übergeben, der ein Wörterbuch mit WSGI-Anfrageparametern darstellt. Die post-Methode kann dann vom benutzerdefinierten Endpunkt aufgerufen werden (wie in den Python 3-Beispielen gezeigt).

Die durchgängige Nutzung der Python 3 Deferred API, wie z. B. die Weiterleitung von Anfragen und der Zugriff auf das Wörterbuch environ, hängt von dem Web-Framework ab, auf das die Anwendung migriert wird. Vergleichen Sie Codeänderungen, die im Python 2-Beispiel vorgenommen wurden, mit den Python 3-Beispielen in den folgenden Abschnitten.

Beispiele für Python 3

Das folgende Beispiel zeigt, wie Sie eine zurückgestellte Aufgabe mit einem Standardendpunkt und einem benutzerdefinierten Endpunkt in einer Flask-Anwendung und Django-Anwendung ausführen.

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)

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

Codebeispiele

Die vollständigen Codebeispiele aus dieser Anleitung finden Sie unter GitHub.