jder-fastapi 0.1.0.dev1__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.
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: jder-fastapi
3
+ Version: 0.1.0.dev1
4
+ Summary: A response builder for FastAPI
5
+ Keywords: jder,json,response,fastapi,python
6
+ Author: Alpheus
7
+ Author-email: Alpheus <contact@alphe.us>
8
+ License-Expression: MIT
9
+ Classifier: Topic :: File Formats
10
+ Classifier: Topic :: File Formats :: JSON
11
+ Classifier: Topic :: File Formats :: JSON :: JSON Schema
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Programming Language :: Python :: 3.15
16
+ Requires-Dist: fastapi>=0.116.0,<0.117.0
17
+ Requires-Python: >=3.13
18
+ Project-URL: Changelog, https://github.com/jder-std/fastapi/blob/main/package/CHANGELOG.md
19
+ Project-URL: Documentation, https://github.com/jder-std/fastapi/blob/main/docs/README.md
20
+ Project-URL: Homepage, https://github.com/jder-std/fastapi
21
+ Project-URL: Issues, https://github.com/jder-std/fastapi/issues
22
+ Project-URL: Repository, https://github.com/jder-std/fastapi
23
+ Description-Content-Type: text/markdown
24
+
25
+ # JDER FastAPI
26
+
27
+ A response builder for FastAPI.
28
+
29
+ This package includes different response builders based on the JSON response structure specified in [JSON Data Errors Response (JDER)](https://github.com/jder-std/spec). With the builders, various kinds of responses can be created easily instead of sending plain text responses.
30
+
31
+ ## Quick Start
32
+
33
+ To create a JSON response, use the following code:
34
+
35
+ ```python
36
+ from fastapi import FastAPI
37
+ from fastapi.responses import Response
38
+ from jder_fastapi.responses.json import createJsonResponse
39
+
40
+ app: FastAPI = FastAPI()
41
+
42
+ @app.get("/")
43
+ async def route() -> Response:
44
+ return createJsonResponse()
45
+ ```
46
+
47
+ And the response will be shown as below:
48
+
49
+ ```json
50
+ {
51
+ "success": true
52
+ }
53
+ ```
54
+
55
+ ## License
56
+
57
+ This project is licensed under the terms of the MIT license.
@@ -0,0 +1,33 @@
1
+ # JDER FastAPI
2
+
3
+ A response builder for FastAPI.
4
+
5
+ This package includes different response builders based on the JSON response structure specified in [JSON Data Errors Response (JDER)](https://github.com/jder-std/spec). With the builders, various kinds of responses can be created easily instead of sending plain text responses.
6
+
7
+ ## Quick Start
8
+
9
+ To create a JSON response, use the following code:
10
+
11
+ ```python
12
+ from fastapi import FastAPI
13
+ from fastapi.responses import Response
14
+ from jder_fastapi.responses.json import createJsonResponse
15
+
16
+ app: FastAPI = FastAPI()
17
+
18
+ @app.get("/")
19
+ async def route() -> Response:
20
+ return createJsonResponse()
21
+ ```
22
+
23
+ And the response will be shown as below:
24
+
25
+ ```json
26
+ {
27
+ "success": true
28
+ }
29
+ ```
30
+
31
+ ## License
32
+
33
+ This project is licensed under the terms of the MIT license.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.8.4,<0.9.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "jder-fastapi"
7
+ version = "0.1.0.dev1"
8
+ dependencies = [
9
+ "fastapi>=0.116.0,<0.117.0",
10
+ ]
11
+ requires-python = ">=3.13"
12
+ authors = [
13
+ { name = "Alpheus", email = "contact@alphe.us" }
14
+ ]
15
+ description = "A response builder for FastAPI"
16
+ readme = "README.md"
17
+ license = "MIT"
18
+ keywords = [
19
+ "jder",
20
+ "json",
21
+ "response",
22
+ "fastapi",
23
+ "python",
24
+ ]
25
+ classifiers = [
26
+ "Topic :: File Formats",
27
+ "Topic :: File Formats :: JSON",
28
+ "Topic :: File Formats :: JSON :: JSON Schema",
29
+ "Framework :: FastAPI",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Programming Language :: Python :: 3.14",
32
+ "Programming Language :: Python :: 3.15",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/jder-std/fastapi"
37
+ Documentation = "https://github.com/jder-std/fastapi/blob/main/docs/README.md"
38
+ Repository = "https://github.com/jder-std/fastapi"
39
+ Issues = "https://github.com/jder-std/fastapi/issues"
40
+ Changelog = "https://github.com/jder-std/fastapi/blob/main/package/CHANGELOG.md"
@@ -0,0 +1 @@
1
+ """JDER FastAPI: A response builder for FastAPI"""
@@ -0,0 +1,5 @@
1
+ from .validation import request_validation_exception_handler
2
+
3
+ __all__ = [
4
+ "request_validation_exception_handler",
5
+ ]
@@ -0,0 +1,5 @@
1
+ from .main import request_validation_exception_handler
2
+
3
+ __all__ = [
4
+ "request_validation_exception_handler",
5
+ ]
@@ -0,0 +1,85 @@
1
+ from typing import Sequence
2
+
3
+ from fastapi.exceptions import RequestValidationError
4
+ from fastapi.requests import Request
5
+ from fastapi.responses import JSONResponse
6
+ from pydantic import BaseModel, ConfigDict, TypeAdapter
7
+
8
+ from jder_fastapi.responses.json import (
9
+ CreateJsonFailureResponseOptions,
10
+ JsonResponseError,
11
+ createJsonResponse,
12
+ )
13
+
14
+
15
+ class Error(BaseModel):
16
+ model_config = ConfigDict(extra="ignore")
17
+ type: str | None
18
+ loc: list[str] | None
19
+ msg: str | None
20
+
21
+
22
+ Errors = TypeAdapter(Sequence[Error])
23
+
24
+
25
+ def request_validation_exception_handler(
26
+ request: Request, exc: RequestValidationError
27
+ ) -> JSONResponse:
28
+ """
29
+ A custom exception handler for `RequestValidationError`.
30
+
31
+ ### Example
32
+
33
+ ```python
34
+ from fastapi import FastAPI
35
+ from fastapi.requests import Request
36
+ from fastapi.responses import JSONResponse
37
+ from fastapi.exceptions import RequestValidationError
38
+ from jder_fastapi.handlers import request_validation_exception_handler
39
+
40
+ app: FastAPI = FastAPI()
41
+
42
+
43
+ @app.exception_handler(RequestValidationError)
44
+ async def validation_exception_handler(
45
+ req: Request,
46
+ exc: RequestValidationError,
47
+ ) -> JSONResponse:
48
+ return request_validation_exception_handler(req, exc)
49
+ ```
50
+ """
51
+ errs: Sequence[Error] = Errors.validate_python(exc.errors())
52
+
53
+ status: int = 400
54
+
55
+ code: str = "parse"
56
+
57
+ if len(errs) == 0:
58
+ return createJsonResponse(
59
+ options=CreateJsonFailureResponseOptions(
60
+ status=status,
61
+ errors=[
62
+ JsonResponseError(
63
+ code=code,
64
+ )
65
+ ],
66
+ )
67
+ )
68
+
69
+ errors: list[JsonResponseError] = []
70
+
71
+ for err in errs:
72
+ errors.append(
73
+ JsonResponseError(
74
+ code=code + f".{err.type}",
75
+ path=err.loc,
76
+ message=err.msg,
77
+ )
78
+ )
79
+
80
+ return createJsonResponse(
81
+ options=CreateJsonFailureResponseOptions(
82
+ status=status,
83
+ errors=errors,
84
+ )
85
+ )
File without changes
@@ -0,0 +1,9 @@
1
+ from .main import (
2
+ CreateResponseOptions,
3
+ createResponse,
4
+ )
5
+
6
+ __all__ = [
7
+ "CreateResponseOptions",
8
+ "createResponse",
9
+ ]
@@ -0,0 +1,17 @@
1
+ from .main import (
2
+ CreateJsonFailureResponseOptions,
3
+ CreateJsonResponseBaseOptions,
4
+ CreateJsonSuccessResponseOptions,
5
+ JsonResponse,
6
+ JsonResponseError,
7
+ createJsonResponse,
8
+ )
9
+
10
+ __all__ = [
11
+ "JsonResponseError",
12
+ "JsonResponse",
13
+ "CreateJsonResponseBaseOptions",
14
+ "CreateJsonSuccessResponseOptions",
15
+ "CreateJsonFailureResponseOptions",
16
+ "createJsonResponse",
17
+ ]
@@ -0,0 +1,195 @@
1
+ from typing import Any, Mapping, Optional, TypeVar
2
+
3
+ from fastapi.responses import JSONResponse, Response
4
+ from pydantic import BaseModel
5
+
6
+ T = TypeVar("T")
7
+
8
+
9
+ class JsonResponseError(BaseModel):
10
+ """
11
+ JSON response error.
12
+ """
13
+
14
+ code: str
15
+ """
16
+ Code representing the error.
17
+ """
18
+ path: Optional[list[str]] = None
19
+ """
20
+ Indicates where the error occurred.
21
+ """
22
+ message: Optional[str] = None
23
+ """
24
+ Detail of the error.
25
+ """
26
+
27
+
28
+ class JsonResponse[T = Any](BaseModel):
29
+ """
30
+ JSON response.
31
+ """
32
+
33
+ success: bool
34
+ """
35
+ Indicates whether the response is successful or not.
36
+ """
37
+ data: Optional[T] = None
38
+ """
39
+ Requested information for the response when `success` is `true`.
40
+ """
41
+ errors: Optional[list[JsonResponseError]] = None
42
+ """
43
+ A list of errors for the response when `success` is `false`.
44
+ """
45
+
46
+
47
+ class CreateJsonResponseBaseOptions(BaseModel):
48
+ """
49
+ Base options of `createJsonResponse` function.
50
+ """
51
+
52
+ status: Optional[int] = None
53
+ """
54
+ Status code of the response.
55
+ By default, it is `200` for success and `400` for failure.
56
+ """
57
+ headers: Optional[Mapping[str, str]] = None
58
+ """
59
+ Additional headers of the response.
60
+ """
61
+
62
+
63
+ class CreateJsonSuccessResponseOptions[T = Any](CreateJsonResponseBaseOptions):
64
+ """
65
+ Options of `createJsonResponse` function.
66
+ """
67
+
68
+ data: Optional[T] = None
69
+ """
70
+ Requested information for the response when `success` is `true`.
71
+ """
72
+
73
+
74
+ class CreateJsonFailureResponseOptions(CreateJsonResponseBaseOptions):
75
+ """
76
+ Options of `createJsonResponse` function.
77
+ """
78
+
79
+ errors: Optional[list[JsonResponseError]] = None
80
+ """
81
+ A list of errors for the response when `success` is `false`.
82
+ """
83
+
84
+
85
+ def createJsonResponse(
86
+ response: Response | None = None,
87
+ options: CreateJsonSuccessResponseOptions[T]
88
+ | CreateJsonFailureResponseOptions
89
+ | None = None,
90
+ ) -> JSONResponse:
91
+ """
92
+ Create a JSON response.
93
+
94
+ ### Examples
95
+
96
+ Example for creating a successful JSON response without data:
97
+
98
+ ```python
99
+ from fastapi import FastAPI
100
+ from fastapi.responses import Response
101
+ from jder_fastapi.responses.json import createJsonResponse
102
+
103
+ app: FastAPI = FastAPI()
104
+
105
+
106
+ @app.get("/")
107
+ async def route() -> Response:
108
+ return createJsonResponse()
109
+ ```
110
+
111
+ Example for creating a successful JSON response with data:
112
+
113
+ ```python
114
+ from fastapi import FastAPI
115
+ from fastapi.responses import Response
116
+ from jder_fastapi.responses.json import createJsonResponse
117
+
118
+ app: FastAPI = FastAPI()
119
+
120
+
121
+ @app.get("/")
122
+ async def route() -> Response:
123
+ return createJsonResponse(
124
+ options={
125
+ "data": "Hello, World!",
126
+ }
127
+ )
128
+ ```
129
+
130
+ Example for creating a failure JSON response:
131
+
132
+ ```python
133
+ from fastapi import FastAPI
134
+ from fastapi.responses import Response
135
+ from jder_fastapi.responses.json import createJsonResponse
136
+
137
+ app: FastAPI = FastAPI()
138
+
139
+
140
+ @app.get("/")
141
+ async def route() -> Response:
142
+ return createJsonResponse(
143
+ options={
144
+ "status": 500,
145
+ "errors": [
146
+ {
147
+ "code": "server",
148
+ "message": "Internal server error",
149
+ },
150
+ ],
151
+ }
152
+ )
153
+ ```
154
+
155
+ Example for merging response:
156
+
157
+ ```python
158
+ from fastapi import FastAPI
159
+ from fastapi.responses import Response
160
+ from jder_fastapi.responses.json import createJsonResponse
161
+
162
+ app: FastAPI = FastAPI()
163
+
164
+
165
+ @app.get("/")
166
+ async def route(res: Response) -> Response:
167
+ return createJsonResponse(res)
168
+ ```
169
+ """
170
+ is_failure: bool = isinstance(options, CreateJsonFailureResponseOptions)
171
+
172
+ status: int = (
173
+ options.status
174
+ if options and options.status
175
+ else (400 if is_failure else 200)
176
+ )
177
+
178
+ headers: Mapping[str, str] = {
179
+ **(dict(response.headers) if response else {}),
180
+ **(options.headers if options and options.headers else {}),
181
+ }
182
+
183
+ body: JsonResponse[T] = JsonResponse(
184
+ success=not is_failure,
185
+ data=options.data
186
+ if isinstance(options, CreateJsonSuccessResponseOptions)
187
+ else None,
188
+ errors=options.errors if is_failure else None,
189
+ )
190
+
191
+ return JSONResponse(
192
+ status_code=status,
193
+ headers=headers,
194
+ content=body.model_dump(exclude_none=True),
195
+ )
@@ -0,0 +1,76 @@
1
+ from typing import Any, Mapping, Optional, TypeVar
2
+
3
+ from fastapi.responses import Response
4
+ from pydantic import BaseModel
5
+
6
+ T = TypeVar("T")
7
+
8
+
9
+ class CreateResponseOptions[T = Any](BaseModel):
10
+ """
11
+ Options of `createResponse` function.
12
+ """
13
+
14
+ status: Optional[int] = None
15
+ """
16
+ Status code of the response.
17
+ """
18
+ headers: Optional[Mapping[str, str]] = None
19
+ """
20
+ Headers of the response.
21
+ """
22
+ body: Optional[T] = None
23
+ """
24
+ Body of the response.
25
+ """
26
+
27
+
28
+ def createResponse(
29
+ response: Response | None = None,
30
+ options: CreateResponseOptions[T] | None = None,
31
+ ) -> Response:
32
+ """
33
+ Create a response.
34
+
35
+ ### Example
36
+
37
+ ```python
38
+ from fastapi import FastAPI
39
+ from fastapi.responses import Response
40
+ from jder_fastapi.responses import createResponse
41
+
42
+ app: FastAPI = FastAPI()
43
+
44
+
45
+ @app.get("/")
46
+ async def route() -> Response:
47
+ return createResponse(
48
+ {
49
+ "headers": {
50
+ "Content-Type": "text/plain",
51
+ },
52
+ "body": "Hello, World!",
53
+ },
54
+ )
55
+ ```
56
+ """
57
+ status: int = 200
58
+ headers: Mapping[str, str] = {}
59
+ body: T | None = None
60
+
61
+ if response:
62
+ headers = dict(response.headers or {})
63
+
64
+ if options:
65
+ if options.status:
66
+ status = options.status
67
+ if options.headers:
68
+ headers = {**headers, **options.headers}
69
+ if options.body:
70
+ body = options.body
71
+
72
+ return Response(
73
+ status_code=status,
74
+ headers=headers,
75
+ content=body,
76
+ )