Python 3용 Deferred API

이 페이지에서는 표준 환경에 Python 3 런타임과 함께 기존 번들 서비스 중 하나인 Deferred API를 사용하는 방법을 설명합니다. 앱은 Python 3용 App Engine 서비스 SDK를 통해 번들 서비스에 액세스할 수 있습니다.

개요

이전에는 지연된 패키지 google.appengine.ext.deferred가 Python 2의 웹 앱 프레임워크에 의존했습니다. 웹 앱 프레임워크가 Python 3용 App Engine 서비스 SDK에서 삭제되었으므로 Python 2 앱을 Python 3으로 업그레이드할 때 몇 가지 항목을 변경해야 합니다.

Deferred API 사용 설정

Python 3용 Deferred API를 사용 설정하기 위해 더 이상 app.yaml 파일에서 builtins.deferredon으로 설정할 필요가 없습니다. 대신 API를 사용 설정하려면 호출에서 use_deferred=Truewrap_wsgi_app()으로 전달해야 합니다.

유사점 및 차이점

기본적으로 Python 3용 Deferred API에서는 Python 2와 동일한 URL /_ah/queue/deferred 및 동일한 기본 큐를 사용합니다. Cloud Tasks로 마이그레이션하는 앱의 경우 기본 큐가 자동으로 생성되지 않으며 지연된 태스크 라이브러리를 사용할 수 없습니다.

앱에서 기본 /_ah/queue/deferred 엔드포인트를 사용하는 경우 Python 3에서 deferred.defer()를 사용하면 Python 2와 동일하게 유지됩니다. 앱에서 지연된 태스크 실행에 커스텀 URL을 사용하는 경우 Python 2용 deferred 모듈의 TaskHandler 클래스가 이 API의 Python 3 버전에서 삭제되었으므로 몇 가지 항목을 변경해야 합니다.

지연된 작업을 실행할 수 있도록 커스텀 URL을 설정하려면 앱이 deferred.Handler 클래스(Python 2에서는 deferred.TaskHandler)에서 post 또는 run_from_request 메서드를 재정의하고 WSGI 요청 매개변수가 포함된 사전을 나타내는 environ 매개변수를 전달하면 됩니다. 그런 다음 Python 3 샘플과 같이 커스텀 엔드포인트에서 post 메서드를 호출할 수 있습니다.

요청 라우팅 및 environ 사전 액세스와 같은 Python 3 Deferred API의 엔드 투 엔드 사용량은 앱에서 마이그레이션하는 웹 프레임워크에 따라 달라집니다. 다음 섹션에서 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를 참조하세요.