bustapi 0.1.0__cp311-cp311-win_amd64.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.

Potentially problematic release.


This version of bustapi might be problematic. Click here for more details.

bustapi/exceptions.py ADDED
@@ -0,0 +1,438 @@
1
+ """
2
+ Exception classes for BustAPI - Flask-compatible exceptions
3
+ """
4
+
5
+ from http import HTTPStatus
6
+ from typing import Optional
7
+
8
+
9
+ class BustAPIException(Exception):
10
+ """Base exception class for BustAPI."""
11
+
12
+ pass
13
+
14
+
15
+ class HTTPException(BustAPIException):
16
+ """
17
+ HTTP exception for error responses (Flask-compatible).
18
+
19
+ This exception is raised when an HTTP error response should be returned.
20
+ """
21
+
22
+ def __init__(self, code: int, description: Optional[str] = None, response=None):
23
+ """
24
+ Initialize HTTP exception.
25
+
26
+ Args:
27
+ code: HTTP status code
28
+ description: Error description
29
+ response: Response object
30
+ """
31
+ self.code = code
32
+ self.name = self._get_name(code)
33
+ self.description = description or self._get_default_description(code)
34
+ self.response = response
35
+
36
+ super().__init__(f"{self.code} {self.name}: {self.description}")
37
+
38
+ def _get_name(self, code: int) -> str:
39
+ """Get name for HTTP status code."""
40
+ try:
41
+ return HTTPStatus(code).name.replace("_", " ").title()
42
+ except ValueError:
43
+ return f"HTTP {code}"
44
+
45
+ def _get_default_description(self, code: int) -> str:
46
+ """Get default description for HTTP status code."""
47
+ try:
48
+ return HTTPStatus(code).phrase
49
+ except ValueError:
50
+ return f"HTTP Error {code}"
51
+
52
+ def get_response(self):
53
+ """
54
+ Get response object for this exception.
55
+
56
+ Returns:
57
+ Response object
58
+ """
59
+ if self.response is not None:
60
+ return self.response
61
+
62
+ from .response import Response
63
+
64
+ return Response(self.description, status=self.code)
65
+
66
+ def get_body(self) -> str:
67
+ """Get response body."""
68
+ return self.description
69
+
70
+ def get_headers(self) -> dict:
71
+ """Get response headers."""
72
+ return {}
73
+
74
+
75
+ # Common HTTP exceptions (Flask-compatible)
76
+
77
+
78
+ class BadRequest(HTTPException):
79
+ """400 Bad Request exception."""
80
+
81
+ def __init__(self, description: Optional[str] = None, response=None):
82
+ super().__init__(400, description, response)
83
+
84
+
85
+ class Unauthorized(HTTPException):
86
+ """401 Unauthorized exception."""
87
+
88
+ def __init__(self, description: Optional[str] = None, response=None):
89
+ super().__init__(401, description, response)
90
+
91
+
92
+ class Forbidden(HTTPException):
93
+ """403 Forbidden exception."""
94
+
95
+ def __init__(self, description: Optional[str] = None, response=None):
96
+ super().__init__(403, description, response)
97
+
98
+
99
+ class NotFound(HTTPException):
100
+ """404 Not Found exception."""
101
+
102
+ def __init__(self, description: Optional[str] = None, response=None):
103
+ super().__init__(404, description, response)
104
+
105
+
106
+ class MethodNotAllowed(HTTPException):
107
+ """405 Method Not Allowed exception."""
108
+
109
+ def __init__(
110
+ self, description: Optional[str] = None, response=None, valid_methods=None
111
+ ):
112
+ super().__init__(405, description, response)
113
+ self.valid_methods = valid_methods or []
114
+
115
+ def get_headers(self) -> dict:
116
+ """Get response headers including Allow header."""
117
+ headers = super().get_headers()
118
+ if self.valid_methods:
119
+ headers["Allow"] = ", ".join(self.valid_methods)
120
+ return headers
121
+
122
+
123
+ class NotAcceptable(HTTPException):
124
+ """406 Not Acceptable exception."""
125
+
126
+ def __init__(self, description: Optional[str] = None, response=None):
127
+ super().__init__(406, description, response)
128
+
129
+
130
+ class RequestTimeout(HTTPException):
131
+ """408 Request Timeout exception."""
132
+
133
+ def __init__(self, description: Optional[str] = None, response=None):
134
+ super().__init__(408, description, response)
135
+
136
+
137
+ class Conflict(HTTPException):
138
+ """409 Conflict exception."""
139
+
140
+ def __init__(self, description: Optional[str] = None, response=None):
141
+ super().__init__(409, description, response)
142
+
143
+
144
+ class Gone(HTTPException):
145
+ """410 Gone exception."""
146
+
147
+ def __init__(self, description: Optional[str] = None, response=None):
148
+ super().__init__(410, description, response)
149
+
150
+
151
+ class LengthRequired(HTTPException):
152
+ """411 Length Required exception."""
153
+
154
+ def __init__(self, description: Optional[str] = None, response=None):
155
+ super().__init__(411, description, response)
156
+
157
+
158
+ class PreconditionFailed(HTTPException):
159
+ """412 Precondition Failed exception."""
160
+
161
+ def __init__(self, description: Optional[str] = None, response=None):
162
+ super().__init__(412, description, response)
163
+
164
+
165
+ class RequestEntityTooLarge(HTTPException):
166
+ """413 Request Entity Too Large exception."""
167
+
168
+ def __init__(self, description: Optional[str] = None, response=None):
169
+ super().__init__(413, description, response)
170
+
171
+
172
+ class RequestURITooLarge(HTTPException):
173
+ """414 Request-URI Too Large exception."""
174
+
175
+ def __init__(self, description: Optional[str] = None, response=None):
176
+ super().__init__(414, description, response)
177
+
178
+
179
+ class UnsupportedMediaType(HTTPException):
180
+ """415 Unsupported Media Type exception."""
181
+
182
+ def __init__(self, description: Optional[str] = None, response=None):
183
+ super().__init__(415, description, response)
184
+
185
+
186
+ class RequestedRangeNotSatisfiable(HTTPException):
187
+ """416 Requested Range Not Satisfiable exception."""
188
+
189
+ def __init__(self, description: Optional[str] = None, response=None):
190
+ super().__init__(416, description, response)
191
+
192
+
193
+ class ExpectationFailed(HTTPException):
194
+ """417 Expectation Failed exception."""
195
+
196
+ def __init__(self, description: Optional[str] = None, response=None):
197
+ super().__init__(417, description, response)
198
+
199
+
200
+ class ImATeapot(HTTPException):
201
+ """418 I'm a teapot exception (RFC 2324)."""
202
+
203
+ def __init__(self, description: Optional[str] = None, response=None):
204
+ super().__init__(418, description, response)
205
+
206
+
207
+ class UnprocessableEntity(HTTPException):
208
+ """422 Unprocessable Entity exception."""
209
+
210
+ def __init__(self, description: Optional[str] = None, response=None):
211
+ super().__init__(422, description, response)
212
+
213
+
214
+ class Locked(HTTPException):
215
+ """423 Locked exception."""
216
+
217
+ def __init__(self, description: Optional[str] = None, response=None):
218
+ super().__init__(423, description, response)
219
+
220
+
221
+ class FailedDependency(HTTPException):
222
+ """424 Failed Dependency exception."""
223
+
224
+ def __init__(self, description: Optional[str] = None, response=None):
225
+ super().__init__(424, description, response)
226
+
227
+
228
+ class PreconditionRequired(HTTPException):
229
+ """428 Precondition Required exception."""
230
+
231
+ def __init__(self, description: Optional[str] = None, response=None):
232
+ super().__init__(428, description, response)
233
+
234
+
235
+ class TooManyRequests(HTTPException):
236
+ """429 Too Many Requests exception."""
237
+
238
+ def __init__(
239
+ self, description: Optional[str] = None, response=None, retry_after=None
240
+ ):
241
+ super().__init__(429, description, response)
242
+ self.retry_after = retry_after
243
+
244
+ def get_headers(self) -> dict:
245
+ """Get response headers including Retry-After header."""
246
+ headers = super().get_headers()
247
+ if self.retry_after:
248
+ headers["Retry-After"] = str(self.retry_after)
249
+ return headers
250
+
251
+
252
+ class RequestHeaderFieldsTooLarge(HTTPException):
253
+ """431 Request Header Fields Too Large exception."""
254
+
255
+ def __init__(self, description: Optional[str] = None, response=None):
256
+ super().__init__(431, description, response)
257
+
258
+
259
+ class UnavailableForLegalReasons(HTTPException):
260
+ """451 Unavailable For Legal Reasons exception."""
261
+
262
+ def __init__(self, description: Optional[str] = None, response=None):
263
+ super().__init__(451, description, response)
264
+
265
+
266
+ # 5xx Server Error exceptions
267
+
268
+
269
+ class InternalServerError(HTTPException):
270
+ """500 Internal Server Error exception."""
271
+
272
+ def __init__(
273
+ self, description: Optional[str] = None, response=None, original_exception=None
274
+ ):
275
+ super().__init__(500, description, response)
276
+ self.original_exception = original_exception
277
+
278
+
279
+ class NotImplemented(HTTPException):
280
+ """501 Not Implemented exception."""
281
+
282
+ def __init__(self, description: Optional[str] = None, response=None):
283
+ super().__init__(501, description, response)
284
+
285
+
286
+ class BadGateway(HTTPException):
287
+ """502 Bad Gateway exception."""
288
+
289
+ def __init__(self, description: Optional[str] = None, response=None):
290
+ super().__init__(502, description, response)
291
+
292
+
293
+ class ServiceUnavailable(HTTPException):
294
+ """503 Service Unavailable exception."""
295
+
296
+ def __init__(
297
+ self, description: Optional[str] = None, response=None, retry_after=None
298
+ ):
299
+ super().__init__(503, description, response)
300
+ self.retry_after = retry_after
301
+
302
+ def get_headers(self) -> dict:
303
+ """Get response headers including Retry-After header."""
304
+ headers = super().get_headers()
305
+ if self.retry_after:
306
+ headers["Retry-After"] = str(self.retry_after)
307
+ return headers
308
+
309
+
310
+ class GatewayTimeout(HTTPException):
311
+ """504 Gateway Timeout exception."""
312
+
313
+ def __init__(self, description: Optional[str] = None, response=None):
314
+ super().__init__(504, description, response)
315
+
316
+
317
+ class HTTPVersionNotSupported(HTTPException):
318
+ """505 HTTP Version Not Supported exception."""
319
+
320
+ def __init__(self, description: Optional[str] = None, response=None):
321
+ super().__init__(505, description, response)
322
+
323
+
324
+ # Application-specific exceptions
325
+
326
+
327
+ class ConfigurationError(BustAPIException):
328
+ """Configuration error exception."""
329
+
330
+ pass
331
+
332
+
333
+ class TemplateNotFound(BustAPIException):
334
+ """Template not found exception."""
335
+
336
+ def __init__(self, template_name: str):
337
+ self.template_name = template_name
338
+ super().__init__(f"Template '{template_name}' not found")
339
+
340
+
341
+ class BlueprintSetupError(BustAPIException):
342
+ """Blueprint setup error exception."""
343
+
344
+ pass
345
+
346
+
347
+ class SecurityError(BustAPIException):
348
+ """Security-related error exception."""
349
+
350
+ pass
351
+
352
+
353
+ # Utility functions
354
+
355
+
356
+ def abort(code: int, description: Optional[str] = None, **kwargs):
357
+ """
358
+ Abort request with HTTP error code.
359
+
360
+ Args:
361
+ code: HTTP status code
362
+ description: Error description
363
+ **kwargs: Additional arguments
364
+
365
+ Raises:
366
+ HTTPException: HTTP exception with specified code
367
+ """
368
+ # Map common status codes to specific exception classes
369
+ exception_map = {
370
+ 400: BadRequest,
371
+ 401: Unauthorized,
372
+ 403: Forbidden,
373
+ 404: NotFound,
374
+ 405: MethodNotAllowed,
375
+ 406: NotAcceptable,
376
+ 408: RequestTimeout,
377
+ 409: Conflict,
378
+ 410: Gone,
379
+ 411: LengthRequired,
380
+ 412: PreconditionFailed,
381
+ 413: RequestEntityTooLarge,
382
+ 414: RequestURITooLarge,
383
+ 415: UnsupportedMediaType,
384
+ 416: RequestedRangeNotSatisfiable,
385
+ 417: ExpectationFailed,
386
+ 418: ImATeapot,
387
+ 422: UnprocessableEntity,
388
+ 423: Locked,
389
+ 424: FailedDependency,
390
+ 428: PreconditionRequired,
391
+ 429: TooManyRequests,
392
+ 431: RequestHeaderFieldsTooLarge,
393
+ 451: UnavailableForLegalReasons,
394
+ 500: InternalServerError,
395
+ 501: NotImplemented,
396
+ 502: BadGateway,
397
+ 503: ServiceUnavailable,
398
+ 504: GatewayTimeout,
399
+ 505: HTTPVersionNotSupported,
400
+ }
401
+
402
+ exception_class = exception_map.get(code, HTTPException)
403
+ if exception_class == HTTPException:
404
+ raise exception_class(code, description, **kwargs)
405
+ else:
406
+ # Filter kwargs for specific exception classes
407
+ if code in (405, 429, 503) and "valid_methods" in kwargs:
408
+ raise exception_class(description, **kwargs)
409
+ else:
410
+ raise exception_class(description)
411
+
412
+
413
+ # Exception handler registry
414
+ _exception_handlers = {}
415
+
416
+
417
+ def register_error_handler(code_or_exception, handler):
418
+ """
419
+ Register error handler for HTTP status code or exception.
420
+
421
+ Args:
422
+ code_or_exception: HTTP status code or exception class
423
+ handler: Handler function
424
+ """
425
+ _exception_handlers[code_or_exception] = handler
426
+
427
+
428
+ def get_error_handler(code_or_exception):
429
+ """
430
+ Get error handler for HTTP status code or exception.
431
+
432
+ Args:
433
+ code_or_exception: HTTP status code or exception class
434
+
435
+ Returns:
436
+ Handler function or None
437
+ """
438
+ return _exception_handlers.get(code_or_exception)
@@ -0,0 +1,62 @@
1
+ """
2
+ Flask compatibility layer for BustAPI
3
+
4
+ This module provides Flask class as an alias for BustAPI to enable drop-in replacement.
5
+ """
6
+
7
+ from .app import BustAPI
8
+ from .blueprints import Blueprint
9
+ from .exceptions import HTTPException
10
+ from .helpers import (
11
+ abort,
12
+ flash,
13
+ get_flashed_messages,
14
+ redirect,
15
+ render_template,
16
+ render_template_string,
17
+ send_file,
18
+ send_from_directory,
19
+ url_for,
20
+ )
21
+ from .request import request
22
+ from .response import Response, jsonify, make_response
23
+
24
+
25
+ class Flask(BustAPI):
26
+ """
27
+ Flask compatibility class - direct alias for BustAPI.
28
+
29
+ This allows BustAPI to be used as a drop-in replacement for Flask:
30
+
31
+ Instead of:
32
+ from flask import Flask
33
+ app = Flask(__name__)
34
+
35
+ Use:
36
+ from bustapi import Flask # or: from bustapi.flask_compat import Flask
37
+ app = Flask(__name__)
38
+
39
+ All Flask functionality is available through BustAPI's implementation.
40
+ """
41
+
42
+ pass
43
+
44
+
45
+ __all__ = [
46
+ "Flask",
47
+ "request",
48
+ "jsonify",
49
+ "make_response",
50
+ "Response",
51
+ "abort",
52
+ "redirect",
53
+ "url_for",
54
+ "render_template",
55
+ "render_template_string",
56
+ "flash",
57
+ "get_flashed_messages",
58
+ "send_file",
59
+ "send_from_directory",
60
+ "HTTPException",
61
+ "Blueprint",
62
+ ]