☰
Current Page
Main Menu
Home
Home
Editing FastAPI - After the Getting Started
Edit
Preview
H1
H2
H3
default
Set your preferred keybinding
default
vim
emacs
markdown
Set this page's format to
AsciiDoc
Creole
Markdown
MediaWiki
Org-mode
Plain Text
RDoc
Textile
Rendering unavailable for
BibTeX
Pod
reStructuredText
Help 1
Help 1
Help 1
Help 2
Help 3
Help 4
Help 5
Help 6
Help 7
Help 8
Autosaved text is available. Click the button to restore it.
Restore Text
https://medium.com/@marcnealer/fastapi-after-the-getting-started-867ecaa99de9 # FastAPI: After the Getting Started | by Marc Nealer | Medium  [Marc Nealer](https://medium.com/@marcnealer) If you’ve read any of my other articles, then you will know that I’m a big fan of FastAPI. I used Django for years and other frameworks didn’t really do much to knock if off the number one list, but FastAPI is something special. In using FastAPI, there are a few things you need to take more notice of, in-order to push your projects forward. Lifespan Events --------------- FastAPI has the ability to attach tasks that will be run when the app is started and a second set to run when the app stops. Bit like middleware, but wrapping the whole app, not requests. This was originally controlled by the .on\_event() argument, where you could set startup or shutdown. ```python from fastapi import FastAPI app = FastAPI() @app.on_event("startup) def startup_event(): with open("log.txt", mode="a" as log: log.write("Application Started") @app.on_event("shutdown") def shutdown_event(): with open("log.txt", mode="a") as log: log.write("Application shutdown") ``` This system is being depreciated though. Its still there and you can use it, but they have added a newer version and YOU CAN’T USE BOTH. The new system allows you to write a context manager and pass this to the FastAPI() object as an argument. If you don’t know, a Context Manager allows code to be run before a block of code and after, such as opening and closing a database connection. ```python from fastapi import FastAPI, Request import settings from contextlib import asynccontextmanager from typing import AsyncGenerator from tortoise.contrib.fastapi import RegisterTortoise @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: # app startup async with RegisterTortoise( app, db_url=settings.db_url, modules=settings.db_modules, generate_schemas=True, add_exception_handlers=True, ): # db connected yield # app teardown main_app = FastAPI(lifespan=lifespan) ``` So using this, your startup tasks are added before the yield and shutdown, after the yield. Its quite a neat little idea. Here I’m registering the tortoise ORM at the start of the application. Removal is automatic, so no shutdown action is required. This is an important stage to understand since FastAPI doesn’t come with any database ORM, or connections. The lifespan is where you need to put these. Also since the function exists for the lifespan on the application, you could start asyncio tasks, which will run the background. Remember, you can’t use both systems. If you set the lifespan parameter, all on\_event() requests will be ignored. Its also worth remembering that on\_event() is depreciated, so if your currently using it, you might want to do a quick recode. Settings -------- Not really part of FastAPI, but it really needs to be there for all web applications. We need to have a place where the application settings are kept and you can’t put them in your main app file. Placing your settings in your app file results in circular import issues. You need to put them in a different file. I would also advise using python dotenv, so your secret stuff is in env files and not saved to your repo ```python from dotenv import find_dotenv, dotenv_values import pathlib import string file_path = pathlib.Path().cwd() static_dir = str(pathlib.Path(pathlib.Path().cwd(), "static")) config = dotenv_values(find_dotenv(".test_fastapi_config.env")) db_user = config.get("DB_USER") db_password = config.get("DB_PASSWORD") db_host = config.get("DB_HOST") db_name = config.get("DB_NAME") db_port = config.get("DB_PORT") db_url = f"asyncpg://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" db_modules = {"models": ["models"]} max_age = 3600 ``` Middleware ---------- If your coming from a full stack framework like Django, then middlware is something someone else writes. Not so with FastAPI. There are a couple of Middleware modules available, but not much. They document also mentions the middleware functions from Starlette. I tried them and they didn’t work!! Creating middleware is really easy though. FastAPI really makes it so. Here is a simple, but useful example. I decided to mimic Django’s session system, where the session data is stored in the database and not in the cookie, so the cookie just has a reference to the db record only ```python from fastapi, import FastApi, Request main_app = FastAPI(lifespan=lifespan) @main_app.middleware("http") async def session_middleware(request: Request, call_next): cookie_val = request.cookies.get("session") if cookie_val: request.scope['session'] = cookie_val else: request.scope['session'] = "".join(random.choices(settings.session_choices, k=128)) response = await call_next(request) response.set_cookie("session", value=request.session, max_age=settings.max_age, httponly=True) return response ``` As you can see the middleware function gets the request and call\_next. the call\_next is the actual view function, so the code block before “await call\_next(request) is run before the view. The response object is returned from this, so you can then make changes before its finally sent to the user. All I’m doing here is getting or creating the session key before the view and then making sure its in the cookie when the response is sent. Exception Handling ------------------ This is a brilliant feature, and one to take notice of. You can add listeners for Python exceptions and run a view to return a response to the user. This lets you handle exceptions without sending a http 500 status code. ```python from fastapi import FastAPI, Request app = FastAPI() class PermissionFailedException(Exception): def __init__(self, permissions: list): self.permissions = permissions def permission_failed_handler(request: Request, exc: PermissionFailedException): """shows an error page if the users authentication scope fails to meet the requirements""" return HTMLResponse(content="templates/permission_failed.html", status_code=401) app.add_exception_handler(PermissionFailedException, permission_failed_handler) ``` While this is great for catching the unexpected, it works best when paired with Dependency Injections. If you have a Dependency defined to run globally, on the router, or on the decorator, it cannot return any values. It can only finish ok, or raise an exception. To deal with this, create custom exceptions and then write an exception handler to deal with redirects etc. Dependency Injections --------------------- Ok, the documentation goes into these in a lot of detail, but at times it gets a little confusing. One area is the one I’ve just mentioned. Dependencies can be set in a lot of different places. If you place the Dependency in the view path then its expected to return some values as a result. Anywhere else, it either works or fails. My first thoughts on this was that was a little useless, but when you pair this with the exception handlers, you begin to see a lot if uses for them. Its best to think of it as having two different Dependency Injection systems. One works on preparing data, and the other validates something before the view runs. Background Tasks ---------------- If you have longer running tasks that your sending to run in a background task manager, then you might be able to this instead, but you need to understand whats going on. In most systems, a request task is completed when a response object is returned to the user i.e, request comes in, start a task, process, return response, finished. FastAPI treats tasks in a slightly different way because of its async nature. The “return” statement calls a response object, that formats the response and sends it to the user, but fastAPI then looks to see if there are any async tasks started and not yet completed. It doesn’t end the routine until those tasks have completed as well. This means the tasks can still be running AFTER the user has gotten a response. ```python from fastapi import BackgroundTasks, FastAPI from asyncio import sleep app = FastAPI() def write_notification(email: str, message=""): await sleep(1) with open("log.txt", mode="w") as email_file: content = f"notification for {email}: {message} sent after the user gets a response" 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": "Response sent before the notification is completed"} ``` A silly example, but it shows what happens. “background\_tasks.add\_task()” starts an asyncio task, and then the return statement is run. However return calls the default response object, which sends the response to the user and then waits for the background task to complete, before the coroutine completes. Tasks like sending emails, writing out files, to disk, or doing a lot of DB updates can be pushed into these tasks and the response sent to the user, before they have completed. just remember that your response is already sent, so you can send a response if the task fails. There is an option in the base response class for setting a background task, but it acts a little differently. With “background\_tasks”, the user response and middleware are run and the response sent. With the Request object, middleware won’t be run until the task is completed. Thus if your using middleware to alter responses, then this will interfere with your task. Learn Pydantic -------------- I can’t stress this one enough. You can skip by with a basic understanding of Pydantic, but its really not enough. Study the module as its fantastic and can bring so much more to your application. FastAPI is not Starlette ------------------------ In many areas, we pull objects from Starlette to use. In fact many items such as the Request class are just links to the Starlette class. With that said, don’t be fooled into thinking all the functions in Starlette will work in FastAPI. They won’t. Go Beyond API’s --------------- FastAPI can handle html responses as well as API’s. There are modules, forwarded from Starlette on handling jinja2, but these are think wrappers. Its easy to add in any templating engine you want. Final Thoughts -------------- What makes FastAPI special is that it contains all the elements for you to create a fantastic application, but unlike Django, its doesn’t come with preset ways of doing things. Also adding the Batteries that Django has, or some of them is really quite easy.
Uploading file...
Edit message:
Cancel