When working on web applications, especially those involving operations that can take a while, it’s essential to keep your API as responsive and efficient as possible. FastAPI, a modern Python web framework, has a nifty feature to help with this: background tasks. These allow you to run operations asynchronously, ensuring the main request-response cycle isn’t bogged down, making your application more responsive and scalable.
So, what’s the deal with background tasks? These are operations that kick off once the main request is done, and the response is sent back to the client. It’s incredibly handy for things that don’t need to gum up the main works, like sending emails, processing uploaded files, updating databases, generating reports, or hitting up external APIs.
Implementing background tasks in FastAPI is really straightforward. You start by importing the BackgroundTasks
class. Check out this simple example:
from fastapi import FastAPI, BackgroundTasks
from time import sleep
app = FastAPI()
def my_long_running_background_function(status: str):
sleep(5) # Simulating a long task
print(f"\n\n\n---\nAll done, status {status}\n---\n\n\n")
@app.get("/")
async def hello(background_tasks: BackgroundTasks):
background_tasks.add_task(my_long_running_background_function, status="my status")
return {"message": "Hello World"}
When someone hits the /
endpoint, it instantly returns {"message": "Hello World"}
while the my_long_running_background_function
does its thing in the background. Pretty cool, right?
Now, sometimes, you might want to run background tasks in custom middleware. It gets a bit more intricate here since middleware doesn’t support passing BackgroundTasks
objects directly. But no worries, you can still create and assign BackgroundTasks
manually to your response:
from fastapi import FastAPI, BackgroundTasks, Request
app = FastAPI()
def my_background_task():
print("Runs in background")
@app.middleware("http")
async def my_background_task_middleware(request: Request, call_next):
background_tasks = BackgroundTasks()
background_tasks.add_task(my_background_task)
response = await call_next(request)
response.background = background_tasks
return response
@app.get("/")
def hello():
return {"message": "Hello World"}
With this setup, every time an HTTP request is made, my_background_task
kicks off in the background, thanks to the middleware.
Now, what if you’ve got multiple tasks to run? FastAPI’s got you covered. You can chain multiple background tasks, and they’ll execute in the order you add them:
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
def task1():
print("Task 1")
def task2():
print("Task 2")
@app.get("/")
async def hello(background_tasks: BackgroundTasks):
background_tasks.add_task(task1)
background_tasks.add_task(task2)
return {"message": "Hello World"}
In this scenario, task1
fires off before task2
, keeping everything orderly.
Handling errors and logging for background tasks is a must. Catching issues and logging them ensures nothing fails silently:
from fastapi import FastAPI, BackgroundTasks
import logging
app = FastAPI()
logger = logging.getLogger(__name__)
def my_background_task():
try:
print("Running task")
except Exception as e:
logger.error(f"Error in background task: {e}")
@app.get("/")
async def hello(background_tasks: BackgroundTasks):
background_tasks.add_task(my_background_task)
return {"message": "Hello World"}
Keeping an eye on and optimizing your background tasks is also crucial. Logging and monitoring help track task execution:
from fastapi import FastAPI, BackgroundTasks
import logging
app = FastAPI()
logger = logging.getLogger(__name__)
def my_background_task():
try:
print("Running task")
logger.info("Task started")
# Task code
logger.info("Task completed")
except Exception as e:
logger.error(f"Error in background task: {e}")
@app.get("/")
async def hello(background_tasks: BackgroundTasks):
background_tasks.add_task(my_background_task)
return {"message": "Hello World"}
To avoid crushing your system, limit the number of concurrent background tasks. Manage them manually or use external task queues with limits:
from fastapi import FastAPI, BackgroundTasks
from concurrent.futures import ThreadPoolExecutor
app = FastAPI()
executor = ThreadPoolExecutor(max_workers=5) # Limit to 5 concurrent tasks
def my_background_task():
print("Running task")
@app.get("/")
async def hello(background_tasks: BackgroundTasks):
executor.submit(my_background_task)
return {"message": "Hello World"}
For real-world applications, imagine sending emails. Instead of making users wait, you can shoot off emails in the background:
from fastapi import FastAPI, BackgroundTasks
from fastapi.responses import JSONResponse
from fastapi.requests import Request
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
app = FastAPI()
def send_email(subject, message, from_addr, to_addr, password):
msg = MIMEMultipart()
msg['From'] = from_addr
msg['To'] = to_addr
msg['Subject'] = subject
msg.attach(MIMEText(message, 'plain'))
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(from_addr, password)
text = msg.as_string()
server.sendmail(from_addr, to_addr, text)
server.quit()
@app.post("/send-email")
async def send_email_endpoint(background_tasks: BackgroundTasks, subject: str, message: str, from_addr: str, to_addr: str, password: str):
background_tasks.add_task(send_email, subject, message, from_addr, to_addr, password)
return JSONResponse(content={"message": "Email sent"}, status_code=200)
Another neat trick is processing uploaded files in the background:
from fastapi import FastAPI, BackgroundTasks, File, UploadFile
from fastapi.responses import JSONResponse
app = FastAPI()
def process_file(file_path):
print(f"Processing file: {file_path}")
@app.post("/upload-file")
async def upload_file(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
file_path = "path/to/uploaded/file"
with open(file_path, "wb") as f:
f.write(file.file.read())
background_tasks.add_task(process_file, file_path)
return JSONResponse(content={"message": "File uploaded and processing started"}, status_code=200)
In conclusion, background tasks in FastAPI are a game-changer for handling time-consuming operations asynchronously. They significantly boost the responsiveness and scalability of your applications. By leveraging this feature, you can build efficient APIs capable of handling complex workflows without compromising performance. Keep your tasks concise and focused, manage errors properly, use connection pooling for database operations, monitor task execution, and consider setting priority and limits on concurrent tasks. Following these practices will help you optimize background tasks and ensure your application runs smoothly and efficiently.
As you continue working with FastAPI and background tasks, stay in the loop with the latest best practices and performance enhancements. This will guide you in building even more robust and efficient applications moving forward.