auditlog-fastapi 0.2.0__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.
Files changed (25) hide show
  1. auditlog_fastapi-0.2.0/LICENSE +21 -0
  2. auditlog_fastapi-0.2.0/PKG-INFO +260 -0
  3. auditlog_fastapi-0.2.0/README.md +227 -0
  4. auditlog_fastapi-0.2.0/fastapi_audit_log/__init__.py +36 -0
  5. auditlog_fastapi-0.2.0/fastapi_audit_log/config.py +86 -0
  6. auditlog_fastapi-0.2.0/fastapi_audit_log/context.py +33 -0
  7. auditlog_fastapi-0.2.0/fastapi_audit_log/db/beanie_document.py +42 -0
  8. auditlog_fastapi-0.2.0/fastapi_audit_log/db/sqlalchemy_table.py +62 -0
  9. auditlog_fastapi-0.2.0/fastapi_audit_log/db/sqlmodel_model.py +33 -0
  10. auditlog_fastapi-0.2.0/fastapi_audit_log/db/tortoise_model.py +33 -0
  11. auditlog_fastapi-0.2.0/fastapi_audit_log/dependencies.py +17 -0
  12. auditlog_fastapi-0.2.0/fastapi_audit_log/exceptions.py +22 -0
  13. auditlog_fastapi-0.2.0/fastapi_audit_log/filters.py +34 -0
  14. auditlog_fastapi-0.2.0/fastapi_audit_log/middleware.py +170 -0
  15. auditlog_fastapi-0.2.0/fastapi_audit_log/models.py +48 -0
  16. auditlog_fastapi-0.2.0/fastapi_audit_log/registry.py +63 -0
  17. auditlog_fastapi-0.2.0/fastapi_audit_log/routes.py +39 -0
  18. auditlog_fastapi-0.2.0/fastapi_audit_log/storage/__init__.py +15 -0
  19. auditlog_fastapi-0.2.0/fastapi_audit_log/storage/asyncpg_storage.py +168 -0
  20. auditlog_fastapi-0.2.0/fastapi_audit_log/storage/base.py +44 -0
  21. auditlog_fastapi-0.2.0/fastapi_audit_log/storage/beanie_storage.py +70 -0
  22. auditlog_fastapi-0.2.0/fastapi_audit_log/storage/sqlalchemy_storage.py +135 -0
  23. auditlog_fastapi-0.2.0/fastapi_audit_log/storage/sqlmodel_storage.py +116 -0
  24. auditlog_fastapi-0.2.0/fastapi_audit_log/storage/tortoise_storage.py +69 -0
  25. auditlog_fastapi-0.2.0/pyproject.toml +64 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ahmed Elgamal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,260 @@
