Uploaded by Aaditya Dulal

FastAPI Guide: Python Web Framework

advertisement
FastAPI
Important topics
WSGI - Web Server Gateway Interface (WSGI) is a mediator between the web server
and python web application. WSGI forwards the requests sent by the users to the
web server to the python web applications. In WSGI, applications take a single
request and return a response at a time. WSGI does not support async / await,
HTTP/2, and web sockets.
A simple example of a WSGI application that returns a static “hello world” page is
given below.
def simple_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']
● The above application interface accepts two arguments, environ and
start_response.
● environ is a dictionary populated by the server for each request received
from the client.
● start_response is a function that takes two arguments; HTTP status code
and response header.
● HTTP headers are wrapped in a list of tuples [("Header Name", "Header
value")]
Status and response headers are sent to the server using the supplied
function i.e. start_response.
● Now, the response is returned in a list using the return keyword.
WGSI servers - Gunicorn, uWSGI, Apache, etc. WSGI frameworks - Bottle, Flask,
Django, etc.
ASGI - Asynchronous Server Gateway Interface (ASGI), which is like WSGI is a
model made to implement an interface between async-capable python web servers,
frameworks, and applications and is applicable across every asyncio framework.
A simple example of an application using asynchronous function is given below:
async def application(scope, receive, send):
event = await receive()
...
await send({"type": "websocket.send", ...})
● The above application interface accepts three arguments; scope, receive and
send.
● scope - a dictionary containing information about the incoming connection.
● receive - A channel on which to receive incoming messages from the server.
● send - A channel on which to send outgoing messages to the server.
Async - Asynchronous (Async) is a way of handling tasks differently than handling
them one by one i.e. multiprocessing at the same time by running the tasks
concurrently. While there are a lot of tasks queued to be completed, asynchronous
code does not wait for a single task to be completed and move on to the next task
rather takes every task into account and carries out another task once the previous
task is completed. However, it doesn’t carry out multiple tasks at the same time.
Gunicorn - Green Unicorn, often shortened as “Gunicorn” is a pure-python WSGI
HTTP server. It is fast, broadly compatible with several python frameworks and
applications, and lightweight while running.
Uvicorn - Uvicorn is an ASGI server implementation, based on uvloop and httptools
and emphasizes speed. It provides support for HTTP/2 and Websockets, which
cannot be handled by WSGI.
Differences between Uvicorn and Gunicorn
The differences between Uvicorn and Gunicorn are as follows:
Uvicorn
Gunicorn
Uvicorn is implemented on ASGI, so it Gunicorn is implemented on WSGI
supports asynchronous applications.
It supports HTTP/2 and web sockets
It does not support HTTP/2 and web
sockets
It works for the applications that are It does not work for the applications
built in asyncio frameworks.
that are built in asyncio frameworks.
Introduction
FastAPI is a high-performance web framework used to build APIs using python.
Key features of FastAPI include:
● Fewer bugs.
● Great editor support, short and easy to understand which makes code writing
fast.
● Automatic documentation.
Why choose Fast API over Django/flask?
Async
FastAPI is implemented on Asynchronous Server Gateway Interface (ASGI) which
supports asynchronous programming and allows us to create both synchronous and
asynchronous applications.
Type Hints
Like other languages like Java and Typescript, python has adapted Type hints since
version 3.5. Type hints help catch the errors, autocompletion, improve the readability
of the code as well as help in developing and debugging the code that interacts with
the API. Being based on type hints, Fast API meets the real standard and is the
future.
Built-in Documentation
In FastAPI, OpenAPI (Swagger) and Redoc are built-in which automatically create
interactive API documentation with UI for every endpoint we create. It saves a lot of
time and makes debugging relatively easier. For achieving the same task of creating
documentation, other frameworks like Django or flask would require other libraries
and the hassle of setting up and creating manual documentation.
Fast / high-performance
Fast API is faster when compared to other python frameworks like Django and flask.
It is speed-oriented and can be utilized to build high-performance and scalable
applications.
Operation/Request Methods
GET - To retrieve a page or some information, a GET request is used.
POST - A POST method is generally used to add something to a database. For
example: Posting a new user login, as the name suggests.
PUT - This request method is used to update something already present in the
database.
DELETE - This request method is used to update information from the database.
In addition, there are other operations such as OPTIONS, HEAD, PATCH, AND
TRACE.
Path Parameters
# path parameter
@app.get("/get-item/{item_id}")
def get_item(
item_id: int = Path(None, description="Enter the ID of the
item.", gt=0, lt=3)
):
return inventory[item_id]
● This function takes a path parameter i.e. {item_id} from the path / endpoint.
● Path is used to set the description and set the constraints, less than (lt) and
greater than (gt) to prevent entering invalid IDs.
# multiple path parameters
@app.get("/get-item/{item_id}/{name}")
def get_item(item_id: int, name: str):
return inventory[item_id]
● This function takes multiple path parameters i.e. {item_id}, {name} from the
path / endpoint.
Query Parameters
# query parameter
@app.get("/get-item-name")
def get_item(name: str):
for item_id in inventory:
if inventory[item_id].name == name:
return inventory[item_id]
return {"Data": "Not found"}
●
If there are no parameters in the path/endpoint, the program interprets the
argument passed in the function as a query parameter by default.
# optional query parameter
@app.get("/get-items")
def get_item(name: Optional[str] = None):
for item_id in inventory:
if inventory[item_id]["Name"] == name:
return inventory[item_id]
return {"Data": "Not found"}
● If there are no parameters in the path/endpoint, the program assumes that the
argument passed in the function is a query parameter by default.
# multiple query parameters
@app.get("/get-name-test")
def get_item(*, name: Optional[str] = None, test: int):
for item_id in inventory:
if inventory[item_id]["Name"] == name:
return inventory[item_id]
return {"Data": "Not found"}
● If there are no parameters in the endpoint, the program assumes that it is a
query parameter by default.
Request Body
The request that has to be sent by the user to the API can be declared by using the
Pydantic model.
● After importing BaseModel from pydantic, a data model has to be created by
declaring the data types.
class Item(BaseModel):
name: str
price: int
category: Optional[str] = None
● Then, a parameter has to be passed declaring the data type to point to the
pydantic model.
@app.post("/create-item")
def create_item(item: Item):
return item
● FastAPI will automatically do the job of converting the pydantic model object
to JSON data and vice-versa.
# Request Body + Path Parameter
@app.put("/update-item/{item_id}")
def add_item(item_id: int, item: Item):
if item_id not in inventory:
return {"Error": "Item does not exist."}
inventory[item_id].update(item)
return inventory[item_id]
● Along with the request body, the path parameters can also be passed as
shown above.
# Request Body + Path Parameter + Query Parameter
@app.post("/add-item/{item_id}")
def add_item(item_id: int, name: str, item: Item):
if item_id in inventory:
return {"Error": "Item already exists"}
inventory[
item_id
] = item
return inventory[item_id], {"Added by": name}
● Also, request body, query parameters, and path parameters can be passed at
once.
Validation of Parameters
Query Parameters
Validation of the query parameters can be done using Query. It allows us to add
validation, additional information as well as other metadata to the query parameters.
● After importing Query, the default value of the parameter has to be set to
Query name: Optional[str] = Query(None). This way different validations can
be added to the parameters as shown as follows.
# String validation of optional query parameter
@app.post("/add-items/{item_id}")
def add_item(item_id: int, item: Item, name: Optional[str] =
Query(None, min_length=3, max_length=50, regex="staff.")):
if item_id in inventory:
return {"Error": "Item already exists"}
inventory[
item_id
] = item # {"name": item.name, "category": item.category,
"price": item.price}
return inventory[item_id], {"Added by": name}
● However, validating a “required” parameter will require us to pass us an
Ellipsis (...) item_id: int, item: Item, name: str = Query(..., min_length=3)
# String validation of required query parameter
@app.post("/add-item-name/{item_id}")
def add_item(item_id: int, item: Item, name: str = Query(...,
min_length=3, max_length=50, regex="staff.")):
if item_id in inventory:
return {"Error": "Item already exists"}
inventory[
item_id
] = item # {"name": item.name, "category": item.category,
"price": item.price}
return inventory[item_id], {"Added by": name}
+
● +-While passing multiple query parameters in a list, Query can be used to set
default values for the parameter, name: Optional[List[str]] = Query(["Rock",
"Pop"])
# Multiple query parameters / Query parameters list (Default)
@app.post("/add-genre")
def add_name(name: Optional[List[str]] = Query(["Rock", "Pop"])):
name_dic = {"name": name}
return name_dic
Path Parameters
● Similar to using Query to validate query parameters, path parameters can be
validated using Path. Constraints like gt (greater than) and lt (less than )are
used to perform a check against the value passed in the parameter.
# Path Parameters Validation
@app.get("/get-item/{item_id}")
def get_item(
item_id: int = Path(None, description="Enter the ID of the
item.", gt=0, lt=3)
):
return inventory[item_id]
● When passing multiple parameters in a function, and we place an optional
parameter before a required parameter, it throws an error. To prevent this, we
can re-order the parameters and place the required one first or simply add an *
before the parameters.
@app.get("/get-item/{item_id}/{name}")
def get_item(name: str, item_id: int):
return inventory[item_id]
● The above function can be written this way by adding an * before the
parameters.
def get_item(*, name: Optional[str] = None, item_id: int):
return inventory[item_id]
● Or, this way by re-ordering the parameters and placing the required ones first.
def get_item(item_id: int, name: Optional[str] = None):
return inventory[item_id]
Download