diff --git a/docs/src/background_tasks/tutorial001.py b/docs/src/background_tasks/tutorial001.py
new file mode 100644
index 000000000..1720a7433
--- /dev/null
+++ b/docs/src/background_tasks/tutorial001.py
@@ -0,0 +1,15 @@
+from fastapi import BackgroundTasks, FastAPI
+
+app = FastAPI()
+
+
+def write_notification(email: str, message=""):
+ with open("log.txt", mode="w") as email_file:
+ content = f"notification for {email}: {message}"
+ email_file.write(content)
+
+
+@app.post("/send-notification/{email}")
+async def send_notification(email: str, background_tasks: BackgroundTasks):
+ background_tasks.add_task(write_notification, email, message="some notification")
+ return {"message": "Notification sent in the background"}
diff --git a/docs/src/background_tasks/tutorial002.py b/docs/src/background_tasks/tutorial002.py
new file mode 100644
index 000000000..9fe737012
--- /dev/null
+++ b/docs/src/background_tasks/tutorial002.py
@@ -0,0 +1,24 @@
+from fastapi import BackgroundTasks, Depends, FastAPI
+
+app = FastAPI()
+
+
+def write_log(message: str):
+ with open("log.txt", mode="a") as log:
+ log.write(message)
+
+
+def get_query(background_tasks: BackgroundTasks, q: str = None):
+ if q:
+ message = f"found query: {q}\n"
+ background_tasks.add_task(write_log, message)
+ return q
+
+
+@app.post("/send-notification/{email}")
+async def send_notification(
+ email: str, background_tasks: BackgroundTasks, q: str = Depends(get_query)
+):
+ message = f"message to {email}\n"
+ background_tasks.add_task(write_log, message)
+ return {"message": "Message sent"}
diff --git a/docs/tutorial/background-tasks.md b/docs/tutorial/background-tasks.md
new file mode 100644
index 000000000..5764dc952
--- /dev/null
+++ b/docs/tutorial/background-tasks.md
@@ -0,0 +1,86 @@
+You can define background tasks to be run *after* returning a response.
+
+This is useful for operations that need to happen after a request, but that the client doesn't really have to be waiting for the operation to complete before receiving his response.
+
+This includes, for example:
+
+* Email notifications sent after performing an action:
+ * As connecting to an email server and sending an email tends to be "slow" (several seconds), you can return the response right away and send the email notification in the background.
+* Processing data:
+ * For example, let's say you receive a file that must go through a slow process, you can return a response of "Accepted" (HTTP 202) and process it in the background.
+
+## Using `BackgroundTasks`
+
+First, import `BackgroundTasks` and define a parameter in your *path operation function* with a type declaration of `BackgroundTasks`:
+
+```Python hl_lines="1 13"
+{!./src/background_tasks/tutorial001.py!}
+```
+
+**FastAPI** will create the object of type `BackgroundTasks` for you and pass it as that parameter.
+
+!!! tip
+ You declare a parameter of `BackgroundTasks` and use it in a very similar way as to when using the `Request` directly.
+
+
+## Create a task function
+
+Create a function to be run as the background task.
+
+It is just a standard function that can receive parameters.
+
+It can be an `async def` or normal `def` function, **FastAPI** will know how to handle it correctly.
+
+In this case, the task function will write to a file (simulating sending an email).
+
+And as the write operation doesn't use `async` and `await`, we define the function with normal `def`:
+
+```Python hl_lines="6 7 8 9"
+{!./src/background_tasks/tutorial001.py!}
+```
+
+## Add the background task
+
+Inside of your *path operation function*, pass your task function to the *background tasks* object with the method `.add_task()`:
+
+```Python hl_lines="14"
+{!./src/background_tasks/tutorial001.py!}
+```
+
+`.add_task()` receives as arguments:
+
+* A task function to be run in the background (`write_notification`).
+* Any sequence of arguments that should be passed to the task function in order (`email`).
+* Any keyword arguments that should be passed to the task function (`message="some notification"`).
+
+## Dependency Injection
+
+Using `BackgroundTasks` also works with the dependency injection system, you can declare a parameter of type `BackgroundTasks` at multiple levels: in a *path operation function*, in a dependency (dependable), in a sub-dependency, etc.
+
+**FastAPI** knows what to do in each case and how to re-use the same object, so that all the background tasks are merged together and are run in the background afterwards:
+
+```Python hl_lines="11 14 20 23"
+{!./src/background_tasks/tutorial002.py!}
+```
+
+In this example, the messages will be written to the `log.txt` file *after* the response is sent.
+
+If there was a query in the request, it will be written to the log in a background task.
+
+And then another background task generated at the *path operation function* will write a message using the `email` path parameter.
+
+## Technical Details
+
+The class `BackgroundTasks` comes directly from `starlette.background`.
+
+It is imported/included directly into FastAPI so that you can import it from `fastapi` and avoid accidentally importing the alternative `BackgroundTask` (without the `s` at the end) from `starlette.background`.
+
+By only using `BackgroundTasks` (and not `BackgroundTask`), it's then possible to use it as a *path operation function* parameter and have **FastAPI** handle the rest for you, just like when using the `Request` object directly.
+
+It's still possible to use `BackgroundTask` alone in FastAPI, but you have to create the object in your code and return a Starlette `Response` including it.
+
+You can see more details in Starlette's official docs for Background Tasks.
+
+## Recap
+
+Import and use `BackgroundTasks` with parameters in *path operation functions* and dependencies to add background tasks.
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index 2db3339db..6affb37e1 100644
--- a/fastapi/__init__.py
+++ b/fastapi/__init__.py
@@ -2,6 +2,8 @@
__version__ = "0.9.1"
+from starlette.background import BackgroundTasks
+
from .applications import FastAPI
from .datastructures import UploadFile
from .exceptions import HTTPException
diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py
index 748fe4a9e..d7fbf853d 100644
--- a/fastapi/dependencies/models.py
+++ b/fastapi/dependencies/models.py
@@ -26,6 +26,7 @@ class Dependant:
name: str = None,
call: Callable = None,
request_param_name: str = None,
+ background_tasks_param_name: str = None,
) -> None:
self.path_params = path_params or []
self.query_params = query_params or []
@@ -35,5 +36,6 @@ class Dependant:
self.dependencies = dependencies or []
self.security_requirements = security_schemes or []
self.request_param_name = request_param_name
+ self.background_tasks_param_name = background_tasks_param_name
self.name = name
self.call = call
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 5c1f42632..157a685c4 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -3,7 +3,18 @@ import inspect
from copy import deepcopy
from datetime import date, datetime, time, timedelta
from decimal import Decimal
-from typing import Any, Callable, Dict, List, Mapping, Sequence, Tuple, Type, Union
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ List,
+ Mapping,
+ Optional,
+ Sequence,
+ Tuple,
+ Type,
+ Union,
+)
from uuid import UUID
from fastapi import params
@@ -16,6 +27,7 @@ from pydantic.errors import MissingError
from pydantic.fields import Field, Required, Shape
from pydantic.schema import get_annotation_from_schema
from pydantic.utils import lenient_issubclass
+from starlette.background import BackgroundTasks
from starlette.concurrency import run_in_threadpool
from starlette.datastructures import UploadFile
from starlette.requests import Headers, QueryParams, Request
@@ -125,6 +137,8 @@ def get_dependant(*, path: str, call: Callable, name: str = None) -> Dependant:
)
elif lenient_issubclass(param.annotation, Request):
dependant.request_param_name = param_name
+ elif lenient_issubclass(param.annotation, BackgroundTasks):
+ dependant.background_tasks_param_name = param_name
elif not isinstance(param.default, params.Depends):
add_param_to_body_fields(param=param, dependant=dependant)
return dependant
@@ -215,13 +229,20 @@ def is_coroutine_callable(call: Callable) -> bool:
async def solve_dependencies(
- *, request: Request, dependant: Dependant, body: Dict[str, Any] = None
-) -> Tuple[Dict[str, Any], List[ErrorWrapper]]:
+ *,
+ request: Request,
+ dependant: Dependant,
+ body: Dict[str, Any] = None,
+ background_tasks: BackgroundTasks = None,
+) -> Tuple[Dict[str, Any], List[ErrorWrapper], Optional[BackgroundTasks]]:
values: Dict[str, Any] = {}
errors: List[ErrorWrapper] = []
for sub_dependant in dependant.dependencies:
- sub_values, sub_errors = await solve_dependencies(
- request=request, dependant=sub_dependant, body=body
+ sub_values, sub_errors, background_tasks = await solve_dependencies(
+ request=request,
+ dependant=sub_dependant,
+ body=body,
+ background_tasks=background_tasks,
)
if sub_errors:
errors.extend(sub_errors)
@@ -258,7 +279,11 @@ async def solve_dependencies(
errors.extend(body_errors)
if dependant.request_param_name:
values[dependant.request_param_name] = request
- return values, errors
+ if dependant.background_tasks_param_name:
+ if background_tasks is None:
+ background_tasks = BackgroundTasks()
+ values[dependant.background_tasks_param_name] = background_tasks
+ return values, errors, background_tasks
def request_params_to_args(
diff --git a/fastapi/routing.py b/fastapi/routing.py
index 67619bda5..493590af7 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -68,7 +68,7 @@ def get_app(
raise HTTPException(
status_code=400, detail="There was an error parsing the body"
)
- values, errors = await solve_dependencies(
+ values, errors, background_tasks = await solve_dependencies(
request=request, dependant=dependant, body=body
)
if errors:
@@ -83,11 +83,17 @@ def get_app(
else:
raw_response = await run_in_threadpool(dependant.call, **values)
if isinstance(raw_response, Response):
+ if raw_response.background is None:
+ raw_response.background = background_tasks
return raw_response
response_data = serialize_response(
field=response_field, response=raw_response
)
- return content_type(content=response_data, status_code=status_code)
+ return content_type(
+ content=response_data,
+ status_code=status_code,
+ background=background_tasks,
+ )
return app
diff --git a/mkdocs.yml b/mkdocs.yml
index 0e4e91c4a..1eed39a8c 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -59,6 +59,7 @@ nav:
- SQL (Relational) Databases: 'tutorial/sql-databases.md'
- NoSQL (Distributed / Big Data) Databases: 'tutorial/nosql-databases.md'
- Bigger Applications - Multiple Files: 'tutorial/bigger-applications.md'
+ - Background Tasks: 'tutorial/background-tasks.md'
- Sub Applications - Behind a Proxy: 'tutorial/sub-applications-proxy.md'
- Application Configuration: 'tutorial/application-configuration.md'
- GraphQL: 'tutorial/graphql.md'
diff --git a/tests/test_tutorial/test_background_tasks/__init__.py b/tests/test_tutorial/test_background_tasks/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial001.py b/tests/test_tutorial/test_background_tasks/test_tutorial001.py
new file mode 100644
index 000000000..f86f7f9dc
--- /dev/null
+++ b/tests/test_tutorial/test_background_tasks/test_tutorial001.py
@@ -0,0 +1,19 @@
+import os
+from pathlib import Path
+
+from starlette.testclient import TestClient
+
+from background_tasks.tutorial001 import app
+
+client = TestClient(app)
+
+
+def test():
+ log = Path("log.txt")
+ if log.is_file():
+ os.remove(log) # pragma: no cover
+ response = client.post("/send-notification/foo@example.com")
+ assert response.status_code == 200
+ assert response.json() == {"message": "Notification sent in the background"}
+ with open("./log.txt") as f:
+ assert "notification for foo@example.com: some notification" in f.read()
diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002.py b/tests/test_tutorial/test_background_tasks/test_tutorial002.py
new file mode 100644
index 000000000..69c8b7f92
--- /dev/null
+++ b/tests/test_tutorial/test_background_tasks/test_tutorial002.py
@@ -0,0 +1,19 @@
+import os
+from pathlib import Path
+
+from starlette.testclient import TestClient
+
+from background_tasks.tutorial002 import app
+
+client = TestClient(app)
+
+
+def test():
+ log = Path("log.txt")
+ if log.is_file():
+ os.remove(log) # pragma: no cover
+ response = client.post("/send-notification/foo@example.com?q=some-query")
+ assert response.status_code == 200
+ assert response.json() == {"message": "Message sent"}
+ with open("./log.txt") as f:
+ assert "found query: some-query\nmessage to foo@example.com" in f.read()