1
+ Metadata-Version: 2.4
2
+ Name: auditlog-fastapi
3
+ Version: 0.2.0
4
+ Summary: Production-grade, reusable audit logging for FastAPI applications.
5
+ License-File: LICENSE
6
+ Author: Ahmed Elgamal
7
+ Author-email: ahmed@example.com
8
+ Requires-Python: >=3.11,<4.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Provides-Extra: all
15
+ Provides-Extra: asyncpg
16
+ Provides-Extra: mongodb
17
+ Provides-Extra: sqlalchemy
18
+ Provides-Extra: sqlmodel
19
+ Provides-Extra: tortoise
20
+ Requires-Dist: aiomysql ; extra == "sqlalchemy" or extra == "tortoise" or extra == "sqlmodel" or extra == "all"
21
+ Requires-Dist: aiosqlite ; extra == "sqlalchemy" or extra == "tortoise" or extra == "sqlmodel" or extra == "all"
22
+ Requires-Dist: asyncpg (>=0.29) ; extra == "sqlalchemy" or extra == "tortoise" or extra == "sqlmodel" or extra == "asyncpg" or extra == "all"
23
+ Requires-Dist: beanie (>=1.20) ; extra == "mongodb" or extra == "all"
24
+ Requires-Dist: fastapi (>=0.100.0)
25
+ Requires-Dist: motor (>=3.0) ; extra == "mongodb" or extra == "all"
26
+ Requires-Dist: pydantic (>=2.0,<3.0)
27
+ Requires-Dist: pydantic-settings (>=2.0)
28
+ Requires-Dist: sqlalchemy[asyncio] (>=2.0.0) ; extra == "sqlalchemy" or extra == "all"
29
+ Requires-Dist: sqlmodel (>=0.0.14) ; extra == "sqlmodel" or extra == "all"
30
+ Requires-Dist: tortoise-orm (>=0.20) ; extra == "tortoise" or extra == "all"
31
+ Description-Content-Type: text/markdown
32
+
33
+ # auditlog-fastapi
34
+
35
+ Production-grade, reusable audit logging for FastAPI applications.
36
+
37
+ ## Features
38
+
39
+ - **Multi-ORM Support:** SQLAlchemy 2 (async), Tortoise ORM, SQLModel, Beanie (MongoDB), and raw asyncpg.
40
+ - **Flexible Storage:** PostgreSQL, MySQL, MariaDB, SQLite, and MongoDB.
41
+ - **Explicit Configuration:** Clean API with `AuditConfig` and `configure()`.
42
+ - **Middleware Integration:** Capture request/response data automatically.
43
+ - **PII Masking:** Redact sensitive fields from logs.
44
+ - **Context Helpers:** Enrich audit logs from route handlers.
45
+ - **Lifespan Integration:** Automatic startup/shutdown of database connections.
46
+ - **Async & Non-blocking:** Built with performance and safety in mind.
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ # Basic install
52
+ pip install auditlog-fastapi
53
+
54
+ # With SQLAlchemy support (Postgres, MySQL, SQLite)
55
+ pip install auditlog-fastapi[sqlalchemy]
56
+
57
+ # With Tortoise ORM support
58
+ pip install auditlog-fastapi[tortoise]
59
+
60
+ # With SQLModel support
61
+ pip install auditlog-fastapi[sqlmodel]
62
+
63
+ # With MongoDB (Beanie) support
64
+ pip install auditlog-fastapi[mongodb]
65
+
66
+ # With raw asyncpg (PostgreSQL only) support
67
+ pip install auditlog-fastapi[asyncpg]
68
+
69
+ # Everything
70
+ pip install auditlog-fastapi[all]
71
+ ```
72
+
73
+ ## Quick Start (SQLAlchemy + SQLite)
74
+
75
+ ```python
76
+ from fastapi import FastAPI
77
+ from fastapi_audit_log import AuditMiddleware, AuditConfig, create_audit_lifespan
78
+
79
+ # 1. Configure the audit log
80
+ config = AuditConfig(
81
+ orm="sqlalchemy",
82
+ dsn="sqlite+aiosqlite:///./audit.db",
83
+ table_name="audit_logs",
84
+ auto_create_table=True,
85
+ )
86
+
87
+ # 2. Create lifespan handler
88
+ app = FastAPI(lifespan=create_audit_lifespan(config))
89
+
90
+ # 3. Add middleware
91
+ app.add_middleware(
92
+ AuditMiddleware,
93
+ log_request_body=True
94
+ )
95
+
96
+ @app.get("/")
97
+ async def root():
98
+ return {"message": "Hello World"}
99
+ ```
100
+
101
+ ## User Configuration Guide
102
+
103
+ ### SQLAlchemy + PostgreSQL
104
+
105
+ ```python
106
+ config = AuditConfig(
107
+ orm="sqlalchemy",
108
+ dsn="postgresql+asyncpg://user:pass@localhost:5432/mydb",
109
+ table_name="audit_logs",
110
+ auto_create_table=True,
111
+ sqlalchemy_pool_size=10,
112
+ mask_fields=["password", "token"],
113
+ )
114
+ ```
115
+
116
+ ### SQLAlchemy + MySQL
117
+
118
+ ```python
119
+ config = AuditConfig(
120
+ orm="sqlalchemy",
121
+ dsn="mysql+aiomysql://user:pass@localhost:3306/mydb",
122
+ auto_create_table=True,
123
+ )
124
+ ```
125
+
126
+ ### Tortoise ORM + PostgreSQL
127
+
128
+ ```python
129
+ config = AuditConfig(
130
+ orm="tortoise",
131
+ dsn="postgres://user:pass@localhost:5432/mydb",
132
+ auto_create_table=True,
133
+ )
134
+ ```
135
+
136
+ ### MongoDB via Beanie
137
+
138
+ ```python
139
+ config = AuditConfig(
140
+ orm="beanie",
141
+ dsn="mongodb://localhost:27017",
142
+ mongodb_database="myapp",
143
+ table_name="audit_logs", # becomes collection name
144
+ )
145
+ ```
146
+
147
+ ### Raw asyncpg (PostgreSQL, maximum performance)
148
+
149
+ ```python
150
+ config = AuditConfig(
151
+ orm="asyncpg",
152
+ dsn="postgresql://user:pass@localhost:5432/mydb",
153
+ batch_size=200,
154
+ )
155
+ ```
156
+
157
+ ### Using with Alembic (SQLAlchemy only)
158
+
159
+ ```python
160
+ # In alembic/env.py — include audit table in your migrations
161
+ from fastapi_audit_log.db.sqlalchemy_table import AuditBase
162
+ target_metadata = [YourBase.metadata, AuditBase.metadata]
163
+ ```
164
+
165
+ ## Enriching Logs from Routes
166
+
167
+ ```python
168
+ from fastapi_audit_log import set_audit_action, set_audit_resource, set_audit_extra
169
+
170
+ @app.post("/items")
171
+ async def create_item(item_id: str):
172
+ set_audit_action("item.create")
173
+ set_audit_resource("item", item_id)
174
+ set_audit_extra("metadata", {"source": "admin_panel"})
175
+ return {"status": "ok"}
176
+ ```
177
+
178
+ ## Retrieving Audit Logs
179
+
180
+ `fastapi-audit-log` provides a built-in helper to add a route for querying and filtering your audit logs.
181
+
182
+ ```python
183
+ from fastapi import FastAPI
184
+ from fastapi_audit_log import add_audit_log_routes
185
+
186
+ app = FastAPI(...)
187
+
188
+ # Register the GET /audit-logs route
189
+ add_audit_log_routes(
190
+ app,
191
+ path="/audit-logs", # default
192
+ tags=["Audit Logs"] # optional tags for OpenAPI
193
+ )
194
+ ```
195
+
196
+ ### Filtering and Pagination
197
+
198
+ The added route supports several query parameters:
199
+
200
+ * **Pagination:** `limit` (default 100, max 1000) and `offset` (default 0).
201
+ * **Filters:** `method`, `path`, `status_code`, `user_id`, and `action`.
202
+
203
+ Example request:
204
+ `GET /audit-logs?method=POST&status_code=201&limit=20`
205
+
206
+ ## Configuration Reference (AuditConfig)
207
+
208
+ | Parameter | Type | Default | Description |
209
+ |-----------|------|---------|-------------|
210
+ | `orm` | `str` | **Required** | One of: `sqlalchemy`, `tortoise`, `sqlmodel`, `beanie`, `asyncpg`. |
211
+ | `dsn` | `str` | **Required** | Connection string for the database. |
212
+ | `table_name` | `str` | `"audit_logs"` | Name of the table or collection. |
213
+ | `auto_create_table`| `bool` | `True` | Whether to create the table on startup. |
214
+ | `batch_size` | `int` | `1` | Set > 1 to enable batching (not all backends yet). |
215
+ | `mask_fields` | `list[str]` | `[]` | PII fields to mask in request bodies. |
216
+ | `on_storage_error`| `Callable` | `None` | Optional callback for storage errors. |
217
+
218
+ ## Development and Examples
219
+
220
+ To run the examples that require a real database (PostgreSQL, MongoDB, MySQL), you can use the provided Docker Compose file:
221
+
222
+ ```bash
223
+ docker-compose up -d
224
+ ```
225
+
226
+ This will start:
227
+
228
+ - **PostgreSQL** at `localhost:5432` (user: `user`, pass: `pass`, db: `audit_db`)
229
+ - **MongoDB** at `localhost:27017`
230
+ - **MySQL** at `localhost:3306` (user: `user`, pass: `pass`, db: `audit_db`)
231
+
232
+ ### Running the MongoDB Example
233
+
234
+ ```bash
235
+ poetry run python examples/beanie_mongodb_usage.py
236
+ ```
237
+
238
+ ### Running the Asyncpg Example
239
+
240
+ ```bash
241
+ poetry run python examples/asyncpg_usage.py
242
+ ```
243
+
244
+ ## Future Features
245
+
246
+ - **Admin UI:** Build a simple web UI for viewing/searching audit logs.
247
+ - **Custom Storage Backends:** Allow users to plug in custom storage backends (e.g., S3, Redis, external APIs).
248
+ - **Event Hooks:** Add hooks for pre/post log processing (e.g., for enrichment, notifications).
249
+ - **Log Export:** Support exporting logs to CSV, JSON, or external log management systems.
250
+ - **Retention Policies:** Add configurable log retention and automatic cleanup.
251
+ - **Multi-Tenancy:** Support tenant-aware logging for SaaS apps.
252
+ - **Security:** Encrypt sensitive log fields at rest, and add role-based access for log viewing.
253
+ - **CLI Tooling:** Provide CLI commands for log inspection, export, and management.
254
+ - **OpenTelemetry Integration:** Integrate with OpenTelemetry for distributed tracing and correlation.
255
+ - **Documentation:** Expand usage examples and add troubleshooting guides.
256
+
257
+ ## License
258
+
259
+ MIT
260
+
@@ -0,0 +1,227 @@
1
+ # auditlog-fastapi
2
+
3
+ Production-grade, reusable audit logging for FastAPI applications.
4
+
5
+ ## Features
6
+
7
+ - **Multi-ORM Support:** SQLAlchemy 2 (async), Tortoise ORM, SQLModel, Beanie (MongoDB), and raw asyncpg.
8
+ - **Flexible Storage:** PostgreSQL, MySQL, MariaDB, SQLite, and MongoDB.
9
+ - **Explicit Configuration:** Clean API with `AuditConfig` and `configure()`.
10
+ - **Middleware Integration:** Capture request/response data automatically.
11
+ - **PII Masking:** Redact sensitive fields from logs.
12
+ - **Context Helpers:** Enrich audit logs from route handlers.
13
+ - **Lifespan Integration:** Automatic startup/shutdown of database connections.
14
+ - **Async & Non-blocking:** Built with performance and safety in mind.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ # Basic install
20
+ pip install auditlog-fastapi
21
+
22
+ # With SQLAlchemy support (Postgres, MySQL, SQLite)
23
+ pip install auditlog-fastapi[sqlalchemy]
24
+
25
+ # With Tortoise ORM support
26
+ pip install auditlog-fastapi[tortoise]
27
+
28
+ # With SQLModel support
29
+ pip install auditlog-fastapi[sqlmodel]
30
+
31
+ # With MongoDB (Beanie) support
32
+ pip install auditlog-fastapi[mongodb]
33
+
34
+ # With raw asyncpg (PostgreSQL only) support
35
+ pip install auditlog-fastapi[asyncpg]
36
+
37
+ # Everything
38
+ pip install auditlog-fastapi[all]
39
+ ```
40
+
41
+ ## Quick Start (SQLAlchemy + SQLite)
42
+
43
+ ```python
44
+ from fastapi import FastAPI
45
+ from fastapi_audit_log import AuditMiddleware, AuditConfig, create_audit_lifespan
46
+
47
+ # 1. Configure the audit log
48
+ config = AuditConfig(
49
+ orm="sqlalchemy",
50
+ dsn="sqlite+aiosqlite:///./audit.db",
51
+ table_name="audit_logs",
52
+ auto_create_table=True,
53
+ )
54
+
55
+ # 2. Create lifespan handler
56
+ app = FastAPI(lifespan=create_audit_lifespan(config))
57
+
58
+ # 3. Add middleware
59
+ app.add_middleware(
60
+ AuditMiddleware,
61
+ log_request_body=True
62
+ )
63
+
64
+ @app.get("/")
65
+ async def root():
66
+ return {"message": "Hello World"}
67
+ ```
68
+
69
+ ## User Configuration Guide
70
+
71
+ ### SQLAlchemy + PostgreSQL
72
+
73
+ ```python
74
+ config = AuditConfig(
75
+ orm="sqlalchemy",
76
+ dsn="postgresql+asyncpg://user:pass@localhost:5432/mydb",
77
+ table_name="audit_logs",
78
+ auto_create_table=True,
79
+ sqlalchemy_pool_size=10,
80
+ mask_fields=["password", "token"],
81
+ )
82
+ ```
83
+
84
+ ### SQLAlchemy + MySQL
85
+
86
+ ```python
87
+ config = AuditConfig(
88
+ orm="sqlalchemy",
89
+ dsn="mysql+aiomysql://user:pass@localhost:3306/mydb",
90
+ auto_create_table=True,
91
+ )
92
+ ```
93
+
94
+ ### Tortoise ORM + PostgreSQL
95
+
96
+ ```python
97
+ config = AuditConfig(
98
+ orm="tortoise",
99
+ dsn="postgres://user:pass@localhost:5432/mydb",
100
+ auto_create_table=True,
101
+ )
102
+ ```
103
+
104
+ ### MongoDB via Beanie
105
+
106
+ ```python
107
+ config = AuditConfig(
108
+ orm="beanie",
109
+ dsn="mongodb://localhost:27017",
110
+ mongodb_database="myapp",
111
+ table_name="audit_logs", # becomes collection name
112
+ )
113
+ ```
114
+
115
+ ### Raw asyncpg (PostgreSQL, maximum performance)
116
+
117
+ ```python
118
+ config = AuditConfig(
119
+ orm="asyncpg",
120
+ dsn="postgresql://user:pass@localhost:5432/mydb",
121
+ batch_size=200,
122
+ )
123
+ ```
124
+
125
+ ### Using with Alembic (SQLAlchemy only)
126
+
127
+ ```python
128
+ # In alembic/env.py — include audit table in your migrations
129
+ from fastapi_audit_log.db.sqlalchemy_table import AuditBase
130
+ target_metadata = [YourBase.metadata, AuditBase.metadata]
131
+ ```
132
+
133
+ ## Enriching Logs from Routes
134
+
135
+ ```python
136
+ from fastapi_audit_log import set_audit_action, set_audit_resource, set_audit_extra
137
+
138
+ @app.post("/items")
139
+ async def create_item(item_id: str):
140
+ set_audit_action("item.create")
141
+ set_audit_resource("item", item_id)
142
+ set_audit_extra("metadata", {"source": "admin_panel"})
143
+ return {"status": "ok"}
144
+ ```
145
+
146
+ ## Retrieving Audit Logs
147
+
148
+ `fastapi-audit-log` provides a built-in helper to add a route for querying and filtering your audit logs.
149
+
150
+ ```python
151
+ from fastapi import FastAPI
152
+ from fastapi_audit_log import add_audit_log_routes
153
+
154
+ app = FastAPI(...)
155
+
156
+ # Register the GET /audit-logs route
157
+ add_audit_log_routes(
158
+ app,
159
+ path="/audit-logs", # default
160
+ tags=["Audit Logs"] # optional tags for OpenAPI
161
+ )
162
+ ```
163
+
164
+ ### Filtering and Pagination
165
+
166
+ The added route supports several query parameters:
167
+
168
+ * **Pagination:** `limit` (default 100, max 1000) and `offset` (default 0).
169
+ * **Filters:** `method`, `path`, `status_code`, `user_id`, and `action`.
170
+
171
+ Example request:
172
+ `GET /audit-logs?method=POST&status_code=201&limit=20`
173
+
174
+ ## Configuration Reference (AuditConfig)
175
+
176
+ | Parameter | Type | Default | Description |
177
+ |-----------|------|---------|-------------|
178
+ | `orm` | `str` | **Required** | One of: `sqlalchemy`, `tortoise`, `sqlmodel`, `beanie`, `asyncpg`. |
179
+ | `dsn` | `str` | **Required** | Connection string for the database. |
180
+ | `table_name` | `str` | `"audit_logs"` | Name of the table or collection. |
181
+ | `auto_create_table`| `bool` | `True` | Whether to create the table on startup. |
182
+ | `batch_size` | `int` | `1` | Set > 1 to enable batching (not all backends yet). |
183
+ | `mask_fields` | `list[str]` | `[]` | PII fields to mask in request bodies. |
184
+ | `on_storage_error`| `Callable` | `None` | Optional callback for storage errors. |
185
+
186
+ ## Development and Examples
187
+
188
+ To run the examples that require a real database (PostgreSQL, MongoDB, MySQL), you can use the provided Docker Compose file:
189
+
190
+ ```bash
191
+ docker-compose up -d
192
+ ```
193
+
194
+ This will start:
195
+
196
+ - **PostgreSQL** at `localhost:5432` (user: `user`, pass: `pass`, db: `audit_db`)
197
+ - **MongoDB** at `localhost:27017`
198
+ - **MySQL** at `localhost:3306` (user: `user`, pass: `pass`, db: `audit_db`)
199
+
200
+ ### Running the MongoDB Example
201
+
202
+ ```bash
203
+ poetry run python examples/beanie_mongodb_usage.py
204
+ ```
205
+
206
+ ### Running the Asyncpg Example
207
+
208
+ ```bash
209
+ poetry run python examples/asyncpg_usage.py
210
+ ```
211
+
212
+ ## Future Features
213
+
214
+ - **Admin UI:** Build a simple web UI for viewing/searching audit logs.
215
+ - **Custom Storage Backends:** Allow users to plug in custom storage backends (e.g., S3, Redis, external APIs).
216
+ - **Event Hooks:** Add hooks for pre/post log processing (e.g., for enrichment, notifications).
217
+ - **Log Export:** Support exporting logs to CSV, JSON, or external log management systems.
218
+ - **Retention Policies:** Add configurable log retention and automatic cleanup.
219
+ - **Multi-Tenancy:** Support tenant-aware logging for SaaS apps.
220
+ - **Security:** Encrypt sensitive log fields at rest, and add role-based access for log viewing.
221
+ - **CLI Tooling:** Provide CLI commands for log inspection, export, and management.
222
+ - **OpenTelemetry Integration:** Integrate with OpenTelemetry for distributed tracing and correlation.
223
+ - **Documentation:** Expand usage examples and add troubleshooting guides.
224
+
225
+ ## License
226
+
227
+ MIT
@@ -0,0 +1,36 @@
1
+ from contextlib import asynccontextmanager
2
+
3
+ from .config import AuditConfig, configure, get_storage
4
+ from .context import set_audit_action, set_audit_extra, set_audit_resource
5
+ from .middleware import AuditMiddleware
6
+ from .routes import add_audit_log_routes
7
+
8
+
9
+ def create_audit_lifespan(config: AuditConfig):
10
+ storage = configure(config)
11
+
12
+ @asynccontextmanager
13
+ async def lifespan(app):
14
+ # Store config in app for testing/access if needed
15
+ if not hasattr(app, "extra"):
16
+ app.extra = {}
17
+ app.extra["audit_config"] = config
18
+
19
+ await storage.startup()
20
+ yield
21
+ await storage.shutdown()
22
+
23
+ return lifespan
24
+
25
+
26
+ __all__ = [
27
+ "AuditConfig",
28
+ "configure",
29
+ "get_storage",
30
+ "AuditMiddleware",
31
+ "create_audit_lifespan",
32
+ "set_audit_action",
33
+ "set_audit_resource",
34
+ "set_audit_extra",
35
+ "add_audit_log_routes",
36
+ ]
@@ -0,0 +1,86 @@
1
+ import typing
2
+ from typing import TYPE_CHECKING, Any, Literal
3
+
4
+ from pydantic import BaseModel, ConfigDict, Field
5
+
6
+ from .exceptions import AuditAlreadyConfiguredError, AuditNotConfiguredError
7
+
8
+ if TYPE_CHECKING:
9
+ from .storage.base import AuditStorage
10
+
11
+ ORMBackend = Literal["sqlalchemy", "tortoise", "sqlmodel", "beanie", "asyncpg"]
12
+
13
+
14
+ class AuditConfig(BaseModel):
15
+ model_config = ConfigDict(arbitrary_types_allowed=True)
16
+
17
+ # Required
18
+ orm: ORMBackend
19
+ dsn: str # full connection string e.g. "postgresql+asyncpg://user:pass@host/db"
20
+
21
+ # Table / collection config
22
+ table_name: str = "audit_logs"
23
+ auto_create_table: bool = True # CREATE TABLE IF NOT EXISTS on startup
24
+
25
+ # SQLAlchemy-specific
26
+ sqlalchemy_pool_size: int = 10
27
+ sqlalchemy_max_overflow: int = 20
28
+ sqlalchemy_pool_timeout: int = 30
29
+ sqlalchemy_echo: bool = False
30
+ expose_metadata: bool = False # if True, exposes Base.metadata for Alembic
31
+
32
+ # Tortoise-specific
33
+ tortoise_modules: dict[str, list[str]] | None = None
34
+
35
+ # MongoDB / Beanie-specific
36
+ mongodb_database: str = "audit"
37
+
38
+ # Batching (for all backends)
39
+ batch_size: int = 1 # set > 1 to enable batch inserts
40
+ batch_flush_interval: float = 5.0 # seconds, used if batch_size > 1
41
+
42
+ # PII masking
43
+ mask_fields: list[str] = Field(default_factory=list)
44
+
45
+ # Error handling
46
+ on_storage_error: Any = None # callable(exc: Exception, entry: AuditEntry) -> None
47
+
48
+
49
+ # Global registry holding the configured storage instance
50
+ _registry: dict[str, Any] = {}
51
+
52
+
53
+ def configure(config: AuditConfig) -> "AuditStorage":
54
+ """
55
+ Initialize and register the audit storage backend.
56
+ Must be called once at app startup before serving requests.
57
+ Returns the configured storage instance.
58
+ Raises AuditConfigurationError if config is invalid or backend cannot connect.
59
+ """
60
+ from .registry import resolve_storage
61
+
62
+ if "storage" in _registry:
63
+ existing_config = _registry["config"]
64
+ if existing_config == config:
65
+ return typing.cast("AuditStorage", _registry["storage"])
66
+ raise AuditAlreadyConfiguredError(
67
+ "Audit storage is already configured with a different configuration."
68
+ )
69
+
70
+ storage = resolve_storage(config)
71
+ _registry["storage"] = storage
72
+ _registry["config"] = config
73
+ return typing.cast("AuditStorage", storage)
74
+
75
+
76
+ def get_storage() -> "AuditStorage":
77
+ """
78
+ Retrieve the globally registered storage instance.
79
+ Raises AuditNotConfiguredError if configure() has not been called.
80
+ """
81
+ if "storage" not in _registry:
82
+ raise AuditNotConfiguredError(
83
+ "Audit storage has not been configured. Call configure() first."
84
+ )
85
+ return typing.cast("AuditStorage", _registry["storage"])
86
+
@@ -0,0 +1,33 @@
1
+ from contextvars import ContextVar
2
+ from typing import Any
3
+
4
+ from .models import AuditEntry
5
+
6
+ _current_entry: ContextVar[AuditEntry | None] = ContextVar("audit_entry", default=None)
7
+
8
+
9
+ def get_current_audit_entry() -> AuditEntry | None:
10
+ """Returns the current audit entry from the context."""
11
+ return _current_entry.get()
12
+
13
+
14
+ def set_audit_action(action: str) -> None:
15
+ """Sets the action field on the current audit entry."""
16
+ entry = get_current_audit_entry()
17
+ if entry:
18
+ entry.action = action
19
+
20
+
21
+ def set_audit_resource(resource_type: str, resource_id: str) -> None:
22
+ """Sets resource_type and resource_id fields on the current audit entry."""
23
+ entry = get_current_audit_entry()
24
+ if entry:
25
+ entry.resource_type = resource_type
26
+ entry.resource_id = resource_id
27
+
28
+
29
+ def set_audit_extra(key: str, value: Any) -> None:
30
+ """Adds a key-value pair to the extra field of the current audit entry."""
31
+ entry = get_current_audit_entry()
32
+ if entry:
33
+ entry.extra[key] = value
@@ -0,0 +1,42 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from beanie import Document
5
+ from pydantic import Field
6
+
7
+
8
+ class AuditLogDocument(Document):
9
+ """Beanie Document model for audit logs, with dynamic collection name set at runtime.""" # noqa: E501
10
+
11
+ id: uuid.UUID = Field(default_factory=uuid.uuid4)
12
+ timestamp: datetime
13
+ user_id: str | None = None
14
+ username: str | None = None
15
+ ip_address: str | None = None
16
+ user_agent: str | None = None
17
+ method: str
18
+ path: str
19
+ query_params: dict | None = None
20
+ status_code: int | None = None
21
+ request_body: dict | None = None
22
+ response_body: dict | None = None
23
+ duration_ms: float | None = None
24
+ action: str | None = None
25
+ resource_type: str | None = None
26
+ resource_id: str | None = None
27
+ extra: dict | None = None
28
+ error: str | None = None
29
+
30
+ class Settings:
31
+ """Settings for Beanie Document, with collection name set dynamically at runtime.""" # noqa: E501
32
+
33
+ name = "audit_logs" # overridden by config at runtime
34
+ indexes = [
35
+ "timestamp",
36
+ "user_id",
37
+ "path",
38
+ "status_code",
39
+ "action",
40
+ "resource_type",
41
+ "resource_id",
42
+ ]