Antes, el paquete diferido google.appengine.ext.deferred dependía del marco de trabajo de aplicaciones web en Python 2. Dado que el marco de trabajo de aplicaciones web se quitó en el SDK de servicios de App Engine para Python 3, debes realizar algunos cambios cuando actualices tu app de Python 2 a Python 3.
Habilita la API diferida
A fin de habilitar la API diferida para Python 3, ya no necesitas configurar builtins.deferred como on en el archivo app.yaml. En su lugar, para habilitar la API, debes pasar use_deferred=True en la llamada a wrap_wsgi_app().
Si tu app usa el extremo /_ah/queue/deferred predeterminado, el uso de deferred.defer() en Python 3 permanece igual que el de Python 2.
Si la app usa una URL personalizada para la ejecución de tareas diferidas, debes realizar algunos cambios, ya que se quitó la clase TaskHandler del módulo deferred de Python 2 en la versión de Python 3 de esta API.
A fin de establecer una URL personalizada para la ejecución de tareas diferidas, la app puede anular el método post o run_from_request en la clase deferred.Handler (antes deferred.TaskHandler en Python 2) y pasar el parámetro environ, que representa un diccionario que contiene parámetros de solicitud WSGI. Luego, se puede llamar al método post desde el extremo personalizado (como se muestra en las muestras de Python 3).
El uso de extremo a extremo de la API diferida de Python 3, como el enrutamiento de solicitudes y el acceso al diccionario environ, depende del framework web al que migra la app. Compara los cambios de código realizados del ejemplo de Python 2 a los ejemplos de Python 3 en las siguientes secciones.
Ejemplos de Python 3
En el siguiente ejemplo, se muestra cómo ejecutar una tarea diferida que usa un extremo predeterminado y un extremo personalizado en una aplicación de Flask y una de Django.
Flask
importosfromflaskimportFlask,requestfromgoogle.appengine.apiimportwrap_wsgi_appfromgoogle.appengine.extimportdeferredfromgoogle.appengine.extimportndbmy_key=os.environ.get("GAE_VERSION","Missing")app=Flask(__name__)app.wsgi_app=wrap_wsgi_app(app.wsgi_app,use_deferred=True)classCounter(ndb.Model):count=ndb.IntegerProperty(indexed=False)defdo_something_later(key,amount):entity=Counter.get_or_insert(key,count=0)entity.count+=amountentity.put()@app.route("/counter/increment")defincrement_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 argumentsdeferred.defer(do_something_later,my_key,10,_url="/custom/path",_countdown=120)return"Deferred counter increment."@app.route("/counter/get")defview_counter():counter=Counter.get_or_insert(my_key,count=0)returnstr(counter.count)@app.route("/custom/path",methods=["POST"])defcustom_deferred():print("Executing deferred task.")# request.environ contains the WSGI `environ` dictionary (See PEP 0333)returndeferred.Handler().post(request.environ)
Django
importosfromdjango.confimportsettingsfromdjango.core.wsgiimportget_wsgi_applicationfromdjango.httpimportHttpResponsefromdjango.urlsimportpathfromgoogle.appengine.apiimportwrap_wsgi_appfromgoogle.appengine.extimportdeferredfromgoogle.appengine.extimportndbmy_key=os.environ.get("GAE_VERSION","Missing")classCounter(ndb.Model):count=ndb.IntegerProperty(indexed=False)defdo_something_later(key,amount):entity=Counter.get_or_insert(key,count=0)entity.count+=amountentity.put()defincrement_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 argumentsdeferred.defer(do_something_later,my_key,10,_url="/custom/path",_countdown=120)returnHttpResponse("Deferred counter increment.")defview_counter(request):counter=Counter.get_or_insert(my_key,count=0)returnHttpResponse(str(counter.count))defcustom_deferred(request):print("Executing deferred task.")# request.environ contains the WSGI `environ` dictionary (See PEP 0333)response,status,headers=deferred.Handler().post(request.environ)returnHttpResponse(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)
Sin ningún framework
importosimportrefromgoogle.appengine.apiimportwrap_wsgi_appfromgoogle.appengine.extimportdeferredfromgoogle.appengine.extimportndbmy_key=os.environ.get("GAE_VERSION","Missing")classCounter(ndb.Model):count=ndb.IntegerProperty(indexed=False)defdo_something_later(key,amount):entity=Counter.get_or_insert(key,count=0)entity.count+=amountentity.put()defIncrementCounter(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 argumentsdeferred.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."]defViewCounter(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")]classCustomDeferredHandler(deferred.Handler):"""Deferred task handler that adds additional logic."""defpost(self,environ):print("Executing deferred task.")returnsuper().post(environ)routes={"counter/increment":IncrementCounter,"counter/get":ViewCounter,"custom/path":CustomDeferredHandler(),}classWSGIApplication:def__call__(self,environ,start_response):path=environ.get("PATH_INFO","").lstrip("/")forregex,handlerinroutes.items():match=re.search(regex,path)ifmatchisnotNone:returnhandler(environ,start_response)start_response("404 Not Found",[("Content-Type","text/plain")])return[b"Not found"]app=wrap_wsgi_app(WSGIApplication(),use_deferred=True)
Muestras de código
Para ver las muestras de código completas de esta guía, consulta GitHub.
[[["Fácil de comprender","easyToUnderstand","thumb-up"],["Resolvió mi problema","solvedMyProblem","thumb-up"],["Otro","otherUp","thumb-up"]],[["Difícil de entender","hardToUnderstand","thumb-down"],["Información o código de muestra incorrectos","incorrectInformationOrSampleCode","thumb-down"],["Faltan la información o los ejemplos que necesito","missingTheInformationSamplesINeed","thumb-down"],["Problema de traducción","translationIssue","thumb-down"],["Otro","otherDown","thumb-down"]],["Última actualización: 2025-09-04 (UTC)"],[[["\u003cp\u003eThe Deferred API for Python 3 on Google App Engine no longer requires setting \u003ccode\u003ebuiltins.deferred\u003c/code\u003e in \u003ccode\u003eapp.yaml\u003c/code\u003e; instead, you must use \u003ccode\u003euse_deferred=True\u003c/code\u003e when calling \u003ccode\u003ewrap_wsgi_app()\u003c/code\u003e.\u003c/p\u003e\n"],["\u003cp\u003eThe default behavior of the Deferred API in Python 3 remains consistent with Python 2, using the \u003ccode\u003e/_ah/queue/deferred\u003c/code\u003e URL and the default queue, but keep in mind that Cloud Tasks will differ.\u003c/p\u003e\n"],["\u003cp\u003eWhen using the local development server for testing, you need to set \u003ccode\u003eDEFERRED_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'\u003c/code\u003e in your \u003ccode\u003eapp.yaml\u003c/code\u003e to make sure the Deferred API works.\u003c/p\u003e\n"],["\u003cp\u003eCustom URLs for deferred task execution in Python 3 require using the \u003ccode\u003edeferred.Handler\u003c/code\u003e class's \u003ccode\u003epost\u003c/code\u003e or \u003ccode\u003erun_from_request\u003c/code\u003e methods, as the \u003ccode\u003eTaskHandler\u003c/code\u003e from Python 2 is no longer available.\u003c/p\u003e\n"],["\u003cp\u003eThe way a Python 3 App uses the Deferred API, including its handling of requests and accessing the \u003ccode\u003eenviron\u003c/code\u003e dictionary, is based on the web framework that is being migrated to, as shown in the provided examples with Flask, Django and without a framework.\u003c/p\u003e\n"]]],[],null,["# Deferred API for Python 3\n\nThis page describes how to use the Deferred API, one of the legacy bundled services,\nwith the [Python 3 runtime](/appengine/docs/standard/python3) for\nthe standard environment. Your app can access the bundled services\nthrough the [**App Engine services SDK for Python 3**](https://github.com/GoogleCloudPlatform/appengine-python-standard).\n\nOverview\n--------\n\nPreviously, the Deferred package [`google.appengine.ext.deferred`](/appengine/docs/legacy/standard/python/refdocs/google.appengine.ext.deferred.deferred)\ndepended on the webapp framework in Python 2. Since the webapp framework has\nbeen removed in the App Engine services SDK for Python 3, you need to\nmake some changes when upgrading your Python 2 app to Python 3.\n\nEnabling the Deferred API\n-------------------------\n\nTo enable the Deferred API for Python 3, you no longer need to set\n[`builtins.deferred`](/appengine/docs/legacy/standard/python/config/appref#builtins)\nto `on` in the `app.yaml` file. Instead, to enable the API, you must pass\n`use_deferred=True` in the call to `wrap_wsgi_app()`.\n| **Important:** When using the [local development server](/appengine/docs/standard/python3/services/access#testing_and_deployment) to test Python 3 apps that use the [Deferred API](/appengine/docs/legacy/standard/python/refdocs/google.appengine.ext.deferred.deferred), you must set the following environment variable in your `app.yaml`: \n| `DEFERRED_USE_CROSS_COMPATIBLE_PICKLE_PROTOCOL: 'True'`\n\nSimilarites and differences\n---------------------------\n\nBy default, the Deferred API for Python 3 uses the same URL `/_ah/queue/deferred`\nand the same [default queue](/appengine/docs/standard/python/taskqueue/push/migrating-push-queues#creating_queues)\nas it did in Python 2. Note that for apps migrating to [Cloud Tasks](/tasks), the default queue is\n[not created automatically](/appengine/docs/legacy/standard/python/taskqueue/push/migrating-push-queues#creating_queues)\nand the [deferred tasks library is not available](/appengine/docs/legacy/standard/python/taskqueue/push/migrating-push-queues#features-not-available).\n\nIf your app uses the default `/_ah/queue/deferred` endpoint, using\n[`deferred.defer()` in Python 3](/appengine/docs/standard/python3/reference/services/bundled/google/appengine/ext/deferred/deferred)\nremains the same as\n[Python 2](/appengine/docs/legacy/standard/python/taskqueue/push/creating-tasks#using_the_instead_of_a_worker_service).\nIf your app uses a custom URL for execution of deferred tasks, you need to make\nsome changes since the `TaskHandler` class in the `deferred` module for Python 2\nhas been removed in the Python 3 version of this API.\n\nTo set a custom URL for execution of deferred tasks, the app can override either\nthe `post` or the `run_from_request` method in the\n[`deferred.Handler` class](/appengine/docs/standard/python3/reference/services/bundled/google/appengine/ext/deferred/Handler)\n(formerly `deferred.TaskHandler` in Python 2), and pass the `environ` parameter\nwhich represents a dictionary containing WSGI request parameters. The `post` method can then be\ncalled from the custom endpoint (as shown in the [Python 3 samples](#python-3-examples)).\n\nThe end-to-end usage of the Python 3 Deferred API, such as routing of requests and\naccessing the [`environ` dictionary](https://www.python.org/dev/peps/pep-0333/#id19),\ndepends on the web framework the app is migrating to. Compare code changes made\nfrom the Python 2 example to the Python 3 examples in the following sections.\n\nPython 3 examples\n-----------------\n\nThe following example shows how to execute a deferred task using a\ndefault endpoint and a custom endpoint in a Flask app and Django app. \n\n### Flask\n\n import os\n\n from flask import Flask, request\n from google.appengine.api import wrap_wsgi_app\n from google.appengine.ext import deferred\n from google.appengine.ext import ndb\n\n my_key = os.environ.get(\"GAE_VERSION\", \"Missing\")\n\n app = Flask(__name__)\n app.wsgi_app = wrap_wsgi_app(app.wsgi_app, use_deferred=True)\n\n\n class Counter(ndb.Model):\n count = ndb.IntegerProperty(indexed=False)\n\n\n def do_something_later(key, amount):\n entity = Counter.get_or_insert(key, count=0)\n entity.count += amount\n entity.put()\n\n\n @app.route(\"/counter/increment\")\n def increment_counter():\n # Use default URL and queue name, no task name, execute ASAP.\n deferred.defer(do_something_later, my_key, 10)\n\n # Use default URL and queue name, no task name, execute after 1 minute.\n deferred.defer(do_something_later, my_key, 10, _countdown=60)\n\n # Providing non-default task queue arguments\n deferred.defer(do_something_later, my_key, 10, _url=\"/custom/path\", _countdown=120)\n\n return \"Deferred counter increment.\"\n\n\n @app.route(\"/counter/get\")\n def view_counter():\n counter = Counter.get_or_insert(my_key, count=0)\n return str(counter.count)\n\n\n @app.route(\"/custom/path\", methods=[\"POST\"])\n def custom_deferred():\n print(\"Executing deferred task.\")\n # request.environ contains the WSGI `environ` dictionary (See PEP 0333)\n return deferred.Handler().post(request.environ)\n\n### Django\n\n import os\n\n from django.conf import settings\n from django.core.wsgi import get_wsgi_application\n from django.http import HttpResponse\n from django.urls import path\n from google.appengine.api import wrap_wsgi_app\n from google.appengine.ext import deferred\n from google.appengine.ext import ndb\n\n my_key = os.environ.get(\"GAE_VERSION\", \"Missing\")\n\n\n class Counter(ndb.Model):\n count = ndb.IntegerProperty(indexed=False)\n\n\n def do_something_later(key, amount):\n entity = Counter.get_or_insert(key, count=0)\n entity.count += amount\n entity.put()\n\n\n def increment_counter(request):\n # Use default URL and queue name, no task name, execute ASAP.\n deferred.defer(do_something_later, my_key, 10)\n\n # Use default URL and queue name, no task name, execute after 1 minute.\n deferred.defer(do_something_later, my_key, 10, _countdown=60)\n\n # Providing non-default task queue arguments\n deferred.defer(do_something_later, my_key, 10, _url=\"/custom/path\", _countdown=120)\n\n return HttpResponse(\"Deferred counter increment.\")\n\n\n def view_counter(request):\n counter = Counter.get_or_insert(my_key, count=0)\n return HttpResponse(str(counter.count))\n\n\n def custom_deferred(request):\n print(\"Executing deferred task.\")\n # request.environ contains the WSGI `environ` dictionary (See PEP 0333)\n response, status, headers = deferred.Handler().post(request.environ)\n return HttpResponse(response, status=status.value)\n\n\n urlpatterns = (\n path(\"counter/get\", view_counter, name=\"view_counter\"),\n path(\"counter/increment\", increment_counter, name=\"increment_counter\"),\n path(\"custom/path\", custom_deferred, name=\"custom_deferred\"),\n )\n\n settings.configure(\n DEBUG=True,\n SECRET_KEY=\"thisisthesecretkey\",\n ROOT_URLCONF=__name__,\n MIDDLEWARE_CLASSES=(\n \"django.middleware.common.CommonMiddleware\",\n \"django.middleware.csrf.CsrfViewMiddleware\",\n \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n ),\n ALLOWED_HOSTS=[\"*\"],\n )\n\n app = wrap_wsgi_app(get_wsgi_application(), use_deferred=True)\n\n### Without any framework\n\n import os\n import re\n\n from google.appengine.api import wrap_wsgi_app\n from google.appengine.ext import deferred\n from google.appengine.ext import ndb\n\n my_key = os.environ.get(\"GAE_VERSION\", \"Missing\")\n\n\n class Counter(ndb.Model):\n count = ndb.IntegerProperty(indexed=False)\n\n\n def do_something_later(key, amount):\n entity = Counter.get_or_insert(key, count=0)\n entity.count += amount\n entity.put()\n\n\n def IncrementCounter(environ, start_response):\n # Use default URL and queue name, no task name, execute ASAP.\n deferred.defer(do_something_later, my_key, 10)\n\n # Use default URL and queue name, no task name, execute after 1 minute.\n deferred.defer(do_something_later, my_key, 10, _countdown=60)\n\n # Providing non-default task queue arguments\n deferred.defer(do_something_later, my_key, 10, _url=\"/custom/path\", _countdown=120)\n\n start_response(\"200 OK\", [(\"Content-Type\", \"text/html\")])\n return [b\"Deferred counter increment.\"]\n\n\n def ViewCounter(environ, start_response):\n counter = Counter.get_or_insert(my_key, count=0)\n start_response(\"200 OK\", [(\"Content-Type\", \"text/html\")])\n return [str(counter.count).encode(\"utf-8\")]\n\n\n class CustomDeferredHandler(deferred.Handler):\n \"\"\"Deferred task handler that adds additional logic.\"\"\"\n\n def post(self, environ):\n print(\"Executing deferred task.\")\n return super().post(environ)\n\n\n routes = {\n \"counter/increment\": IncrementCounter,\n \"counter/get\": ViewCounter,\n \"custom/path\": CustomDeferredHandler(),\n }\n\n\n class WSGIApplication:\n def __call__(self, environ, start_response):\n path = environ.get(\"PATH_INFO\", \"\").lstrip(\"/\")\n for regex, handler in routes.items():\n match = re.search(regex, path)\n if match is not None:\n return handler(environ, start_response)\n\n start_response(\"404 Not Found\", [(\"Content-Type\", \"text/plain\")])\n return [b\"Not found\"]\n\n\n app = wrap_wsgi_app(WSGIApplication(), use_deferred=True)\n\nCode samples\n------------\n\nTo view the complete code samples from this guide, see\n[GitHub](https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/appengine/standard_python3/bundled-services)."]]