Structure of an endpoint file
Each endpoint is defined in its own file inside app/endpoints/<version of
endpoint>/<endpoint-name>.py
(for details about versioning see
versioning).
Each endpoint has his own input- and output-model definition inside
app/endpoints/<version of endpoint>/models.py
(for details see
models).
A basic endpoint looks like:
"""Endpoint definition for example.""" from fastapi import APIRouter from fastapi import Body from fastapi_serviceutils.app import Endpoint from fastapi_serviceutils.app import create_id_logger from starlette.requests import Request from app.endpoints.v1.models import Example from app.endpoints.v1.models import GetExample ENDPOINT = Endpoint(router=APIRouter(), route='/example', version='v1') SUMMARY = 'Example requests.' EXAMPLE = Body(..., example={'name': 'John Doe'}) @ENDPOINT.router.post('/', response_model=Output, summary=SUMMARY) async def example(request: Request, params: GetExample = EXAMPLE) -> Example: """Example docstring for an endpoint. # Parameters: request (Request): the original request. params (GetExample): the request-params converted to an instance of `GetExample`. # Returns: (Example) the processed result for this endpoint as an instance of `Example`. """ _, log = create_id_logger(request=request, endpoint=ENDPOINT) log.debug(f'received request for {request.url} with params {params}.') age, city, street, housenumber = await some_extraction_function(name=params.name) return Example( name=params.name, age=age, city=city, street=street, housenumber=housenumber )
ENDPOINT
The variable ENDPOINT
defines the endpoint and is required and used for
function set_version_endpoints
from fastapi-serviceutils
(inside
app/endpoints/<version of endpoint>/__init__.py
and
app/endpoints/__init__.py
).
ENDPOINT.route
The route
-attribute of ENDPOINT
defines the route-suffix for this endpoint.
It is modified inside app/endpoints/<version of endpoint>/__init__.py
:
from fastapi_serviceutils.app.endpoints import set_version_endpoints from app.endpoints.v1 import example ENDPOINTS = set_version_endpoints( endpoints=[example], version='v1', prefix_template='/api/{version}{route}' )
This means this endpoint is available on route /api/v1/example
.
Inside app/endpoints/__init__.py
an additional route for this endpoint under
latest
is added:
"""Define routers to be available as endpoints in the service.""" from fastapi_serviceutils.app.endpoints import set_version_endpoints from app.endpoints.v1 import ENDPOINTS as v1 LATEST = set_version_endpoints( endpoints=v1, version='latest', prefix_template='{route}' ) ENDPOINTS = LATEST + v1
ENDPOINT.version
The attribute version
of ENDPOINT
is the same like the version-folder of
this endpoint.
SUMMARY
The SUMMARY
is required for the OpenAPI-documentation of the endpoint.
It should describe in short what this endpoint does.
EXAMPLE
The EXAMPLE
is an instance of fastapi.Body
and is used as example value
for the OpenAPI-documentation of the endpoint.
The endpoint-function
decorator
The decorator ENDPOINT.router.post
register the endpoint for the APIRouter.
In this example it is used like:
@ENDPOINT.router.post('/', response_model=Output, summary=SUMMARY)
Here we define the response_model to be used for the output of this endpoint
and that we want to show the summary as defined in SUMMARY
for the
OpenAPI-documentation of this endpoint.
parameters of the endpoint-function
async def example(request: Request, params: GetExample = EXAMPLE) -> Example:
requires the parameters request
as an instance of
starlette.requests.Request
.
This request-variable is used to create the request-id for the current request
to be used in the logger to be able to differentiate between different requests
inside the log-file.
It is used in:
_, log = create_id_logger(request=request, endpoint=ENDPOINT) log.debug(f'received request for {request.url} with params {params}.')
Logging with this log
uses the request-id of this request in log-entries.
The next parameter params
contains the request-parameters passed to the
endpoint converted to an instance of the defined input-model (in this case
GetExample
).
Because this is a class, you can access its attributes using .
.
For example we can access the request-parameter name
in this example using
params.name
.
The type annotation of the return value of this function shows (like in the
decorator response_model
) that this function returns an instance of the
output-model (in this case Example
).
Note
In fact the function does not have to return an instance of the output-model, it can also be a dict containing the same content as an instance of the output-model.
docstring
The docstring is defined using markdown to be able to extract it using
pydoc-markdown when running make docs
.
processing logic
The part
age, city, street, housenumber = await some_extraction_function(name=params.name)
is only an example.
Here you would place the processing logic of the endpoint.
Try to use async-functions here and call them with await
to let your
endpoint be as asynchronous as possible.
combining the output
Finally (after the processing logic) we combine the output of the endpoint to be returned to the client. In this example this would be:
return Example( name=params.name, age=age, city=city, street=street, housenumber=housenumber )