|
4 | 4 |
|
5 | 5 | # Introduction |
6 | 6 |
|
7 | | -This project provides collection of utilities for FastAPI framework as: |
| 7 | +This project provides collection of utilities for smooth integration of FastAPI framework with Google Cloud Platform services as logging and tracing. |
8 | 8 |
|
9 | | -* Custom Middlewares |
10 | | -* Exception Catchers |
11 | | -* Custom Routes |
| 9 | +The key features of this project are: |
| 10 | + |
| 11 | +* Logging to Stackdriver |
| 12 | +* Tracing to Stackdriver |
| 13 | +* Custom middleware for configuration of logging |
| 14 | +* Custom exception handlers treating HTTP and validation exceptions |
| 15 | +* Custom routes for documentation and favicon |
| 16 | +* Custom responses with statuses `success`, `warning` and `error` and standardized error messages |
12 | 17 |
|
13 | 18 | # Quick Start |
14 | 19 |
|
15 | 20 | This section shows how to use the utilities provided by this project: |
16 | 21 |
|
17 | 22 | ```python |
18 | | -from fastapi import FastAPI, Request |
| 23 | +"""File main.py with FastAPI app""" |
| 24 | +import os |
19 | 25 | from fastapi.exceptions import RequestValidationError |
20 | 26 | from starlette.exceptions import HTTPException |
21 | | - |
22 | | -from surquest.fastapi.utils.route import Route |
23 | | -from surquest.fastapi.utils.middleware import BasicMiddleware |
24 | | -from surquest.fastapi.utils.catcher import ( |
| 27 | +from fastapi import FastAPI, Request, Query |
| 28 | + |
| 29 | +# import surquest modules and objects |
| 30 | +from surquest.fastapi.utils.route import Route # custom routes for documentation and FavIcon |
| 31 | +from surquest.fastapi.utils.GCP.tracer import Tracer |
| 32 | +from surquest.fastapi.utils.GCP.logging import Logger |
| 33 | +from surquest.fastapi.schemas.responses import Response |
| 34 | +from surquest.fastapi.utils.GCP.middleware import LoggingMiddleware |
| 35 | +from surquest.fastapi.utils.GCP.catcher import ( |
25 | 36 | catch_validation_exceptions, |
26 | 37 | catch_http_exceptions, |
27 | 38 | ) |
28 | 39 |
|
| 40 | +PATH_PREFIX = os.getenv('PATH_PREFIX','') |
| 41 | + |
29 | 42 | app = FastAPI( |
30 | | - title="Sample REST API application", |
31 | | - openapi_url="/openapi.json" |
| 43 | + title="Exchange Rates ETL", |
| 44 | + openapi_url=F"{PATH_PREFIX}/openapi.json" |
32 | 45 | ) |
33 | 46 |
|
34 | 47 | # add middleware |
35 | | -app.add_middleware(BasicMiddleware) # this middleware writes request and response to the log |
| 48 | +app.add_middleware(LoggingMiddleware) |
36 | 49 |
|
37 | 50 | # exception handlers |
38 | 51 | app.add_exception_handler(HTTPException, catch_http_exceptions) |
39 | 52 | app.add_exception_handler(RequestValidationError, catch_validation_exceptions) |
40 | 53 |
|
41 | | -# custom routes |
42 | | -app.add_api_route(path="/", endpoint=Route.get_documentation, include_in_schema=False) |
| 54 | +# custom routes to documentation and favicon |
| 55 | +app.add_api_route(path=F"{PATH_PREFIX}/", endpoint=Route.get_documentation, include_in_schema=False) |
| 56 | +app.add_api_route(path=PATH_PREFIX, endpoint=Route.get_favicon, include_in_schema=False) |
| 57 | + |
| 58 | +# custom route to illustrate logging and tracing |
| 59 | +@app.get(F"{PATH_PREFIX}/users") |
| 60 | +async def get_users( |
| 61 | + age: int = Query( |
| 62 | + default=18, |
| 63 | + description="Minimal age of the user", |
| 64 | + example=30, |
| 65 | + |
| 66 | + ), |
| 67 | +): |
| 68 | + |
| 69 | + with Tracer.start_span("Generate users"): |
| 70 | + |
| 71 | + users = [ |
| 72 | + { "name": "John Doe", "age": 30, "email": "[email protected]"}, |
| 73 | + { "name": "Will Smith", "age": 42, "email": "[email protected]"} |
| 74 | + ] |
| 75 | + |
| 76 | + Logger.info('Found %s users', len(users), extra={"users": users}) |
| 77 | + |
| 78 | + with Tracer.start_span("Filtering users"): |
| 79 | + |
| 80 | + output = [] |
| 81 | + excluded = [] |
| 82 | + Logger.debug(F"Filtering users by age > {age}") |
| 83 | + |
| 84 | + for user in users: |
| 85 | + |
| 86 | + if user["age"] > age: |
| 87 | + output.append(user) |
| 88 | + else: |
| 89 | + excluded.append(user) |
| 90 | + |
| 91 | + Logger.debug( |
| 92 | + 'Number of excluded users: %s', len(excluded), |
| 93 | + extra={"excluded": excluded} |
| 94 | + ) |
| 95 | + |
| 96 | + return Response.set(data=output) |
| 97 | +``` |
| 98 | + |
| 99 | +The endpoint `/users` will return the following standard response: |
| 100 | + |
| 101 | +```json |
| 102 | +{ |
| 103 | + "info": { |
| 104 | + "status": "success" |
| 105 | + }, |
| 106 | + "data": [ |
| 107 | + { |
| 108 | + "name": "John Doe", |
| 109 | + "age": 30, |
| 110 | + |
| 111 | + }, |
| 112 | + { |
| 113 | + "name": "Will Smith", |
| 114 | + "age": 42, |
| 115 | + |
| 116 | + } |
| 117 | + ] |
| 118 | +} |
43 | 119 | ``` |
44 | 120 |
|
| 121 | +and the logs will are available in Google Cloud Platform console within Stackdriver Logging: |
| 122 | + |
| 123 | + |
| 124 | + |
| 125 | +as well as the traces are available in Google Cloud Platform console within Stackdriver Trace: |
| 126 | + |
| 127 | + |
| 128 | + |
45 | 129 |
|
46 | 130 | # Local development |
47 | 131 |
|
|
0 commit comments