Deferred API ל-Python 3

בדף הזה מוסבר איך להשתמש ב-Deferred API, אחד מהשירותים המצורפים מדור קודם, עם סביבת זמן הריצה של Python 3 בסביבה הרגילה. האפליקציה יכולה לגשת לשירותים הכלולים בחבילה באמצעות App Engine services SDK for Python 3.

סקירה כללית

בעבר, חבילת ההשהיה google.appengine.ext.deferred הייתה תלויה במסגרת webapp ב-Python 2. מאחר שהמסגרת webapp הוסרה מ-SDK של שירותי App Engine ל-Python 3, צריך לבצע כמה שינויים כשמשדרגים אפליקציית Python 2 ל-Python 3.

הפעלת Deferred API

כדי להפעיל את Deferred API ל-Python 3, כבר לא צריך להגדיר את הקובץ builtins.deferred app.yaml. במקום זאת, כדי להפעיל את ה-API, צריך להעביר את use_deferred=True בקריאה ל-wrap_wsgi_app().

דמיון והבדלים

כברירת מחדל, Deferred API ל-Python 3 משתמש באותה כתובת URL‏ /_ah/queue/deferred ובאותו תור ברירת מחדל כמו ב-Python 2. שימו לב: באפליקציות שעוברות אל Cloud Tasks, תור ברירת המחדל לא נוצר באופן אוטומטי וספריית המשימות שמושהות לא זמינה.

אם האפליקציה שלכם משתמשת בנקודת הקצה /_ah/queue/deferred שמוגדרת כברירת מחדל, השימוש ב-deferred.defer() ב-Python 3 נשאר זהה לשימוש ב-Python 2. אם האפליקציה שלכם משתמשת בכתובת URL מותאמת אישית להרצת משימות שנדחות, תצטרכו לבצע כמה שינויים כי המחלקה TaskHandler במודול deferred ל-Python 2 הוסרה בגרסה Python 3 של ה-API הזה.

כדי להגדיר כתובת URL מותאמת אישית להרצת משימות מושהות, האפליקציה יכולה לשנות את השיטה post או את השיטה run_from_request במחלקה deferred.Handler (לשעבר deferred.TaskHandler ב-Python 2), ולהעביר את הפרמטר environ שמייצג מילון שמכיל פרמטרים של בקשת WSGI. אחרי זה אפשר לקרוא לשיטה post מנקודת הקצה המותאמת אישית (כפי שמוצג בדוגמאות של Python 3).

השימוש מקצה לקצה ב-Python 3 Deferred API, כמו ניתוב בקשות וגישה למילון environ, תלוי במסגרת האינטרנט שאליה האפליקציה מועברת. משווים את שינויי הקוד שבוצעו בדוגמה של Python 2 לדוגמאות של Python 3 בקטעים הבאים.

דוגמאות ל-Python 3

בדוגמה הבאה אפשר לראות איך להריץ משימה מושהית באמצעות נקודת קצה שמוגדרת כברירת מחדל ונקודת קצה בהתאמה אישית באפליקציית Flask ובאפליקציית 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)

ללא מסגרת

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)

דוגמאות קוד

כדי לראות את דוגמאות הקוד המלאות מהמדריך הזה, אפשר לעיין ב-GitHub.