API differita per Python 3

Questa pagina descrive come utilizzare l'API Deferred, uno dei servizi in bundle legacy, con l'ambiente di runtime Python 3 per l'ambiente standard. La tua app può accedere ai servizi in bundle tramite l'SDK dei servizi App Engine per Python 3.

Panoramica

In precedenza, il pacchetto Deferred google.appengine.ext.deferred dipendeva dal webapp framework in Python 2. Poiché il webapp framework è stato rimosso dall'SDK dei servizi App Engine per Python 3, devi apportare alcune modifiche quando esegui l'upgrade della tua app Python 2 a Python 3.

Abilitazione dell'API Deferred

Per abilitare l'API differita per Python 3, non è più necessario impostare builtins.deferred su on nel file app.yaml. Per abilitare l'API, devi passare use_deferred=True nella chiamata a wrap_wsgi_app().

Somiglianze e differenze

Per impostazione predefinita, l'API Deferred per Python 3 utilizza lo stesso URL /_ah/queue/deferred e la stessa coda predefinita di Python 2. Tieni presente che per le app di cui viene eseguita la migrazione a Cloud Tasks, la coda predefinita non viene creata automaticamente e la libreria delle attività differite non è disponibile.

Se la tua app utilizza l'endpoint /_ah/queue/deferred predefinito, l'utilizzo di deferred.defer() in Python 3 rimane invariato rispetto a Python 2. Se la tua app utilizza un URL personalizzato per l'esecuzione delle attività posticipate, devi apportare alcune modifiche, poiché la classe TaskHandler nel modulo deferred per Python 2 è stata rimossa nella versione Python 3 di questa API.

Per impostare un URL personalizzato per l'esecuzione di attività posticipate, l'app può eseguire l'override del metodo post o run_from_request nella classe deferred.Handler (in precedenza deferred.TaskHandler in Python 2) e passare il parametro environ, che rappresenta un dizionario contenente i parametri della richiesta WSGI. Il metodo post può quindi essere chiamato dall'endpoint personalizzato (come mostrato negli esempi di Python 3).

L'utilizzo end-to-end dell'API Deferred di Python 3, ad esempio il routing delle richieste e l'accesso al dizionario environ, dipende dal framework web a cui viene eseguita la migrazione dell'app. Confronta le modifiche al codice apportate dall'esempio Python 2 agli esempi Python 3 nelle sezioni seguenti.

Esempi di Python 3

L'esempio seguente mostra come eseguire un'attività differita utilizzando un endpoint predefinito e un endpoint personalizzato in un'app Flask e in un'app 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)

Senza alcun 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)

Esempi di codice

Per visualizzare gli esempi di codice completi di questa guida, consulta GitHub.