fastapi-commons 0.8.4__tar.gz → 0.8.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_commons-0.8.6/AUTHORS.md +3 -0
- fastapi_commons-0.8.4/CHANGELOG.rst → fastapi_commons-0.8.6/CHANGELOG.md +3 -8
- fastapi_commons-0.8.6/PKG-INFO +212 -0
- fastapi_commons-0.8.6/README.md +179 -0
- fastapi_commons-0.8.6/pyproject.toml +170 -0
- fastapi_commons-0.8.4/pyproject.toml → fastapi_commons-0.8.6/pyproject.toml.orig +15 -24
- fastapi_commons-0.8.4/.coveragerc +0 -28
- fastapi_commons-0.8.4/.env_template +0 -7
- fastapi_commons-0.8.4/.github/workflows/checks.yml +0 -63
- fastapi_commons-0.8.4/.github/workflows/python-publish.yaml +0 -47
- fastapi_commons-0.8.4/.github/workflows/release-on-tag-push.yml +0 -52
- fastapi_commons-0.8.4/.gitignore +0 -52
- fastapi_commons-0.8.4/.pre-commit-config.yaml +0 -35
- fastapi_commons-0.8.4/.python-version +0 -1
- fastapi_commons-0.8.4/AUTHORS.rst +0 -5
- fastapi_commons-0.8.4/PKG-INFO +0 -36
- fastapi_commons-0.8.4/README.md +0 -2
- fastapi_commons-0.8.4/README.rst +0 -4
- fastapi_commons-0.8.4/setup.cfg +0 -4
- fastapi_commons-0.8.4/src/fastapi_commons.egg-info/PKG-INFO +0 -36
- fastapi_commons-0.8.4/src/fastapi_commons.egg-info/SOURCES.txt +0 -39
- fastapi_commons-0.8.4/src/fastapi_commons.egg-info/dependency_links.txt +0 -1
- fastapi_commons-0.8.4/src/fastapi_commons.egg-info/requires.txt +0 -16
- fastapi_commons-0.8.4/src/fastapi_commons.egg-info/scm_file_list.json +0 -36
- fastapi_commons-0.8.4/src/fastapi_commons.egg-info/scm_version.json +0 -8
- fastapi_commons-0.8.4/src/fastapi_commons.egg-info/top_level.txt +0 -1
- fastapi_commons-0.8.4/uv.lock +0 -1890
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/LICENSE +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/__init__.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/auth.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/conf.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/handlers.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/helpers.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/instrumentation.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/log/__init__.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/log/filters.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/middleware/__init__.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/middleware/correlation_id.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/middleware/log_context.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/src/fastapi_commons/middleware/prometheus.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/tests/__init__.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/tests/conftest.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/tests/unit/__init__.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/tests/unit/test_auth.py +0 -0
- {fastapi_commons-0.8.4 → fastapi_commons-0.8.6}/tests/unit/test_poc.py +0 -0
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
Changelog
|
|
3
|
-
=========
|
|
1
|
+
# Changelog
|
|
4
2
|
|
|
5
|
-
Version 0.1.0
|
|
6
|
-
=============
|
|
3
|
+
## Version 0.1.0
|
|
7
4
|
|
|
8
5
|
- Breaking change: made absolute path generation for object storage to be optional.
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
Version 0.0.1
|
|
12
|
-
=============
|
|
7
|
+
## Version 0.0.1
|
|
13
8
|
|
|
14
9
|
- Initial commit
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-commons
|
|
3
|
+
Version: 0.8.6
|
|
4
|
+
Summary: Re-usable FastAPI code
|
|
5
|
+
Keywords:
|
|
6
|
+
Author: Oleg Korsak
|
|
7
|
+
Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
|
|
8
|
+
License-Expression: GPL-3.0-only
|
|
9
|
+
License-File: AUTHORS.md
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Requires-Dist: aiohttp[speedups]>=3.14.3,<3.15.0
|
|
14
|
+
Requires-Dist: asyncpg~=0.31.0
|
|
15
|
+
Requires-Dist: fastapi~=0.141.1
|
|
16
|
+
Requires-Dist: msgspec~=0.21.1
|
|
17
|
+
Requires-Dist: pydantic[email]~=2.13.5
|
|
18
|
+
Requires-Dist: pydantic-settings~=2.15.0
|
|
19
|
+
Requires-Dist: python-jose~=3.5.0
|
|
20
|
+
Requires-Dist: sqlalchemy[asyncio]~=2.1.1
|
|
21
|
+
Requires-Dist: opentelemetry-api~=1.45.0
|
|
22
|
+
Requires-Dist: opentelemetry-sdk~=1.45.0
|
|
23
|
+
Requires-Dist: opentelemetry-exporter-otlp~=1.45.0
|
|
24
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi~=0.65b0
|
|
25
|
+
Requires-Dist: opentelemetry-instrumentation-logging~=0.65b0
|
|
26
|
+
Requires-Dist: prometheus-client~=0.26.0
|
|
27
|
+
Requires-Dist: python3-commons[authn]~=0.24.6
|
|
28
|
+
Requires-Dist: starlette~=1.7.0
|
|
29
|
+
Requires-Python: >=3.14.0, <3.15.0
|
|
30
|
+
Project-URL: Homepage, https://github.com/kamikaze/fastapi-commons
|
|
31
|
+
Project-URL: Documentation, https://github.com/kamikaze/fastapi-commons/wiki
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# fastapi-commons
|
|
35
|
+
|
|
36
|
+
Common utilities, middleware, security, and observability helpers for production-ready [FastAPI](https://fastapi.tiangolo.com/) microservices.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Overview
|
|
41
|
+
|
|
42
|
+
`fastapi-commons` provides shared building blocks across FastAPI services to eliminate boilerplate. It integrates Prometheus metrics, OpenTelemetry distributed tracing, contextual logging with correlation IDs, OpenID Connect (OIDC) JWT and database API key authentication, and structured exception handling.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
### 1. Observability & Prometheus Metrics
|
|
49
|
+
- **`PrometheusMiddleware`**: Tracks HTTP request counts, response statuses, in-progress requests, and request durations.
|
|
50
|
+
- Automatically resolves parameterized route templates (e.g. `/items/{id}` instead of `/items/123`) to prevent metric cardinality explosion.
|
|
51
|
+
- Supports OpenTelemetry trace exemplars on duration histograms to link metrics to traces.
|
|
52
|
+
- **`metrics`**: Pre-configured route handler exposing metrics in OpenMetrics / Prometheus exposition format.
|
|
53
|
+
- **Access log filtering**: Built-in `EndpointFilter` suppresses noisy `GET /metrics` access logs in Uvicorn.
|
|
54
|
+
|
|
55
|
+
### 2. Distributed Tracing (OpenTelemetry)
|
|
56
|
+
- **`setup_opentelemetry`**: One-line configuration for OpenTelemetry tracing.
|
|
57
|
+
- Configures `TracerProvider`, `Resource` attributes, and `BatchSpanProcessor` with an OTLP gRPC exporter.
|
|
58
|
+
- Instruments FastAPI endpoints and Python logging for trace correlation.
|
|
59
|
+
|
|
60
|
+
### 3. Correlation ID & Contextual Logging
|
|
61
|
+
- **`CorrelationIDMiddleware`**: Inspects or generates an `X-Correlation-ID` HTTP header, propagating it across request context and returning it in responses.
|
|
62
|
+
- **`LogContextMiddleware`**: Captures client information (such as client IP, respecting `X-Forwarded-For`).
|
|
63
|
+
- **`CorrelationIDFilter` & `LogContextFilter`**: Logging filters that inject correlation IDs and request context into Python log records.
|
|
64
|
+
- **`SHARED_LOG_CONFIG`**: Standard log format containing timestamps, log levels, logger names, trace IDs, span IDs, and service names.
|
|
65
|
+
|
|
66
|
+
### 4. Authentication & Authorization
|
|
67
|
+
- **OIDC Token Verification (`get_token_verifier`)**:
|
|
68
|
+
- Validates Bearer JWT tokens against OpenID Connect (OIDC) JSON Web Key Sets (JWKS) via RS256.
|
|
69
|
+
- Deserializes and validates token claims into custom models using `msgspec`.
|
|
70
|
+
- Can be toggled via environment settings (`API_AUTH_ENABLED`).
|
|
71
|
+
- **Database API Key Verification (`get_api_key_verifier`)**:
|
|
72
|
+
- FastAPI dependency for validating API keys from request headers against database records using SQLAlchemy async sessions.
|
|
73
|
+
|
|
74
|
+
### 5. Exception Handling
|
|
75
|
+
- **`@handle_exceptions`**: Decorator for endpoint functions and handlers that catches common application errors and maps them to standard HTTP exceptions:
|
|
76
|
+
- `PermissionError` $\to$ `401 Unauthorized`
|
|
77
|
+
- `LookupError` $\to$ `404 Not Found`
|
|
78
|
+
- `ValueError` $\to$ `400 Bad Request`
|
|
79
|
+
- `ConnectionRefusedError` $\to$ `503 Service Unavailable`
|
|
80
|
+
- `NotImplementedError` $\to$ `501 Not Implemented`
|
|
81
|
+
- `AppError` / `ValidationError` $\to$ `500 Internal Server Error`
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Installation
|
|
86
|
+
|
|
87
|
+
Using `uv` (recommended):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv add fastapi-commons
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or using `pip`:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install fastapi-commons
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Usage Guide
|
|
102
|
+
|
|
103
|
+
### Metrics & OpenTelemetry Setup
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from fastapi import FastAPI
|
|
107
|
+
from fastapi_commons import PrometheusMiddleware, metrics, setup_opentelemetry
|
|
108
|
+
|
|
109
|
+
app = FastAPI(title="My Service")
|
|
110
|
+
|
|
111
|
+
# Setup OpenTelemetry tracing
|
|
112
|
+
setup_opentelemetry(app, app_name="my-service")
|
|
113
|
+
|
|
114
|
+
# Setup Prometheus metrics middleware and endpoint
|
|
115
|
+
app.add_middleware(PrometheusMiddleware, app_name="my-service")
|
|
116
|
+
app.add_route("/metrics", metrics)
|
|
117
|
+
|
|
118
|
+
@app.get("/items/{item_id}")
|
|
119
|
+
async def get_item(item_id: int):
|
|
120
|
+
return {"item_id": item_id}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Correlation ID & Logging
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import logging
|
|
127
|
+
from fastapi import FastAPI
|
|
128
|
+
from fastapi_commons.middleware.correlation_id import CorrelationIDMiddleware
|
|
129
|
+
from fastapi_commons.middleware.log_context import LogContextMiddleware
|
|
130
|
+
from fastapi_commons.log.filters import CorrelationIDFilter, LogContextFilter
|
|
131
|
+
|
|
132
|
+
app = FastAPI()
|
|
133
|
+
|
|
134
|
+
# Add middlewares
|
|
135
|
+
app.add_middleware(CorrelationIDMiddleware)
|
|
136
|
+
app.add_middleware(LogContextMiddleware)
|
|
137
|
+
|
|
138
|
+
# Attach filters to your log handlers
|
|
139
|
+
handler = logging.StreamHandler()
|
|
140
|
+
handler.addFilter(CorrelationIDFilter())
|
|
141
|
+
handler.addFilter(LogContextFilter())
|
|
142
|
+
logging.basicConfig(
|
|
143
|
+
level=logging.INFO,
|
|
144
|
+
format="%(asctime)s [%(correlation_id)s] [%(client_ip)s] %(message)s",
|
|
145
|
+
handlers=[handler],
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### OIDC Authentication
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from fastapi import Depends, FastAPI
|
|
153
|
+
from python3_commons.auth import TokenData
|
|
154
|
+
from fastapi_commons.auth import get_token_verifier
|
|
155
|
+
|
|
156
|
+
app = FastAPI()
|
|
157
|
+
|
|
158
|
+
class UserClaims(TokenData):
|
|
159
|
+
sub: str
|
|
160
|
+
email: str | None = None
|
|
161
|
+
|
|
162
|
+
verify_token = get_token_verifier(UserClaims)
|
|
163
|
+
|
|
164
|
+
@app.get("/protected")
|
|
165
|
+
async def protected_route(claims: UserClaims = Depends(verify_token)):
|
|
166
|
+
return {"message": f"Hello, {claims.sub}!"}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Exception Handling Decorator
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from fastapi import FastAPI
|
|
173
|
+
from fastapi_commons.handlers import handle_exceptions
|
|
174
|
+
|
|
175
|
+
app = FastAPI()
|
|
176
|
+
|
|
177
|
+
@app.get("/find/{item_id}")
|
|
178
|
+
@handle_exceptions
|
|
179
|
+
async def find_item(item_id: int):
|
|
180
|
+
if item_id < 0:
|
|
181
|
+
raise ValueError("Item ID must be positive")
|
|
182
|
+
if item_id == 404:
|
|
183
|
+
raise LookupError("Item not found")
|
|
184
|
+
return {"item_id": item_id}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Configuration
|
|
190
|
+
|
|
191
|
+
Settings can be customized via environment variables:
|
|
192
|
+
|
|
193
|
+
| Environment Variable | Default | Description |
|
|
194
|
+
|----------------------|---------------------|--------------------------------------------------|
|
|
195
|
+
| `API_AUTH_ENABLED` | `true` | Enable or disable API authentication enforcement |
|
|
196
|
+
| `OTLP_GRPC_ENDPOINT` | `http://tempo:4317` | OpenTelemetry gRPC collector endpoint |
|
|
197
|
+
| `OIDC_AUTHORITY_URL` | - | OpenID Connect authority / issuer URL |
|
|
198
|
+
| `OIDC_CLIENT_ID` | - | OpenID Connect client ID |
|
|
199
|
+
| `OIDC_CLIENT_SECRET` | - | OpenID Connect client secret |
|
|
200
|
+
| `OIDC_AUDIENCE` | - | Expected token audience |
|
|
201
|
+
| `OIDC_TIMEOUT` | `10.0` | OIDC HTTP request timeout (seconds) |
|
|
202
|
+
| `OIDC_VERIFY_CERT` | `true` | Verify TLS certificates for OIDC requests |
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
This project is licensed under the GNU General Public License v3 (`GPL-3.0`). See [LICENSE](LICENSE) for details.
|
|
209
|
+
|
|
210
|
+
## Contributors
|
|
211
|
+
|
|
212
|
+
See [AUTHORS.md](AUTHORS.md).
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# fastapi-commons
|
|
2
|
+
|
|
3
|
+
Common utilities, middleware, security, and observability helpers for production-ready [FastAPI](https://fastapi.tiangolo.com/) microservices.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
`fastapi-commons` provides shared building blocks across FastAPI services to eliminate boilerplate. It integrates Prometheus metrics, OpenTelemetry distributed tracing, contextual logging with correlation IDs, OpenID Connect (OIDC) JWT and database API key authentication, and structured exception handling.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
### 1. Observability & Prometheus Metrics
|
|
16
|
+
- **`PrometheusMiddleware`**: Tracks HTTP request counts, response statuses, in-progress requests, and request durations.
|
|
17
|
+
- Automatically resolves parameterized route templates (e.g. `/items/{id}` instead of `/items/123`) to prevent metric cardinality explosion.
|
|
18
|
+
- Supports OpenTelemetry trace exemplars on duration histograms to link metrics to traces.
|
|
19
|
+
- **`metrics`**: Pre-configured route handler exposing metrics in OpenMetrics / Prometheus exposition format.
|
|
20
|
+
- **Access log filtering**: Built-in `EndpointFilter` suppresses noisy `GET /metrics` access logs in Uvicorn.
|
|
21
|
+
|
|
22
|
+
### 2. Distributed Tracing (OpenTelemetry)
|
|
23
|
+
- **`setup_opentelemetry`**: One-line configuration for OpenTelemetry tracing.
|
|
24
|
+
- Configures `TracerProvider`, `Resource` attributes, and `BatchSpanProcessor` with an OTLP gRPC exporter.
|
|
25
|
+
- Instruments FastAPI endpoints and Python logging for trace correlation.
|
|
26
|
+
|
|
27
|
+
### 3. Correlation ID & Contextual Logging
|
|
28
|
+
- **`CorrelationIDMiddleware`**: Inspects or generates an `X-Correlation-ID` HTTP header, propagating it across request context and returning it in responses.
|
|
29
|
+
- **`LogContextMiddleware`**: Captures client information (such as client IP, respecting `X-Forwarded-For`).
|
|
30
|
+
- **`CorrelationIDFilter` & `LogContextFilter`**: Logging filters that inject correlation IDs and request context into Python log records.
|
|
31
|
+
- **`SHARED_LOG_CONFIG`**: Standard log format containing timestamps, log levels, logger names, trace IDs, span IDs, and service names.
|
|
32
|
+
|
|
33
|
+
### 4. Authentication & Authorization
|
|
34
|
+
- **OIDC Token Verification (`get_token_verifier`)**:
|
|
35
|
+
- Validates Bearer JWT tokens against OpenID Connect (OIDC) JSON Web Key Sets (JWKS) via RS256.
|
|
36
|
+
- Deserializes and validates token claims into custom models using `msgspec`.
|
|
37
|
+
- Can be toggled via environment settings (`API_AUTH_ENABLED`).
|
|
38
|
+
- **Database API Key Verification (`get_api_key_verifier`)**:
|
|
39
|
+
- FastAPI dependency for validating API keys from request headers against database records using SQLAlchemy async sessions.
|
|
40
|
+
|
|
41
|
+
### 5. Exception Handling
|
|
42
|
+
- **`@handle_exceptions`**: Decorator for endpoint functions and handlers that catches common application errors and maps them to standard HTTP exceptions:
|
|
43
|
+
- `PermissionError` $\to$ `401 Unauthorized`
|
|
44
|
+
- `LookupError` $\to$ `404 Not Found`
|
|
45
|
+
- `ValueError` $\to$ `400 Bad Request`
|
|
46
|
+
- `ConnectionRefusedError` $\to$ `503 Service Unavailable`
|
|
47
|
+
- `NotImplementedError` $\to$ `501 Not Implemented`
|
|
48
|
+
- `AppError` / `ValidationError` $\to$ `500 Internal Server Error`
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
Using `uv` (recommended):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uv add fastapi-commons
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or using `pip`:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install fastapi-commons
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Usage Guide
|
|
69
|
+
|
|
70
|
+
### Metrics & OpenTelemetry Setup
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from fastapi import FastAPI
|
|
74
|
+
from fastapi_commons import PrometheusMiddleware, metrics, setup_opentelemetry
|
|
75
|
+
|
|
76
|
+
app = FastAPI(title="My Service")
|
|
77
|
+
|
|
78
|
+
# Setup OpenTelemetry tracing
|
|
79
|
+
setup_opentelemetry(app, app_name="my-service")
|
|
80
|
+
|
|
81
|
+
# Setup Prometheus metrics middleware and endpoint
|
|
82
|
+
app.add_middleware(PrometheusMiddleware, app_name="my-service")
|
|
83
|
+
app.add_route("/metrics", metrics)
|
|
84
|
+
|
|
85
|
+
@app.get("/items/{item_id}")
|
|
86
|
+
async def get_item(item_id: int):
|
|
87
|
+
return {"item_id": item_id}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Correlation ID & Logging
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import logging
|
|
94
|
+
from fastapi import FastAPI
|
|
95
|
+
from fastapi_commons.middleware.correlation_id import CorrelationIDMiddleware
|
|
96
|
+
from fastapi_commons.middleware.log_context import LogContextMiddleware
|
|
97
|
+
from fastapi_commons.log.filters import CorrelationIDFilter, LogContextFilter
|
|
98
|
+
|
|
99
|
+
app = FastAPI()
|
|
100
|
+
|
|
101
|
+
# Add middlewares
|
|
102
|
+
app.add_middleware(CorrelationIDMiddleware)
|
|
103
|
+
app.add_middleware(LogContextMiddleware)
|
|
104
|
+
|
|
105
|
+
# Attach filters to your log handlers
|
|
106
|
+
handler = logging.StreamHandler()
|
|
107
|
+
handler.addFilter(CorrelationIDFilter())
|
|
108
|
+
handler.addFilter(LogContextFilter())
|
|
109
|
+
logging.basicConfig(
|
|
110
|
+
level=logging.INFO,
|
|
111
|
+
format="%(asctime)s [%(correlation_id)s] [%(client_ip)s] %(message)s",
|
|
112
|
+
handlers=[handler],
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### OIDC Authentication
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from fastapi import Depends, FastAPI
|
|
120
|
+
from python3_commons.auth import TokenData
|
|
121
|
+
from fastapi_commons.auth import get_token_verifier
|
|
122
|
+
|
|
123
|
+
app = FastAPI()
|
|
124
|
+
|
|
125
|
+
class UserClaims(TokenData):
|
|
126
|
+
sub: str
|
|
127
|
+
email: str | None = None
|
|
128
|
+
|
|
129
|
+
verify_token = get_token_verifier(UserClaims)
|
|
130
|
+
|
|
131
|
+
@app.get("/protected")
|
|
132
|
+
async def protected_route(claims: UserClaims = Depends(verify_token)):
|
|
133
|
+
return {"message": f"Hello, {claims.sub}!"}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Exception Handling Decorator
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from fastapi import FastAPI
|
|
140
|
+
from fastapi_commons.handlers import handle_exceptions
|
|
141
|
+
|
|
142
|
+
app = FastAPI()
|
|
143
|
+
|
|
144
|
+
@app.get("/find/{item_id}")
|
|
145
|
+
@handle_exceptions
|
|
146
|
+
async def find_item(item_id: int):
|
|
147
|
+
if item_id < 0:
|
|
148
|
+
raise ValueError("Item ID must be positive")
|
|
149
|
+
if item_id == 404:
|
|
150
|
+
raise LookupError("Item not found")
|
|
151
|
+
return {"item_id": item_id}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Configuration
|
|
157
|
+
|
|
158
|
+
Settings can be customized via environment variables:
|
|
159
|
+
|
|
160
|
+
| Environment Variable | Default | Description |
|
|
161
|
+
|----------------------|---------------------|--------------------------------------------------|
|
|
162
|
+
| `API_AUTH_ENABLED` | `true` | Enable or disable API authentication enforcement |
|
|
163
|
+
| `OTLP_GRPC_ENDPOINT` | `http://tempo:4317` | OpenTelemetry gRPC collector endpoint |
|
|
164
|
+
| `OIDC_AUTHORITY_URL` | - | OpenID Connect authority / issuer URL |
|
|
165
|
+
| `OIDC_CLIENT_ID` | - | OpenID Connect client ID |
|
|
166
|
+
| `OIDC_CLIENT_SECRET` | - | OpenID Connect client secret |
|
|
167
|
+
| `OIDC_AUDIENCE` | - | Expected token audience |
|
|
168
|
+
| `OIDC_TIMEOUT` | `10.0` | OIDC HTTP request timeout (seconds) |
|
|
169
|
+
| `OIDC_VERIFY_CERT` | `true` | Verify TLS certificates for OIDC requests |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
This project is licensed under the GNU General Public License v3 (`GPL-3.0`). See [LICENSE](LICENSE) for details.
|
|
176
|
+
|
|
177
|
+
## Contributors
|
|
178
|
+
|
|
179
|
+
See [AUTHORS.md](AUTHORS.md).
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.12.18,<0.13.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fastapi-commons"
|
|
7
|
+
version = "0.8.6"
|
|
8
|
+
description = "Re-usable FastAPI code"
|
|
9
|
+
license = "GPL-3.0-only"
|
|
10
|
+
license-files = [
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"AUTHORS.md",
|
|
13
|
+
]
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Programming Language :: Python",
|
|
18
|
+
]
|
|
19
|
+
keywords = []
|
|
20
|
+
requires-python = ">=3.14.0,<3.15.0"
|
|
21
|
+
dependencies = [
|
|
22
|
+
"aiohttp[speedups]>=3.14.3,<3.15.0",
|
|
23
|
+
"asyncpg~=0.31.0",
|
|
24
|
+
"fastapi~=0.141.1",
|
|
25
|
+
"msgspec~=0.21.1",
|
|
26
|
+
"pydantic[email]~=2.13.5",
|
|
27
|
+
"pydantic-settings~=2.15.0",
|
|
28
|
+
"python-jose~=3.5.0",
|
|
29
|
+
"SQLAlchemy[asyncio]~=2.1.1",
|
|
30
|
+
"opentelemetry-api~=1.45.0",
|
|
31
|
+
"opentelemetry-sdk~=1.45.0",
|
|
32
|
+
"opentelemetry-exporter-otlp~=1.45.0",
|
|
33
|
+
"opentelemetry-instrumentation-fastapi~=0.65b0",
|
|
34
|
+
"opentelemetry-instrumentation-logging~=0.65b0",
|
|
35
|
+
"prometheus-client~=0.26.0",
|
|
36
|
+
"python3-commons[authn]~=0.24.6",
|
|
37
|
+
"starlette~=1.7.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[[project.authors]]
|
|
41
|
+
name = "Oleg Korsak"
|
|
42
|
+
email = "kamikaze.is.waiting.you@gmail.com"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/kamikaze/fastapi-commons"
|
|
46
|
+
Documentation = "https://github.com/kamikaze/fastapi-commons/wiki"
|
|
47
|
+
|
|
48
|
+
[dependency-groups]
|
|
49
|
+
dev = [
|
|
50
|
+
"pre-commit",
|
|
51
|
+
"pyright",
|
|
52
|
+
"ruff",
|
|
53
|
+
"ty",
|
|
54
|
+
"types-aiobotocore-s3",
|
|
55
|
+
]
|
|
56
|
+
testing = [
|
|
57
|
+
"pytest",
|
|
58
|
+
"pytest-asyncio",
|
|
59
|
+
"pytest-cov",
|
|
60
|
+
"pytest-mock",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[tool.uv.build-backend]
|
|
64
|
+
source-include = [
|
|
65
|
+
"CHANGELOG.md",
|
|
66
|
+
"tests/**",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 120
|
|
71
|
+
indent-width = 4
|
|
72
|
+
target-version = "py314"
|
|
73
|
+
|
|
74
|
+
[tool.ruff.analyze]
|
|
75
|
+
detect-string-imports = true
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint]
|
|
78
|
+
select = [
|
|
79
|
+
"A",
|
|
80
|
+
"ANN",
|
|
81
|
+
"ARG",
|
|
82
|
+
"ASYNC",
|
|
83
|
+
"B",
|
|
84
|
+
"C4",
|
|
85
|
+
"DTZ",
|
|
86
|
+
"E",
|
|
87
|
+
"EM",
|
|
88
|
+
"EXE",
|
|
89
|
+
"F",
|
|
90
|
+
"FA",
|
|
91
|
+
"FAST",
|
|
92
|
+
"FBT",
|
|
93
|
+
"FLY",
|
|
94
|
+
"FURB",
|
|
95
|
+
"G",
|
|
96
|
+
"I",
|
|
97
|
+
"ICN",
|
|
98
|
+
"INP",
|
|
99
|
+
"ISC",
|
|
100
|
+
"LOG",
|
|
101
|
+
"N",
|
|
102
|
+
"NPY",
|
|
103
|
+
"PD",
|
|
104
|
+
"PERF",
|
|
105
|
+
"PIE",
|
|
106
|
+
"PLC",
|
|
107
|
+
"PLE",
|
|
108
|
+
"PLR",
|
|
109
|
+
"PLW",
|
|
110
|
+
"PT",
|
|
111
|
+
"PTH",
|
|
112
|
+
"PYI",
|
|
113
|
+
"Q",
|
|
114
|
+
"RET",
|
|
115
|
+
"RSE",
|
|
116
|
+
"RUF",
|
|
117
|
+
"S",
|
|
118
|
+
"SIM",
|
|
119
|
+
"SLF",
|
|
120
|
+
"SLOT",
|
|
121
|
+
"T20",
|
|
122
|
+
"TC",
|
|
123
|
+
"TID",
|
|
124
|
+
"TRY",
|
|
125
|
+
"UP",
|
|
126
|
+
"W",
|
|
127
|
+
"YTT",
|
|
128
|
+
]
|
|
129
|
+
ignore = [
|
|
130
|
+
"TC001",
|
|
131
|
+
"TC002",
|
|
132
|
+
"TC003",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
[tool.ruff.lint.per-file-ignores]
|
|
136
|
+
"tests/*.py" = [
|
|
137
|
+
"DTZ",
|
|
138
|
+
"S101",
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
[tool.ruff.lint.flake8-quotes]
|
|
142
|
+
docstring-quotes = "double"
|
|
143
|
+
inline-quotes = "single"
|
|
144
|
+
|
|
145
|
+
[tool.ruff.format]
|
|
146
|
+
exclude = ["*.pyi"]
|
|
147
|
+
indent-style = "space"
|
|
148
|
+
quote-style = "single"
|
|
149
|
+
|
|
150
|
+
[tool.pyright]
|
|
151
|
+
pythonVersion = "3.14"
|
|
152
|
+
venvPath = "."
|
|
153
|
+
venv = ".venv"
|
|
154
|
+
reportMatchNotExhaustive = "error"
|
|
155
|
+
reportUnnecessaryComparison = "error"
|
|
156
|
+
|
|
157
|
+
[tool.ty.rules]
|
|
158
|
+
unknown-argument = "ignore"
|
|
159
|
+
|
|
160
|
+
[tool.pytest.ini_options]
|
|
161
|
+
pythonpath = ["src"]
|
|
162
|
+
asyncio_mode = "strict"
|
|
163
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
164
|
+
addopts = "--cov fastapi_commons --cov-fail-under=0 --cov-report term-missing --verbose"
|
|
165
|
+
norecursedirs = [
|
|
166
|
+
"dist",
|
|
167
|
+
"build",
|
|
168
|
+
".tox",
|
|
169
|
+
]
|
|
170
|
+
testpaths = ["tests"]
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["
|
|
3
|
-
build-backend = "
|
|
2
|
+
requires = ["uv_build>=0.12.18,<0.13.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fastapi-commons"
|
|
7
|
-
|
|
7
|
+
version = "0.8.6"
|
|
8
8
|
description = "Re-usable FastAPI code"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Oleg Korsak", email = "kamikaze.is.waiting.you@gmail.com" }
|
|
11
11
|
]
|
|
12
|
-
license = "GPL-3.0"
|
|
13
|
-
|
|
12
|
+
license = "GPL-3.0-only"
|
|
13
|
+
license-files = ["LICENSE", "AUTHORS.md"]
|
|
14
|
+
readme = "README.md"
|
|
14
15
|
classifiers = [
|
|
15
16
|
"Development Status :: 4 - Beta",
|
|
16
17
|
"Programming Language :: Python"
|
|
@@ -22,32 +23,27 @@ dependencies = [
|
|
|
22
23
|
"asyncpg~=0.31.0",
|
|
23
24
|
"fastapi~=0.141.1",
|
|
24
25
|
"msgspec~=0.21.1",
|
|
25
|
-
"pydantic[email]~=2.13.
|
|
26
|
+
"pydantic[email]~=2.13.5",
|
|
26
27
|
"pydantic-settings~=2.15.0",
|
|
27
28
|
"python-jose~=3.5.0",
|
|
28
|
-
"SQLAlchemy[asyncio]~=2.
|
|
29
|
-
"opentelemetry-api~=1.
|
|
30
|
-
"opentelemetry-sdk~=1.
|
|
31
|
-
"opentelemetry-exporter-otlp~=1.
|
|
29
|
+
"SQLAlchemy[asyncio]~=2.1.1",
|
|
30
|
+
"opentelemetry-api~=1.45.0",
|
|
31
|
+
"opentelemetry-sdk~=1.45.0",
|
|
32
|
+
"opentelemetry-exporter-otlp~=1.45.0",
|
|
32
33
|
"opentelemetry-instrumentation-fastapi~=0.65b0",
|
|
33
34
|
"opentelemetry-instrumentation-logging~=0.65b0",
|
|
34
35
|
"prometheus-client~=0.26.0",
|
|
35
|
-
"python3-commons[authn]~=0.24.
|
|
36
|
-
"starlette~=1.
|
|
36
|
+
"python3-commons[authn]~=0.24.6",
|
|
37
|
+
"starlette~=1.7.0",
|
|
37
38
|
]
|
|
38
39
|
|
|
39
40
|
[dependency-groups]
|
|
40
41
|
dev = [
|
|
41
|
-
"build",
|
|
42
|
-
"pip",
|
|
43
42
|
"pre-commit",
|
|
44
43
|
"pyright",
|
|
45
44
|
"ruff",
|
|
46
|
-
"setuptools",
|
|
47
|
-
"setuptools_scm",
|
|
48
45
|
"ty",
|
|
49
46
|
"types-aiobotocore-s3",
|
|
50
|
-
"wheel",
|
|
51
47
|
]
|
|
52
48
|
testing = [
|
|
53
49
|
"pytest",
|
|
@@ -60,14 +56,9 @@ testing = [
|
|
|
60
56
|
Homepage = "https://github.com/kamikaze/fastapi-commons"
|
|
61
57
|
Documentation = "https://github.com/kamikaze/fastapi-commons/wiki"
|
|
62
58
|
|
|
63
|
-
[tool.
|
|
64
|
-
|
|
65
|
-
exclude = ["tests"]
|
|
59
|
+
[tool.uv.build-backend]
|
|
60
|
+
source-include = ["CHANGELOG.md", "tests/**"]
|
|
66
61
|
|
|
67
|
-
[tool.setuptools_scm]
|
|
68
|
-
|
|
69
|
-
[tool.bdist_wheel]
|
|
70
|
-
universal = true
|
|
71
62
|
|
|
72
63
|
[tool.ruff]
|
|
73
64
|
line-length = 120
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# .coveragerc to control coverage.py
|
|
2
|
-
[run]
|
|
3
|
-
branch = True
|
|
4
|
-
source = fastapi_commons
|
|
5
|
-
# omit = bad_file.py
|
|
6
|
-
|
|
7
|
-
[paths]
|
|
8
|
-
source =
|
|
9
|
-
src/
|
|
10
|
-
*/site-packages/
|
|
11
|
-
|
|
12
|
-
[report]
|
|
13
|
-
# Regexes for lines to exclude from consideration
|
|
14
|
-
exclude_lines =
|
|
15
|
-
# Have to re-enable the standard pragma
|
|
16
|
-
pragma: no cover
|
|
17
|
-
|
|
18
|
-
# Don't complain about missing debug-only code:
|
|
19
|
-
def __repr__
|
|
20
|
-
if self\.debug
|
|
21
|
-
|
|
22
|
-
# Don't complain if tests don't hit defensive assertion code:
|
|
23
|
-
raise AssertionError
|
|
24
|
-
raise NotImplementedError
|
|
25
|
-
|
|
26
|
-
# Don't complain if non-runnable code isn't run:
|
|
27
|
-
if 0:
|
|
28
|
-
if __name__ == .__main__.:
|