fastapi-armor-lib 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,3 @@
1
+ from .armor import Armor
2
+
3
+ __all__ = ["Armor"]
fastapi_armor/armor.py ADDED
@@ -0,0 +1,27 @@
1
+ from fastapi import FastAPI
2
+
3
+ from .error_handler import add_exception_handlers
4
+ from .logger import setup_logger
5
+ from .sentry import init_sentry
6
+
7
+
8
+ class Armor:
9
+ def __init__(
10
+ self,
11
+ app: FastAPI,
12
+ log_file: str = "logs/app.log",
13
+ log_max_bytes: int = 5_000_000,
14
+ log_backup_count: int = 3,
15
+ sentry_dsn: str = "",
16
+ environment: str = "development",
17
+ ):
18
+ self.app = app
19
+ self.logger = setup_logger(
20
+ log_file=log_file,
21
+ max_bytes=log_max_bytes,
22
+ backup_count=log_backup_count,
23
+ )
24
+ add_exception_handlers(app=self.app, logger=self.logger)
25
+
26
+ if sentry_dsn:
27
+ init_sentry(dsn=sentry_dsn, environment=environment)
@@ -0,0 +1,36 @@
1
+ import logging
2
+
3
+ from fastapi import FastAPI, HTTPException, Request
4
+ from fastapi.exceptions import RequestValidationError
5
+ from fastapi.responses import JSONResponse
6
+
7
+ JSON_MEDIA_TYPE = "application/json; charset=utf-8"
8
+
9
+
10
+ def add_exception_handlers(app: FastAPI, logger: logging.Logger) -> None:
11
+ @app.exception_handler(HTTPException)
12
+ async def http_exception_handler(_: Request, exc: HTTPException) -> JSONResponse:
13
+ return JSONResponse(
14
+ status_code=exc.status_code,
15
+ content={"detail": exc.detail},
16
+ media_type=JSON_MEDIA_TYPE,
17
+ )
18
+
19
+ @app.exception_handler(RequestValidationError)
20
+ async def validation_exception_handler(
21
+ _: Request, exc: RequestValidationError
22
+ ) -> JSONResponse:
23
+ return JSONResponse(
24
+ status_code=422,
25
+ content={"error": "Validation failed", "detail": exc.errors()},
26
+ media_type=JSON_MEDIA_TYPE,
27
+ )
28
+
29
+ @app.exception_handler(Exception)
30
+ async def generic_exception_handler(_: Request, exc: Exception) -> JSONResponse:
31
+ logger.exception("Unhandled exception occurred", exc_info=exc)
32
+ return JSONResponse(
33
+ status_code=500,
34
+ content={"error": "Internal server error"},
35
+ media_type=JSON_MEDIA_TYPE,
36
+ )
@@ -0,0 +1,37 @@
1
+ import logging
2
+ from logging.handlers import RotatingFileHandler
3
+ from pathlib import Path
4
+
5
+
6
+ def setup_logger(
7
+ log_file: str = "logs/app.log",
8
+ max_bytes: int = 5_000_000,
9
+ backup_count: int = 3,
10
+ ) -> logging.Logger:
11
+ logger = logging.getLogger("fastapi_armor")
12
+ logger.setLevel(logging.INFO)
13
+
14
+ if logger.handlers:
15
+ return logger
16
+
17
+ log_path = Path(log_file)
18
+ log_path.parent.mkdir(parents=True, exist_ok=True)
19
+
20
+ formatter = logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
21
+
22
+ file_handler = RotatingFileHandler(
23
+ filename=log_path,
24
+ maxBytes=max_bytes,
25
+ backupCount=backup_count,
26
+ encoding="utf-8",
27
+ )
28
+ file_handler.setFormatter(formatter)
29
+
30
+ console_handler = logging.StreamHandler()
31
+ console_handler.setFormatter(formatter)
32
+
33
+ logger.addHandler(file_handler)
34
+ logger.addHandler(console_handler)
35
+ logger.propagate = False
36
+
37
+ return logger
@@ -0,0 +1,19 @@
1
+ from typing import Optional
2
+
3
+ import sentry_sdk
4
+ from sentry_sdk.integrations.fastapi import FastApiIntegration
5
+ from sentry_sdk.integrations.starlette import StarletteIntegration
6
+
7
+
8
+ def init_sentry(dsn: str, environment: str = "development") -> Optional[None]:
9
+ if not dsn:
10
+ return None
11
+
12
+ sentry_sdk.init(
13
+ dsn=dsn,
14
+ environment=environment,
15
+ integrations=[FastApiIntegration(), StarletteIntegration()],
16
+ traces_sample_rate=1.0,
17
+ send_default_pii=False,
18
+ )
19
+ return None
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-armor-lib
3
+ Version: 0.1.0
4
+ Summary: Production-ready error handling, logging, and monitoring for FastAPI apps
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: fastapi>=0.100.0
8
+ Requires-Dist: sentry-sdk[fastapi]>=1.40.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest; extra == "dev"
11
+ Requires-Dist: httpx; extra == "dev"
12
+ Requires-Dist: uvicorn; extra == "dev"
13
+
14
+ # fastapi-armor 🛡️
15
+
16
+ > Production-ready error handling, logging, and Sentry monitoring for FastAPI — in one line.
17
+
18
+ [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/)
19
+ [![FastAPI](https://img.shields.io/badge/FastAPI-0.100+-009688.svg)](https://fastapi.tiangolo.com/)
20
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
21
+ [![PyPI version](https://img.shields.io/pypi/v/fastapi-armor.svg)](https://pypi.org/project/fastapi-armor/)
22
+
23
+ ---
24
+
25
+ ## Why fastapi-armor?
26
+
27
+ Every production FastAPI app needs the same boilerplate: structured logging, clean error responses, and error monitoring. **fastapi-armor** wires all of that up automatically — no copy-pasting, no missing edge cases.
28
+
29
+ **Before:**
30
+ ```python
31
+ from fastapi import FastAPI, HTTPException, Request
32
+ from fastapi.responses import JSONResponse
33
+ import logging, logging.handlers, sentry_sdk
34
+ # ... 60+ lines of setup code
35
+
36
+ app = FastAPI()
37
+ ```
38
+
39
+ **After:**
40
+ ```python
41
+ from fastapi import FastAPI
42
+ from fastapi_armor import Armor
43
+
44
+ app = FastAPI()
45
+ Armor(app) # ✅ Done.
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ pip install fastapi-armor
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Quick Start
59
+
60
+ ```python
61
+ from fastapi import FastAPI
62
+ from fastapi_armor import Armor
63
+
64
+ app = FastAPI()
65
+
66
+ Armor(
67
+ app,
68
+ log_file="logs/app.log",
69
+ sentry_dsn="https://your-dsn@sentry.io/123", # optional
70
+ environment="production",
71
+ )
72
+
73
+ @app.get("/")
74
+ def root():
75
+ return {"status": "running"}
76
+ ```
77
+
78
+ That's it. Your app now has:
79
+ - ✅ Rotating file logs + console output
80
+ - ✅ Clean JSON error responses for all exception types
81
+ - ✅ Sentry integration (if DSN is provided)
82
+
83
+ ---
84
+
85
+ ## What It Does
86
+
87
+ ### 🔴 Error Handling
88
+
89
+ | Exception Type | Response |
90
+ |---|---|
91
+ | `HTTPException` | `{"detail": "..."}` with original status code |
92
+ | `RequestValidationError` | `{"error": "Validation failed", "detail": [...]}` with 422 |
93
+ | Any other `Exception` | `{"error": "Internal server error"}` with 500 (full traceback logged internally) |
94
+
95
+ All responses use `Content-Type: application/json; charset=utf-8`.
96
+
97
+ ### 📋 Logging
98
+
99
+ - **Rotating file logs** — auto-creates the `logs/` directory
100
+ - **Console output** — for local development
101
+ - **Format:** `2024-01-01 12:00:00 | ERROR | Traceback (most recent call last): ...`
102
+ - Named logger: `fastapi_armor`
103
+
104
+ ### 🔍 Sentry (optional)
105
+
106
+ Initializes `sentry_sdk` with `FastApiIntegration` and `StarletteIntegration` when a DSN is provided. Private data is not sent by default.
107
+
108
+ ---
109
+
110
+ ## Parameters
111
+
112
+ | Parameter | Type | Default | Description |
113
+ |---|---|---|---|
114
+ | `app` | `FastAPI` | required | Your FastAPI application instance |
115
+ | `log_file` | `str` | `"logs/app.log"` | Path to the rotating log file |
116
+ | `log_max_bytes` | `int` | `5_000_000` | Max size per log file (5 MB) |
117
+ | `log_backup_count` | `int` | `3` | Number of backup log files to keep |
118
+ | `sentry_dsn` | `str` | `""` | Sentry DSN — leave empty to disable |
119
+ | `environment` | `str` | `"development"` | Environment tag sent to Sentry |
120
+
121
+ ---
122
+
123
+ ## Requirements
124
+
125
+ - Python 3.9+
126
+ - FastAPI 0.100+
127
+ - sentry-sdk 1.40+ *(installed automatically)*
128
+
129
+ ---
130
+
131
+ ## Development
132
+
133
+ ```bash
134
+ git clone https://github.com/raghadalb-tech/fastapi-armor.git
135
+ cd fastapi-armor
136
+ pip install -e ".[dev]"
137
+ pytest tests/ -v
138
+ ```
139
+
140
+ Test output:
@@ -0,0 +1,9 @@
1
+ fastapi_armor/__init__.py,sha256=L--H3LWEWrvAxV7XEYKxPlVooWggAckMw98A5Gbut20,49
2
+ fastapi_armor/armor.py,sha256=JVYS6vIt0pcmwVmuz3Mg2d07txxjBAvA7i2ER-saJ14,768
3
+ fastapi_armor/error_handler.py,sha256=ik28lhUvI65TP1AyIsmNrb5nCWv0LNGRTqipRyGZuC0,1332
4
+ fastapi_armor/logger.py,sha256=WY5OgP6VGVed02tbMdz8t5OZK9nJL4rq8tb9T544Hxo,975
5
+ fastapi_armor/sentry.py,sha256=UiUQr_nbsowZDVCakjz4HTg2ClVW82Pceazy3Q4ZRUw,540
6
+ fastapi_armor_lib-0.1.0.dist-info/METADATA,sha256=9Ftgb_TJjsxNmOfWINtwfOfVSkr01Z1BOxcxHR1O4M4,3849
7
+ fastapi_armor_lib-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
8
+ fastapi_armor_lib-0.1.0.dist-info/top_level.txt,sha256=I-0oX8mMbAJwIsoxfh2aGYzOC3ADD3bapAshG0cjEMs,14
9
+ fastapi_armor_lib-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ fastapi_armor