FastAPI
Jump to navigation
Jump to search
Contents
- 1 Why fastAPI
- 2 Technologies
- 3 URL mapping
- 4 Getting stuff from requests: Path variables
- 5 Getting stuff from URL requests: Query parameters
- 6 Data Validation: Pydantic
- 7 Extract blob of JSON that came in from request
- 8 Creating responses
- 9 Implied and explicit Query, Path and Body Parameters
- 10 Response models
- 11 Using the Request Directly (GET IP ADDRESS)
- 12 Background Tasks
- 13 Containerization
- 14 Other stuff I need to learn
Why fastAPI
- a free UI to test your rest api - interactive API documentation systems, OpenAPI (Swagger) and JSON Schema standards
- Supposedly security and authentication is integrated
- Supposedly fast
- Editor support, useful for code editor autocompletion
Technologies
- REST
- how you form your API
- Representational State Transfer
- Has an HTTP verb, a URL which related to a bit of data, and a parameterization of that
- Async
- How you deliver it
- Co-operative multitasking
- Puts the onus on you to decide when the rest of your computer should be able to do stuff
- Easy to get wrong
- Easy to write code that will block the whole web application rather than make just one request slow
- Excels in that it's able to deal with thousands and thousands of network sockets
- Dependency Injection (DI)
URL mapping
app = FastAPI() @app.get( "/" ) def root( ... ): return {"greeting": "Hello world"} app.include_router( events.router, prefix="/events"...)
router = APIRouter() @router.post( "/", ..., status=201) def create_object( ... ): ...
Getting stuff from requests: Path variables
- given this info
GET /events/1234?detail=full
Authorization: Token ABCD1234
- implicit way
@router.get( "/event/{id}" ) def get_object( id: int ): ...
- more explicit way - make things be required
from fastapi import Path from pydantic import Required @router.get( "/event/{id}" ) def get_object( id: int = Path(Required) ): ...
Getting stuff from URL requests: Query parameters
- given this info
GET /search/?text=something
Authorization: Token ABCD1234
- implicit way
@router.get( "/search" ) def search( text: str = None, offset: int = 0, limit: int = 100 ): ...
- more explicit way - make things be required
from fastapi import Query @router.get( "/search" ) def search( text: str = Query( default=None, max_length=20 ), offset: int = Query( 0 ), limit: int = Query( 100 ) ):
Data Validation: Pydantic
- Is this valid data??
- Throws helpful error messages if the data isn't formatted correctly
- "This field is required, but you didn't specify it."
from datatime import date as DateType from enum import Enum from pydantic import BaseModel class Types( Enum ): done = 'DONE' cancelled = 'CANCELLED' class Event( BaseModel ): date: DateType type: Types text: str
Extract blob of JSON that came in from request
from pydantic import Required @router.post( "/events/" ) def create_object( event: Event = Required ): ...
Creating responses
- If you need to retun something, just return simple Python objects. The framework renders them to JSON for you
@app.get( "/" ) def root( ... ): return {"greeting": "Hello World" } @app.post( "/events/". response_model=Event, status_code=201 ) def create_object( event: Event = Required ): ... return { "date": "2019-06-02", "type": "DONE", "text": "some stuff got done" }
Implied and explicit Query, Path and Body Parameters
Can use standard python types, or more funky python types like datetime.timedelta
Query
- Implicit way: params given in function args are by default query params. Can declare type and default value/optionality
- If you need additional validation, set arg default value as type fastapi.Query
- min/max length
- regex
- Declare more metadata
- title
- description
- alias
- deprecated=True
- set default as first arg to fastapi.Query()
- make required by using elipsis as first arg
- Can allow user to reuse query key by declaring type as typing.List[<type>]
- Can define list of multiple values as default
Path
- Implicit way: Path paramters are always required as it has to be part of the path
- Just put the path parameter inside the curly braces on the path argument, e.g., "@app.get("/items/{item_id}")"
- Explicit way: Include the parameter explicitly as a function argument of type
fastapi.Path
() - order of the different parameters doesn't matter
- Can have first argument of function by * to let python know that all arguments following it should be taken as kwargs, which will allow function arg reordering.
- validations
- gt/lt, ge/le
Body
- subclass the
pydantic.BaseModel
object - declare class attributes
- In function args declare type as your new subclass
fastapi.Body
- Use to declare the entire unit of body params in the subclass to be optional, or to setup additional validation, metadata.- Additional optional args to Body: example
pydantic.Field
- Use Field for additional validation on individual members of the BaseModel subclass- Model attributes/validation/metadata declared via Field() function args include title, max_length, description, gt,
example="Foo"
, etc...
- Model attributes/validation/metadata declared via Field() function args include title, max_length, description, gt,
- can nest list of Body Parameters withing body parameters using typing.List, typing.Set (converts collections with duplicate data into set of unique items), typing.Dict[int, float] for example, etc...
- include a member class Config within subclass class attributes
schema_extra
to provide documentation example
Form
- pass
File
- File inherits from Form
- Files are uploaded as "Form data". Form data has special encoding that;s different from JSON
- Thus the header
multipart/form-data
- Thus the header
- UploadFile has following attributes
- filename
- content_type
- file - an object with a file-like interface
- async methods that need to be awaited if your function is async
- contents = await myfile.read()
- write, seek, and close
Response models
- Declare a response model for validation and documentation purposes. Pydantic will also filter out any members not declared in the model.
- Can also leave out unset params in response using arg
response_model_exclude_unset=True
- Can also leave out unset params in response using arg
- Typical to have multiple related models.
- Can dump contents of model A's data instance directly into the constructor for modelB's instance another using
ModelB( **model_a.dict() )
. The filtering will also happen at this step. - Can have SUB-subclasses to reduce code duplication.
- Can dump contents of model A's data instance directly into the constructor for modelB's instance another using
Using the Request Directly (GET IP ADDRESS)
fastapi.Request
is just alias tostarlette.Request
- Include
request: Request
in the function args - This is how you get access to unvarnished, unvalidated request as it comes in from client
- request attributes
- request.method
- request.url, request.url.path, request.url.port, request.url.scheme
- request.headers['content-type'] (e.g.)
- request.query_params['search'] (e.g.)
- request.path_params['username'] (e.g.)
- request.client, *HOSTNAME OR IP ADDRESS*
request.client.host
, - request.cookies.get('mycookie')
Background Tasks
- Send email notifications, process data, write to logs, etc AFTER returning a response
- Import
fastapi.BackgroundTasks
- Define a parameter
background_tasks
in the path operation function with type declaration of BackgroundTasks - Use background_tasks.add_task( function, args, kwargs )
- Use dependency injection to hang a background task on the queue anywhere on the call stack (not just within the path operation function)
Containerization
Single Server Docker Image
- uvicorn-gunicorn-fastapi-docker - Docker image with Uvicorn managed by Gunicorn with FastAPI
- handles replication using a process manager (like Gunicorn with Uvicorn workers) in each container
Env Vars
MODULE_NAME
MODULE_NAME=app.main
by default if there's a file/app/app/main.py
MODULE_NAME=main
by default if there's a file/app/main.py
MODULE_NAME=custom_app.custom_main
by default if there's a file/app/custom_app/custom_main.py
VARIABLE_NAME
- named value inside the module MODULE_NAME that is an instance of class
FastAPI.FastAPI
, e.g., within main.pyfoo = FastAPI()
VARIABLE_NAME=foo
Other stuff I need to learn
fastapi.Cookie
- the role of header parameters