responseapi 1.0.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,31 @@
1
+ from responseapi._exceptions.api_exception import APIException
2
+ from responseapi._interceptors.handlers import (
3
+ api_exception_handler,
4
+ http_exception_handler,
5
+ register_exception_handlers,
6
+ unhandled_exception_handler,
7
+ validation_exception_handler,
8
+ )
9
+ from responseapi._schemas.response_schema import (
10
+ CustomErrorResponseSchema,
11
+ CustomSuccessResponseSchema,
12
+ )
13
+ from responseapi._services.response_service import APIResponse
14
+
15
+ __all__ = [
16
+ "APIException",
17
+ "APIResponse",
18
+ "CustomErrorResponseSchema",
19
+ "CustomSuccessResponseSchema",
20
+ "api_exception_handler",
21
+ "http_exception_handler",
22
+ "register_exception_handlers",
23
+ "unhandled_exception_handler",
24
+ "validation_exception_handler",
25
+ ]
26
+
27
+
28
+ def __getattr__(name: str):
29
+ if name in __all__:
30
+ return globals()[name]
31
+ raise AttributeError(f"module {__name__} has no attribute {name}")
@@ -0,0 +1,3 @@
1
+ from responseapi._exceptions.api_exception import APIException
2
+
3
+ __all__ = ["APIException"]
@@ -0,0 +1,36 @@
1
+ from typing import Any, Mapping
2
+
3
+ from fastapi import status
4
+
5
+
6
+ class APIException(Exception):
7
+ """Custom API Exception for domain-level errors.
8
+
9
+ This exception mirrors FastAPI's ``HTTPException`` style but is a plain
10
+ ``Exception`` that you can raise from your business logic.
11
+
12
+ Parameters
13
+ ----------
14
+ error:
15
+ Human-readable error message that will be exposed to the client.
16
+ errors:
17
+ Optional extra error data (e.g., validation errors) placed under
18
+ an ``errors`` key in the JSON response.
19
+ status_code:
20
+ HTTP status code to return. Defaults to ``400 Bad Request``.
21
+ headers:
22
+ Optional extra HTTP headers to include in the response.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ error: str = "An error occurred",
28
+ errors: Any = None,
29
+ status_code: int = status.HTTP_400_BAD_REQUEST,
30
+ headers: Mapping[str, str] | None = None,
31
+ ) -> None:
32
+ super().__init__(error)
33
+ self.error = error
34
+ self.errors = errors
35
+ self.status_code = status_code
36
+ self.headers = headers
@@ -0,0 +1,15 @@
1
+ from responseapi._interceptors.handlers import (
2
+ api_exception_handler,
3
+ http_exception_handler,
4
+ register_exception_handlers,
5
+ unhandled_exception_handler,
6
+ validation_exception_handler,
7
+ )
8
+
9
+ __all__ = [
10
+ "api_exception_handler",
11
+ "http_exception_handler",
12
+ "validation_exception_handler",
13
+ "unhandled_exception_handler",
14
+ "register_exception_handlers",
15
+ ]
@@ -0,0 +1,66 @@
1
+ from fastapi import FastAPI, Request, status
2
+ from fastapi.exceptions import HTTPException, RequestValidationError
3
+ from fastapi.responses import JSONResponse
4
+
5
+ from responseapi._exceptions.api_exception import APIException
6
+ from responseapi._services.response_service import APIResponse
7
+
8
+
9
+ async def api_exception_handler(_: Request, exc: APIException) -> JSONResponse:
10
+ """
11
+ Handles Domain-level APIExceptions cleanly
12
+ """
13
+ return APIResponse.error(
14
+ error=exc.error,
15
+ errors=exc.errors,
16
+ status_code=exc.status_code,
17
+ headers=getattr(exc, "headers", None),
18
+ )
19
+
20
+
21
+ async def http_exception_handler(_: Request, exc: HTTPException) -> JSONResponse:
22
+ """
23
+ Handles HTTPExceptions cleanly
24
+ """
25
+ return APIResponse.error(
26
+ error=str(exc.detail) if hasattr(exc, "detail") else str(exc),
27
+ errors=getattr(exc, "errors", None),
28
+ status_code=exc.status_code,
29
+ headers=getattr(exc, "headers", None),
30
+ )
31
+
32
+
33
+ async def validation_exception_handler(
34
+ _: Request, exc: RequestValidationError
35
+ ) -> JSONResponse:
36
+ """
37
+ Handles ValidationExceptions cleanly
38
+ """
39
+ return APIResponse.error(
40
+ error="Validation error",
41
+ errors=exc.errors(),
42
+ status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
43
+ )
44
+
45
+
46
+ async def unhandled_exception_handler(_: Request, exc: Exception) -> JSONResponse:
47
+ """
48
+ Handles unhandled exceptions cleanly
49
+ """
50
+ return APIResponse.error(
51
+ error="Internal server error",
52
+ errors=str(exc),
53
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
54
+ )
55
+
56
+
57
+ def register_exception_handlers(app: FastAPI, include_unhandled: bool = True) -> None:
58
+ """
59
+ Hook all responseapi exception interceptors in the FastAPI lifecycle
60
+ """
61
+ app.add_exception_handler(APIException, api_exception_handler)
62
+ app.add_exception_handler(HTTPException, http_exception_handler)
63
+ app.add_exception_handler(RequestValidationError, validation_exception_handler)
64
+
65
+ if include_unhandled:
66
+ app.add_exception_handler(Exception, unhandled_exception_handler)
@@ -0,0 +1,6 @@
1
+ from responseapi._schemas.response_schema import (
2
+ CustomErrorResponseSchema,
3
+ CustomSuccessResponseSchema,
4
+ )
5
+
6
+ __all__ = ["CustomSuccessResponseSchema", "CustomErrorResponseSchema"]
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import field
4
+ from typing import Generic, List, Optional, TypeVar, Union
5
+
6
+ from dataclasses import dataclass
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ @dataclass(slots=True)
12
+ class CustomSuccessResponseSchema(Generic[T]):
13
+ """Custom success response schema for API endpoints.
14
+
15
+ Attributes
16
+ ----------
17
+ success: bool
18
+ Indicates the request succeeded.
19
+ data: Optional[Union[T, List[T]]]
20
+ The payload returned by the endpoint. Can be a single object or a list.
21
+ message: Optional[str]
22
+ Human‑readable description of the result.
23
+ """
24
+
25
+ success: bool = field(default=True)
26
+ data: Optional[Union[T, List[T]]] = field(default=None)
27
+ message: Optional[str] = field(default=None)
28
+
29
+
30
+ @dataclass(slots=True)
31
+ class CustomErrorResponseSchema(Generic[T]):
32
+ """Custom error response schema for API endpoints.
33
+
34
+ Attributes
35
+ ----------
36
+ success: bool
37
+ Always ``False`` for error responses.
38
+ errors: Optional[Union[T, List[T]]]
39
+ Detailed error information.
40
+ error: Optional[str]
41
+ Short error message.
42
+ """
43
+
44
+ success: bool = field(default=False)
45
+ errors: Optional[Union[T, List[T]]] = field(default=None)
46
+ error: Optional[str] = field(default=None)
@@ -0,0 +1,3 @@
1
+ from responseapi._services.response_service import APIResponse
2
+
3
+ __all__ = ["APIResponse"]
@@ -0,0 +1,64 @@
1
+ from typing import Any
2
+
3
+ from fastapi import status
4
+ from fastapi.encoders import jsonable_encoder
5
+ from fastapi.responses import JSONResponse
6
+
7
+
8
+ class APIResponse:
9
+ """
10
+ Factory class for constructing standardized API JSON responses.
11
+ """
12
+
13
+ @staticmethod
14
+ def success(
15
+ data: Any = None,
16
+ message: str = "Operation completed successfully",
17
+ status_code: int = status.HTTP_200_OK,
18
+ headers: dict[str, str] | None = None,
19
+ ) -> JSONResponse:
20
+ """
21
+ Generates a standard HTTP success response
22
+ """
23
+
24
+ if status_code == status.HTTP_204_NO_CONTENT:
25
+ return JSONResponse(
26
+ status_code=status_code,
27
+ content=None,
28
+ headers=headers,
29
+ )
30
+
31
+ payload = {
32
+ "success": True,
33
+ "message": message,
34
+ "data": jsonable_encoder(data),
35
+ }
36
+
37
+ return JSONResponse(
38
+ content=payload,
39
+ status_code=status_code,
40
+ headers=headers,
41
+ )
42
+
43
+ @staticmethod
44
+ def error(
45
+ error: str = "An error occurred",
46
+ errors: Any = None,
47
+ status_code: int = status.HTTP_400_BAD_REQUEST,
48
+ headers: dict[str, str] | None = None,
49
+ ) -> JSONResponse:
50
+ """
51
+ Generates a standard HTTP error response
52
+ """
53
+
54
+ payload = {
55
+ "success": False,
56
+ "error": error,
57
+ "errors": jsonable_encoder(errors),
58
+ }
59
+
60
+ return JSONResponse(
61
+ content=payload,
62
+ status_code=status_code,
63
+ headers=headers,
64
+ )
responseapi/py.typed ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: responseapi
3
+ Version: 1.0.0
4
+ Summary: A clean, production-grade standardized response envelope framework for FastAPI applications.
5
+ Author-email: Dev Raj Khadka <devrajkhadka941@gmail.com>, Aryan Tamang <aryantamangcs@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: api-design,exception-handler,fastapi,jsonresponse,middleware,openapi,pydantic
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Framework :: FastAPI
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: fastapi>=0.100.0
21
+ Requires-Dist: pydantic>=2.0.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # responseapi
25
+
26
+ [![PyPI version](https://img.shields.io/pypi/v/responseapi.svg)](https://pypi.org/project/responseapi/)
27
+ [![Python Version](https://img.shields.io/pypi/pyversions/responseapi.svg)](https://pypi.org/project/responseapi/)
28
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
29
+
30
+ A clean, production-grade standardized response envelope framework for **FastAPI** applications.
31
+
32
+ ---
33
+
34
+ ## 📌 Features
35
+
36
+ - **Standardized Response Format**: Enforces unified JSON response envelopes for both success and error responses across your API.
37
+ - **Automated Exception Handling**: Seamlessly intercepts domain-level `APIException`, FastAPI `HTTPException`, `RequestValidationError`, and unhandled server errors.
38
+ - **Pydantic & OpenAPI Integration**: Generic models (`APISuccessResponseModel`, `CustomSuccessResponseSchema`, `CustomErrorResponseSchema`) for type safety and automatic OpenAPI schema generation.
39
+ - **Structured Logging**: Built-in exception logging using `loguru`.
40
+ - **Zero Boilerplate**: Simple registration via `register_exception_handlers(app)`.
41
+
42
+ ---
43
+
44
+ ## 🚀 Installation
45
+
46
+ Install via `pip`:
47
+
48
+ ```bash
49
+ pip install responseapi
50
+ ```
51
+
52
+ Or using `uv`:
53
+
54
+ ```bash
55
+ uv add responseapi
56
+ ```
57
+
58
+ Or using `poetry`:
59
+
60
+ ```bash
61
+ poetry add responseapi
62
+ ```
63
+
64
+ ---
65
+
66
+ ## 💡 Quickstart
67
+
68
+ ```python
69
+ from fastapi import FastAPI
70
+ from responseapi import (
71
+ APIResponse,
72
+ APIException,
73
+ register_exception_handlers,
74
+ )
75
+
76
+ app = FastAPI(title="My API")
77
+
78
+ # Register standardized exception handlers
79
+ register_exception_handlers(app)
80
+
81
+ @app.get("/users/{user_id}")
82
+ def get_user(user_id: int):
83
+ if user_id <= 0:
84
+ raise APIException(
85
+ error="User not found",
86
+ errors={"user_id": "Must be a positive integer"},
87
+ status_code=404,
88
+ )
89
+
90
+ return APIResponse.success(
91
+ data={"user_id": user_id, "username": "johndoe"},
92
+ message="User details retrieved successfully",
93
+ )
94
+ ```
95
+
96
+ ---
97
+
98
+ ## 📦 Response Envelopes
99
+
100
+ ### 1. Success Response Structure (`HTTP 200 OK`)
101
+
102
+ ```json
103
+ {
104
+ "success": true,
105
+ "message": "User details retrieved successfully",
106
+ "data": {
107
+ "user_id": 1,
108
+ "username": "johndoe"
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### 2. Error Response Structure (`HTTP 400 / 404 / 422 / 500`)
114
+
115
+ ```json
116
+ {
117
+ "success": false,
118
+ "error": "User not found",
119
+ "errors": {
120
+ "user_id": "Must be a positive integer"
121
+ }
122
+ }
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 📖 API Reference
128
+
129
+ ### `APIResponse`
130
+
131
+ Factory class to return standardized `JSONResponse` objects:
132
+
133
+ - `APIResponse.success(data=None, message="Operation completed successfully", status_code=200, headers=None)`
134
+ - `APIResponse.error(error="An error occurred", errors=None, status_code=400, headers=None)`
135
+
136
+ ### `APIException`
137
+
138
+ Custom domain exception for raising controlled errors:
139
+
140
+ ```python
141
+ raise APIException(
142
+ error="Insufficient funds",
143
+ errors={"balance": 10.0, "required": 50.0},
144
+ status_code=400
145
+ )
146
+ ```
147
+
148
+ ### `register_exception_handlers(app: FastAPI, include_unhandled: bool = True)`
149
+
150
+ Registers error handlers for:
151
+ - `APIException` -> Returns `APIResponse.error(...)`
152
+ - `HTTPException` -> Returns `APIResponse.error(...)`
153
+ - `RequestValidationError` -> Returns validation errors formatted with status `422`
154
+ - `Exception` (optional) -> Catches unhandled errors and returns status `500`
155
+
156
+ ### Pydantic Models & Schemas
157
+
158
+ - `APISuccessResponseModel[T]`: Pydantic generic model for standard success envelopes.
159
+ - `CustomSuccessResponseSchema[T]`: Pydantic generic schema for success payloads.
160
+ - `CustomErrorResponseSchema[T]`: Pydantic generic schema for error payloads.
161
+
162
+ ---
163
+
164
+ ## 🛠️ Deploying / Publishing to PyPI
165
+
166
+ Follow these instructions to build and publish `responseapi` to **PyPI** (Python Package Index).
167
+
168
+ ### 1. Prerequisites
169
+
170
+ Ensure you have a PyPI account registered at [pypi.org](https://pypi.org) and an API token or Trusted Publisher configuration set up.
171
+
172
+ ### 2. Build the Package
173
+
174
+ You can build the package using `uv` or standard Python `build`:
175
+
176
+ #### Using `uv`:
177
+ ```bash
178
+ uv build
179
+ ```
180
+
181
+ #### Using `build`:
182
+ ```bash
183
+ pip install build twine
184
+ python -m build
185
+ ```
186
+
187
+ This generates distribution archives in the `dist/` directory:
188
+ - `dist/responseapi-1.0.0-py3-none-any.whl` (Wheel)
189
+ - `dist/responseapi-1.0.0.tar.gz` (Source distribution)
190
+
191
+ ### 3. Upload to PyPI
192
+
193
+ #### Option A: Using `uv publish`
194
+
195
+ Set your API token and publish:
196
+
197
+ ```bash
198
+ uv publish --token <your-pypi-token>
199
+ ```
200
+
201
+ Or publish to **TestPyPI** first to verify:
202
+
203
+ ```bash
204
+ uv publish --publish-url https://test.pypi.org/legacy/ --token <your-testpypi-token>
205
+ ```
206
+
207
+ #### Option B: Using `twine`
208
+
209
+ ```bash
210
+ twine check dist/*
211
+ twine upload dist/* -u __token__ -p <your-pypi-token>
212
+ ```
213
+
214
+ ### 4. Automated Publishing via GitHub Actions (Recommended)
215
+
216
+ This project includes a `.github/workflows/publish.yml` GitHub Actions workflow.
217
+
218
+ 1. Go to your GitHub repository **Settings > Secrets and variables > Actions**.
219
+ 2. Create a secret named `PYPI_API_TOKEN` containing your PyPI API token.
220
+ 3. Push a new release or git tag (e.g., `git tag v1.0.0 && git push origin v1.0.0`).
221
+ 4. GitHub Actions will automatically test, build, and publish the package to PyPI!
222
+
223
+ ---
224
+
225
+ ## 📄 License
226
+
227
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,14 @@
1
+ responseapi/__init__.py,sha256=JGIhKp6f3cyrj4yiEcFunX9Dn1r4uOrdRWsl3dn1bBE,892
2
+ responseapi/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
3
+ responseapi/_exceptions/__init__.py,sha256=u71rvrTpfEONlbnPTowqcXdtlK2R-Ld0JUhTPqsKba0,91
4
+ responseapi/_exceptions/api_exception.py,sha256=GFdQAjPCcm_dsBNQo4OpmoESQCm7qCxhY-_TDTI4Npw,1087
5
+ responseapi/_interceptors/__init__.py,sha256=YgSRaXq5XTHGY29Yu4ViELMAnCg1Dq9VqD_w_Jw1Etk,386
6
+ responseapi/_interceptors/handlers.py,sha256=oF8H3toM7UANh83z8FYA-JtRm4KaFqd2uoa2ts5Tp_w,2091
7
+ responseapi/_schemas/__init__.py,sha256=yhw6LUWBOkihsPVWDXitiKXN8o64eGlW3wGXmHcKQaA,189
8
+ responseapi/_schemas/response_schema.py,sha256=h0P6JDCbUNu_LnfmVffhnvfazdwEELi4gkGdMOTADNE,1261
9
+ responseapi/_services/__init__.py,sha256=7RttsGHBoc6VNrrF1cjFW7tPJIUp6o3AZAeBziDqUWQ,90
10
+ responseapi/_services/response_service.py,sha256=4SVYNWrW0rfmvX3D9-cJtUWfFxUwJWpxlKMICyZEzFs,1610
11
+ responseapi-1.0.0.dist-info/METADATA,sha256=kt99qfQZl-8kS_4hUTA-TavnHSBri8OBSK-esBU7jYQ,6239
12
+ responseapi-1.0.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
13
+ responseapi-1.0.0.dist-info/licenses/LICENSE,sha256=623cFq2VY_6DALSkVetrK2FurEfXkiSz9_z3Lk7rjQM,1076
14
+ responseapi-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 responseapi Authors
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.