menzoapi 0.0.2__tar.gz → 1.0.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.
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: menzoapi
3
+ Version: 1.0.0
4
+ Summary: A lightweight FastAPI-inspired framework
5
+ Home-page: https://github.com/menzoapi/menzoapi
6
+ Author: MenzoAPI Contributors
7
+ Classifier: Development Status :: 5 - Production/Stable
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
20
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
21
+ Requires-Dist: black>=23.0.0; extra == "dev"
22
+ Requires-Dist: flake8>=6.0.0; extra == "dev"
23
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
24
+ Dynamic: author
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: provides-extra
30
+ Dynamic: requires-python
31
+ Dynamic: summary
32
+
33
+ # README.md
34
+ # MenzoAPI v1.0.0
35
+
36
+ A lightweight FastAPI-inspired framework for Python 3.10+ with zero external dependencies.
37
+
38
+ ## Features
39
+
40
+ - **Routing**: GET, POST, PUT, DELETE, PATCH
41
+ - **Route Parameters**: `/users/{id}`
42
+ - **Query Parameters**: `/search?q=test&page=1`
43
+ - **Request Object**: headers, body, json, params, query, method, path
44
+ - **Response Object**: status, body, headers
45
+ - **JSON Responses**: Automatic serialization
46
+ - **Middleware System**: Multiple middleware support
47
+ - **Exception Handlers**: Custom exception handling
48
+ - **Built-in Docs**: `/docs` endpoint with auto-listed routes
49
+ - **Async Support**: Full async endpoint support
50
+ - **Thread-Safe Routing**: Safe for concurrent requests
51
+ - **Automatic JSON Parsing**: Request body parsing
52
+ - **Automatic Status Codes**: Smart response status detection
53
+ - **Static File Serving**: Serve static files
54
+ - **CORS Support**: Cross-Origin Resource Sharing
55
+ - **Logging**: Built-in logging
56
+ - **Type Hints**: Complete type annotations everywhere
57
+ - **Zero Dependencies**: Pure Python standard library
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install menzoapi
63
+ ```
64
+ Quick Start
65
+ ```python
66
+ from menzoapi import MenzoAPI, Server
67
+
68
+ app = MenzoAPI()
69
+
70
+ @app.get("/")
71
+ def home(request):
72
+ return {"message": "Hello, MenzoAPI!"}
73
+
74
+ @app.get("/users/{id}")
75
+ def get_user(request):
76
+ user_id = request.params["id"]
77
+ return {"id": user_id, "name": "User " + user_id}
78
+
79
+ @app.post("/users")
80
+ async def create_user(request):
81
+ data = await request.json
82
+ return {"created": True, "data": data}
83
+
84
+ Server(app).run()
85
+ ```
86
+ Advanced Usage
87
+ Middleware
88
+ ```python
89
+ from menzoapi import MenzoAPI
90
+ from menzoapi.middleware import LoggerMiddleware, CORSMiddleware
91
+
92
+ app = MenzoAPI()
93
+ app.use(LoggerMiddleware())
94
+ app.use(CORSMiddleware(origins=["*"]))
95
+ ```
96
+ Exception Handling
97
+ ```python
98
+ from menzoapi.exceptions import NotFoundException
99
+
100
+ @app.exception_handler(NotFoundException)
101
+ def handle_not_found(exc):
102
+ return {"error": str(exc)}, 404
103
+ ```
104
+ Static Files
105
+ ```python
106
+ app.static("/static", "./public")
107
+ ```
108
+ CORS
109
+ ```python
110
+ app.enable_cors(
111
+ origins=["https://example.com"],
112
+ methods=["GET", "POST"],
113
+ headers=["Content-Type"]
114
+ )
115
+ ```
116
+ API Reference
117
+ MenzoAPI
118
+ ```python
119
+ @app.get(path) - GET route
120
+
121
+ @app.post(path) - POST route
122
+
123
+ @app.put(path) - PUT route
124
+
125
+ @app.delete(path) - DELETE route
126
+
127
+ @app.patch(path) - PATCH route
128
+
129
+ app.use(middleware) - Add middleware
130
+
131
+ app.exception_handler(type) - Add exception handler
132
+
133
+ app.enable_cors() - Enable CORS
134
+
135
+ app.static(path, directory) - Serve static files
136
+ Request
137
+ request.method - HTTP method
138
+
139
+ request.path - Request path
140
+
141
+ request.headers - Headers dict
142
+
143
+ request.query - Query parameters
144
+
145
+ request.params - Route parameters
146
+
147
+ await request.body - Raw body
148
+
149
+ await request.json - JSON body
150
+
151
+ Response
152
+ Response(status, body, headers) - Create response
153
+
154
+ response.json(data, status) - JSON response
155
+ ```
156
+ Server
157
+ ```python
158
+ from menzoapi import Server
159
+
160
+ Server(app, host="0.0.0.0", port=8000, workers=4).run()
161
+ host - Bind address (default: 127.0.0.1)
162
+ ```
163
+
164
+ # ENDING PART
165
+ port - Bind port (default: 8000)
166
+
167
+ workers - Number of worker processes (default: 1)
168
+ Documentation
169
+ Visit /docs endpoint in your browser to see auto-generated API documentation.
170
+
171
+ License
172
+ MIT
173
+ ---
174
+ <h1> By Sarthak Kamat </h1>
175
+ ---
@@ -0,0 +1,143 @@
1
+ # README.md
2
+ # MenzoAPI v1.0.0
3
+
4
+ A lightweight FastAPI-inspired framework for Python 3.10+ with zero external dependencies.
5
+
6
+ ## Features
7
+
8
+ - **Routing**: GET, POST, PUT, DELETE, PATCH
9
+ - **Route Parameters**: `/users/{id}`
10
+ - **Query Parameters**: `/search?q=test&page=1`
11
+ - **Request Object**: headers, body, json, params, query, method, path
12
+ - **Response Object**: status, body, headers
13
+ - **JSON Responses**: Automatic serialization
14
+ - **Middleware System**: Multiple middleware support
15
+ - **Exception Handlers**: Custom exception handling
16
+ - **Built-in Docs**: `/docs` endpoint with auto-listed routes
17
+ - **Async Support**: Full async endpoint support
18
+ - **Thread-Safe Routing**: Safe for concurrent requests
19
+ - **Automatic JSON Parsing**: Request body parsing
20
+ - **Automatic Status Codes**: Smart response status detection
21
+ - **Static File Serving**: Serve static files
22
+ - **CORS Support**: Cross-Origin Resource Sharing
23
+ - **Logging**: Built-in logging
24
+ - **Type Hints**: Complete type annotations everywhere
25
+ - **Zero Dependencies**: Pure Python standard library
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ pip install menzoapi
31
+ ```
32
+ Quick Start
33
+ ```python
34
+ from menzoapi import MenzoAPI, Server
35
+
36
+ app = MenzoAPI()
37
+
38
+ @app.get("/")
39
+ def home(request):
40
+ return {"message": "Hello, MenzoAPI!"}
41
+
42
+ @app.get("/users/{id}")
43
+ def get_user(request):
44
+ user_id = request.params["id"]
45
+ return {"id": user_id, "name": "User " + user_id}
46
+
47
+ @app.post("/users")
48
+ async def create_user(request):
49
+ data = await request.json
50
+ return {"created": True, "data": data}
51
+
52
+ Server(app).run()
53
+ ```
54
+ Advanced Usage
55
+ Middleware
56
+ ```python
57
+ from menzoapi import MenzoAPI
58
+ from menzoapi.middleware import LoggerMiddleware, CORSMiddleware
59
+
60
+ app = MenzoAPI()
61
+ app.use(LoggerMiddleware())
62
+ app.use(CORSMiddleware(origins=["*"]))
63
+ ```
64
+ Exception Handling
65
+ ```python
66
+ from menzoapi.exceptions import NotFoundException
67
+
68
+ @app.exception_handler(NotFoundException)
69
+ def handle_not_found(exc):
70
+ return {"error": str(exc)}, 404
71
+ ```
72
+ Static Files
73
+ ```python
74
+ app.static("/static", "./public")
75
+ ```
76
+ CORS
77
+ ```python
78
+ app.enable_cors(
79
+ origins=["https://example.com"],
80
+ methods=["GET", "POST"],
81
+ headers=["Content-Type"]
82
+ )
83
+ ```
84
+ API Reference
85
+ MenzoAPI
86
+ ```python
87
+ @app.get(path) - GET route
88
+
89
+ @app.post(path) - POST route
90
+
91
+ @app.put(path) - PUT route
92
+
93
+ @app.delete(path) - DELETE route
94
+
95
+ @app.patch(path) - PATCH route
96
+
97
+ app.use(middleware) - Add middleware
98
+
99
+ app.exception_handler(type) - Add exception handler
100
+
101
+ app.enable_cors() - Enable CORS
102
+
103
+ app.static(path, directory) - Serve static files
104
+ Request
105
+ request.method - HTTP method
106
+
107
+ request.path - Request path
108
+
109
+ request.headers - Headers dict
110
+
111
+ request.query - Query parameters
112
+
113
+ request.params - Route parameters
114
+
115
+ await request.body - Raw body
116
+
117
+ await request.json - JSON body
118
+
119
+ Response
120
+ Response(status, body, headers) - Create response
121
+
122
+ response.json(data, status) - JSON response
123
+ ```
124
+ Server
125
+ ```python
126
+ from menzoapi import Server
127
+
128
+ Server(app, host="0.0.0.0", port=8000, workers=4).run()
129
+ host - Bind address (default: 127.0.0.1)
130
+ ```
131
+
132
+ # ENDING PART
133
+ port - Bind port (default: 8000)
134
+
135
+ workers - Number of worker processes (default: 1)
136
+ Documentation
137
+ Visit /docs endpoint in your browser to see auto-generated API documentation.
138
+
139
+ License
140
+ MIT
141
+ ---
142
+ <h1> By Sarthak Kamat </h1>
143
+ ---
@@ -0,0 +1,32 @@
1
+ # menzoapi/__init__.py
2
+ from menzoapi.app import MenzoAPI
3
+ from menzoapi.server import Server
4
+ from menzoapi.types import Request, Response
5
+ from menzoapi.middleware import Middleware, LoggerMiddleware, CORSMiddleware
6
+ from menzoapi.exceptions import (
7
+ HTTPException,
8
+ NotFoundException,
9
+ MethodNotAllowedException,
10
+ BadRequestException,
11
+ UnauthorizedException,
12
+ ForbiddenException,
13
+ InternalServerErrorException,
14
+ )
15
+
16
+ __version__ = "1.0.0"
17
+ __all__ = [
18
+ "MenzoAPI",
19
+ "Server",
20
+ "Request",
21
+ "Response",
22
+ "Middleware",
23
+ "LoggerMiddleware",
24
+ "CORSMiddleware",
25
+ "HTTPException",
26
+ "NotFoundException",
27
+ "MethodNotAllowedException",
28
+ "BadRequestException",
29
+ "UnauthorizedException",
30
+ "ForbiddenException",
31
+ "InternalServerErrorException",
32
+ ]
@@ -0,0 +1,290 @@
1
+ # menzoapi/app.py
2
+ import asyncio
3
+ import inspect
4
+ import json
5
+ import logging
6
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
7
+ from urllib.parse import parse_qs
8
+
9
+ from menzoapi.types import Request, Response
10
+ from menzoapi.middleware import Middleware
11
+ from menzoapi.exceptions import HTTPException, NotFoundException, MethodNotAllowedException
12
+ from menzoapi.static import StaticFileHandler
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class MenzoAPI:
18
+ __slots__ = (
19
+ "_routes",
20
+ "_middlewares",
21
+ "_exception_handlers",
22
+ "_static_handlers",
23
+ "_cors_enabled",
24
+ "_cors_origins",
25
+ "_cors_methods",
26
+ "_cors_headers",
27
+ )
28
+
29
+ def __init__(self) -> None:
30
+ self._routes: Dict[str, Dict[str, Callable]] = {}
31
+ self._middlewares: List[Middleware] = []
32
+ self._exception_handlers: Dict[type, Callable] = {}
33
+ self._static_handlers: List[StaticFileHandler] = []
34
+ self._cors_enabled: bool = False
35
+ self._cors_origins: List[str] = []
36
+ self._cors_methods: List[str] = []
37
+ self._cors_headers: List[str] = []
38
+
39
+ def _add_route(self, path: str, method: str, handler: Callable) -> None:
40
+ self._routes.setdefault(path, {})[method] = handler
41
+
42
+ def get(self, path: str) -> Callable:
43
+ def decorator(handler: Callable) -> Callable:
44
+ self._add_route(path, "GET", handler)
45
+ return handler
46
+ return decorator
47
+
48
+ def post(self, path: str) -> Callable:
49
+ def decorator(handler: Callable) -> Callable:
50
+ self._add_route(path, "POST", handler)
51
+ return handler
52
+ return decorator
53
+
54
+ def put(self, path: str) -> Callable:
55
+ def decorator(handler: Callable) -> Callable:
56
+ self._add_route(path, "PUT", handler)
57
+ return handler
58
+ return decorator
59
+
60
+ def delete(self, path: str) -> Callable:
61
+ def decorator(handler: Callable) -> Callable:
62
+ self._add_route(path, "DELETE", handler)
63
+ return handler
64
+ return decorator
65
+
66
+ def patch(self, path: str) -> Callable:
67
+ def decorator(handler: Callable) -> Callable:
68
+ self._add_route(path, "PATCH", handler)
69
+ return handler
70
+ return decorator
71
+
72
+ def use(self, middleware: Middleware) -> None:
73
+ self._middlewares.append(middleware)
74
+
75
+ def exception_handler(self, exception_type: type) -> Callable:
76
+ def decorator(handler: Callable) -> Callable:
77
+ self._exception_handlers[exception_type] = handler
78
+ return handler
79
+ return decorator
80
+
81
+ def enable_cors(
82
+ self,
83
+ origins: Optional[List[str]] = None,
84
+ methods: Optional[List[str]] = None,
85
+ headers: Optional[List[str]] = None,
86
+ ) -> None:
87
+ self._cors_enabled = True
88
+ self._cors_origins = origins or ["*"]
89
+ self._cors_methods = methods or ["GET", "POST", "PUT", "DELETE", "PATCH"]
90
+ self._cors_headers = headers or ["Content-Type", "Authorization"]
91
+
92
+ def static(self, path: str, directory: str) -> None:
93
+ self._static_handlers.append(StaticFileHandler(path, directory))
94
+
95
+ def _parse_path_params(self, route_path: str, actual_path: str) -> Dict[str, str]:
96
+ route_parts = route_path.strip("/").split("/")
97
+ actual_parts = actual_path.strip("/").split("/")
98
+ if len(route_parts) != len(actual_parts):
99
+ return {}
100
+
101
+ params = {}
102
+ for rp, ap in zip(route_parts, actual_parts):
103
+ if rp.startswith("{") and rp.endswith("}"):
104
+ params[rp[1:-1]] = ap
105
+ elif rp != ap:
106
+ return {}
107
+ return params
108
+
109
+ def _match_route(self, path: str, method: str) -> Tuple[Optional[Callable], Dict[str, str]]:
110
+ for route_path, methods in self._routes.items():
111
+ if route_path == path:
112
+ return (methods.get(method), {}) if method in methods else (None, {})
113
+ if "{" in route_path:
114
+ params = self._parse_path_params(route_path, path)
115
+ if params and method in methods:
116
+ return methods[method], params
117
+ return None, {}
118
+
119
+ def _handle_exception(self, exc: Exception) -> Response:
120
+ for exc_type, handler in self._exception_handlers.items():
121
+ if isinstance(exc, exc_type):
122
+ result = handler(exc)
123
+ if inspect.iscoroutine(result):
124
+ loop = asyncio.new_event_loop()
125
+ asyncio.set_event_loop(loop)
126
+ result = loop.run_until_complete(result)
127
+ loop.close()
128
+ return result if isinstance(result, Response) else self._normalize_response(result)
129
+
130
+ if isinstance(exc, NotFoundException):
131
+ return Response(404, json.dumps({"error": "Not Found"}), {"Content-Type": "application/json"})
132
+ if isinstance(exc, MethodNotAllowedException):
133
+ return Response(405, json.dumps({"error": "Method Not Allowed"}), {"Content-Type": "application/json"})
134
+ if isinstance(exc, HTTPException):
135
+ return Response(exc.status_code, json.dumps({"error": exc.detail}), {"Content-Type": "application/json"})
136
+
137
+ logger.error(f"Unhandled exception: {exc}", exc_info=True)
138
+ return Response(500, json.dumps({"error": "Internal Server Error"}), {"Content-Type": "application/json"})
139
+
140
+ def _cors_headers(self) -> Dict[str, str]:
141
+ if not self._cors_enabled:
142
+ return {}
143
+ return {
144
+ "Access-Control-Allow-Origin": "*" if "*" in self._cors_origins else ", ".join(self._cors_origins),
145
+ "Access-Control-Allow-Methods": ", ".join(self._cors_methods),
146
+ "Access-Control-Allow-Headers": ", ".join(self._cors_headers),
147
+ }
148
+
149
+ def _prepare_request(self, scope: Dict[str, Any], receive: Callable) -> Request:
150
+ query_string = scope.get("query_string", b"").decode()
151
+ headers = {}
152
+ for k, v in scope.get("headers", []):
153
+ headers[k.decode().lower()] = v.decode()
154
+
155
+ return Request(
156
+ method=scope.get("method", "GET"),
157
+ path=scope.get("path", "/"),
158
+ headers=headers,
159
+ query=parse_qs(query_string),
160
+ scope=scope,
161
+ receive=receive,
162
+ )
163
+
164
+ async def _apply_middleware(self, request: Request, handler: Callable) -> Response:
165
+ async def process(idx: int) -> Response:
166
+ if idx >= len(self._middlewares):
167
+ return await self._call_handler(handler, request)
168
+ return await self._middlewares[idx].process(request, lambda req: process(idx + 1))
169
+ return await process(0)
170
+
171
+ async def _call_handler(self, handler: Callable, request: Request) -> Response:
172
+ result = await handler(request) if inspect.iscoroutinefunction(handler) else handler(request)
173
+ return result if isinstance(result, Response) else self._normalize_response(result)
174
+
175
+ def _normalize_response(self, result: Any) -> Response:
176
+ if result is None:
177
+ return Response(204, b"")
178
+ if isinstance(result, dict):
179
+ return Response(200, json.dumps(result), {"Content-Type": "application/json"})
180
+ if isinstance(result, (str, bytes)):
181
+ content_type = "text/plain" if isinstance(result, str) else "application/octet-stream"
182
+ return Response(200, result.encode() if isinstance(result, str) else result, {"Content-Type": content_type})
183
+ return Response(200, json.dumps({"result": str(result)}), {"Content-Type": "application/json"})
184
+
185
+ def _docs_routes(self) -> List[Dict[str, Any]]:
186
+ return [
187
+ {"path": p, "method": m, "handler": h.__name__}
188
+ for p, methods in self._routes.items()
189
+ for m, h in methods.items()
190
+ ]
191
+
192
+ async def _serve_static(self, path: str) -> Optional[Response]:
193
+ for handler in self._static_handlers:
194
+ if path.startswith(handler.path_prefix):
195
+ return await handler.serve(path)
196
+ return None
197
+
198
+ async def _handle_request(self, scope: Dict[str, Any], receive: Callable) -> Response:
199
+ path = scope.get("path", "/")
200
+ method = scope.get("method", "GET")
201
+
202
+ if method == "OPTIONS" and self._cors_enabled:
203
+ return Response(200, b"", self._cors_headers())
204
+
205
+ if path == "/docs":
206
+ return Response(200, self._docs_html().encode(), {"Content-Type": "text/html"})
207
+
208
+ static_resp = await self._serve_static(path)
209
+ if static_resp:
210
+ return static_resp
211
+
212
+ request = self._prepare_request(scope, receive)
213
+ handler, path_params = self._match_route(path, method)
214
+
215
+ if not handler:
216
+ if path in self._routes:
217
+ raise MethodNotAllowedException(f"Method {method} not allowed for {path}")
218
+ raise NotFoundException(f"Route {path} not found")
219
+
220
+ request.params = path_params
221
+
222
+ try:
223
+ response = await self._apply_middleware(request, handler)
224
+ if self._cors_enabled:
225
+ response.headers.update(self._cors_headers())
226
+ return response
227
+ except Exception as e:
228
+ return self._handle_exception(e)
229
+
230
+ def _docs_html(self) -> str:
231
+ routes = self._docs_routes()
232
+ rows = "".join(
233
+ f"""
234
+ <tr>
235
+ <td><span class="method method-{r['method'].lower()}">{r['method']}</span></td>
236
+ <td><code>{r['path']}</code></td>
237
+ <td>{r['handler']}</td>
238
+ </tr>"""
239
+ for r in routes
240
+ )
241
+
242
+ return f"""
243
+ <!DOCTYPE html>
244
+ <html>
245
+ <head>
246
+ <meta charset="utf-8">
247
+ <title>MenzoAPI Documentation</title>
248
+ <style>
249
+ body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1200px; margin: 40px auto; padding: 0 20px; }}
250
+ h1 {{ color: #333; border-bottom: 2px solid #eee; padding-bottom: 10px; }}
251
+ table {{ width: 100%; border-collapse: collapse; margin-top: 20px; }}
252
+ th {{ text-align: left; padding: 12px; background: #f5f5f5; font-weight: 600; }}
253
+ td {{ padding: 12px; border-bottom: 1px solid #eee; }}
254
+ .method {{ display: inline-block; padding: 4px 12px; border-radius: 4px; font-weight: 600; font-size: 14px; }}
255
+ .method-get {{ background: #61affe; color: white; }}
256
+ .method-post {{ background: #49cc90; color: white; }}
257
+ .method-put {{ background: #fca130; color: white; }}
258
+ .method-delete {{ background: #f93e3e; color: white; }}
259
+ .method-patch {{ background: #50e3c2; color: white; }}
260
+ code {{ background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-size: 14px; }}
261
+ .info {{ margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 4px; }}
262
+ </style>
263
+ </head>
264
+ <body>
265
+ <h1>MenzoAPI v1.0.0 Documentation</h1>
266
+ <div class="info"><strong>Total Routes:</strong> {len(routes)}</div>
267
+ <table>
268
+ <thead><tr><th>Method</th><th>Path</th><th>Handler</th></tr></thead>
269
+ <tbody>{rows}</tbody>
270
+ </table>
271
+ </body>
272
+ </html>
273
+ """
274
+
275
+ async def __call__(self, scope: Dict[str, Any], receive: Callable, send: Callable) -> None:
276
+ if scope["type"] != "http":
277
+ return
278
+
279
+ try:
280
+ response = await self._handle_request(scope, receive)
281
+ headers = [(k.encode(), v.encode()) for k, v in response.headers.items()]
282
+
283
+ await send({"type": "http.response.start", "status": response.status, "headers": headers})
284
+ body = response.body if isinstance(response.body, bytes) else response.body.encode()
285
+ await send({"type": "http.response.body", "body": body})
286
+
287
+ except Exception as e:
288
+ logger.error(f"Error processing request: {e}", exc_info=True)
289
+ await send({"type": "http.response.start", "status": 500, "headers": [(b"content-type", b"application/json")]})
290
+ await send({"type": "http.response.body", "body": b'{"error": "Internal Server Error"}'})
@@ -0,0 +1,38 @@
1
+ # menzoapi/exceptions.py
2
+ class HTTPException(Exception):
3
+ __slots__ = ("status_code", "detail")
4
+
5
+ def __init__(self, status_code: int, detail: str = "") -> None:
6
+ self.status_code = status_code
7
+ self.detail = detail
8
+ super().__init__(detail or f"HTTP {status_code}")
9
+
10
+
11
+ class NotFoundException(HTTPException):
12
+ def __init__(self, detail: str = "Not Found") -> None:
13
+ super().__init__(404, detail)
14
+
15
+
16
+ class MethodNotAllowedException(HTTPException):
17
+ def __init__(self, detail: str = "Method Not Allowed") -> None:
18
+ super().__init__(405, detail)
19
+
20
+
21
+ class BadRequestException(HTTPException):
22
+ def __init__(self, detail: str = "Bad Request") -> None:
23
+ super().__init__(400, detail)
24
+
25
+
26
+ class UnauthorizedException(HTTPException):
27
+ def __init__(self, detail: str = "Unauthorized") -> None:
28
+ super().__init__(401, detail)
29
+
30
+
31
+ class ForbiddenException(HTTPException):
32
+ def __init__(self, detail: str = "Forbidden") -> None:
33
+ super().__init__(403, detail)
34
+
35
+
36
+ class InternalServerErrorException(HTTPException):
37
+ def __init__(self, detail: str = "Internal Server Error") -> None:
38
+ super().__init__(500, detail)