Difference between revisions of "FastAPI"

From Colettapedia
Jump to navigation Jump to search
Line 129: Line 129:
 
Can use standard python types, or more funky python types like <code>datetime.timedelta</code>
 
Can use standard python types, or more funky python types like <code>datetime.timedelta</code>
 
===Query===
 
===Query===
====Implicit====
+
* Implicit way: params given in function args are by default query params. Can declare type and default value/optionality
* params given in function args are by default query params. Can declare type and default value/optionality
 
====Explicit====
 
 
* If you need additional validation, set arg default value as type fastapi.Query
 
* If you need additional validation, set arg default value as type fastapi.Query
 
** min/max length
 
** min/max length
Line 145: Line 143:
 
** Can define list of multiple values as default
 
** Can define list of multiple values as default
 
===Path===
 
===Path===
* Path paramters are always required as it has to be part of the path
+
* Implicit way: Path paramters are always required as it has to be part of the path
====Implicit====
+
** Just put the path parameter inside the curly braces on the path argument, e.g., "@app.get("/items/{item_id}")"
* 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 <code>fastapi.Path</code>()
====Explicit====
 
* Include the parameter explicitly as a function argument of type <code>fastapi.Path</code>()
 
 
* order of the different parameters doesn't matter
 
* 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.
 
** 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.
Line 155: Line 151:
 
** gt/lt, ge/le
 
** gt/lt, ge/le
 
===Body===
 
===Body===
====Implicit====
 
 
* subclass the <code>pydantic.BaseModel</code> object
 
* subclass the <code>pydantic.BaseModel</code> object
 
* declare class attributes
 
* declare class attributes
 
* In function args declare type as your new subclass
 
* In function args declare type as your new subclass
====Explicit====
 
 
* <code>fastapi.Body</code> - Use to declare the entire unit of body params in the subclass to be optional, or to setup additional validation, metadata.
 
* <code>fastapi.Body</code> - 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
 
** Additional optional args to Body: example
Line 167: Line 161:
 
* include a member class Config within subclass class attributes
 
* include a member class Config within subclass class attributes
 
** <code>schema_extra</code> to provide documentation example
 
** <code>schema_extra</code> 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 <code>multipart/form-data</code>
 +
* 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==
 
==Response models==
Line 174: Line 182:
 
** Can dump contents of model A's data instance directly into the constructor for modelB's instance another using <code>ModelB( **model_a.dict() )</code>. The filtering will also happen at this step.
 
** Can dump contents of model A's data instance directly into the constructor for modelB's instance another using <code>ModelB( **model_a.dict() )</code>. The filtering will also happen at this step.
 
** Can have SUB-subclasses to reduce code duplication.
 
** Can have SUB-subclasses to reduce code duplication.
 +
 +
==Using the Request Directly (GET IP ADDRESS)==
 +
* <code>fastapi.Request</code> is just alias to <code>starlette.Request</code>
 +
* Include <code>request: Request</code> 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* <code>request.client.host</code>,
 +
** request.cookies.get('mycookie')
 +
 +
==Background Tasks==
 +
* Send email notifications, process data, write to logs, etc AFTER returning a response
 +
# Import <code>fastapi.BackgroundTasks</code>
 +
# Define a parameter <code>background_tasks</code> 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)
  
 
==Other stuff I need to learn==
 
==Other stuff I need to learn==
 
* <code>fastapi.Cookie</code>
 
* <code>fastapi.Cookie</code>
 
* the role of header parameters
 
* the role of header parameters

Revision as of 00:39, 27 April 2020

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...
  • 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
  • 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
  • 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.

Using the Request Directly (GET IP ADDRESS)

  • fastapi.Request is just alias to starlette.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
  1. Import fastapi.BackgroundTasks
  2. Define a parameter background_tasks in the path operation function with type declaration of BackgroundTasks
  3. 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)

Other stuff I need to learn

  • fastapi.Cookie
  • the role of header parameters