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/__init__.py +96 -0
- bustapi/app.py +552 -0
- bustapi/blueprints.py +500 -0
- bustapi/bustapi_core.cp311-win_amd64.pyd +0 -0
- bustapi/exceptions.py +438 -0
- bustapi/flask_compat.py +62 -0
- bustapi/helpers.py +370 -0
- bustapi/py.typed +0 -0
- bustapi/request.py +378 -0
- bustapi/response.py +406 -0
- bustapi/testing.py +375 -0
- bustapi-0.1.0.dist-info/METADATA +233 -0
- bustapi-0.1.0.dist-info/RECORD +16 -0
- bustapi-0.1.0.dist-info/WHEEL +4 -0
- bustapi-0.1.0.dist-info/entry_points.txt +2 -0
- bustapi-0.1.0.dist-info/licenses/LICENSE +21 -0
bustapi/blueprints.py
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Blueprint support for BustAPI - Flask-compatible blueprints
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Blueprint:
|
|
9
|
+
"""
|
|
10
|
+
Flask-compatible blueprint for organizing routes.
|
|
11
|
+
|
|
12
|
+
Blueprints allow you to organize related routes and other functionality
|
|
13
|
+
into reusable components that can be registered with an application.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
bp = Blueprint('api', __name__, url_prefix='/api')
|
|
17
|
+
|
|
18
|
+
@bp.route('/users')
|
|
19
|
+
def get_users():
|
|
20
|
+
return {'users': []}
|
|
21
|
+
|
|
22
|
+
app.register_blueprint(bp)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
name: str,
|
|
28
|
+
import_name: str,
|
|
29
|
+
static_folder: Optional[str] = None,
|
|
30
|
+
static_url_path: Optional[str] = None,
|
|
31
|
+
template_folder: Optional[str] = None,
|
|
32
|
+
url_prefix: Optional[str] = None,
|
|
33
|
+
subdomain: Optional[str] = None,
|
|
34
|
+
url_defaults: Optional[Dict[str, Any]] = None,
|
|
35
|
+
root_path: Optional[str] = None,
|
|
36
|
+
cli_group: Optional[str] = None,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
Initialize blueprint.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
name: Blueprint name
|
|
43
|
+
import_name: Import name (usually __name__)
|
|
44
|
+
static_folder: Static files folder
|
|
45
|
+
static_url_path: Static files URL path
|
|
46
|
+
template_folder: Template folder
|
|
47
|
+
url_prefix: URL prefix for all routes
|
|
48
|
+
subdomain: Subdomain for blueprint
|
|
49
|
+
url_defaults: Default values for URL parameters
|
|
50
|
+
root_path: Root path
|
|
51
|
+
cli_group: CLI group name
|
|
52
|
+
"""
|
|
53
|
+
self.name = name
|
|
54
|
+
self.import_name = import_name
|
|
55
|
+
self.static_folder = static_folder
|
|
56
|
+
self.static_url_path = static_url_path
|
|
57
|
+
self.template_folder = template_folder
|
|
58
|
+
self.url_prefix = url_prefix
|
|
59
|
+
self.subdomain = subdomain
|
|
60
|
+
self.url_defaults = url_defaults or {}
|
|
61
|
+
self.root_path = root_path
|
|
62
|
+
self.cli_group = cli_group
|
|
63
|
+
|
|
64
|
+
# Deferred functions to be registered when blueprint is registered with app
|
|
65
|
+
self.deferred_functions: List[Tuple[str, str, Callable, List[str]]] = []
|
|
66
|
+
|
|
67
|
+
# Error handlers
|
|
68
|
+
self.error_handler_spec: Dict[Union[int, type], Callable] = {}
|
|
69
|
+
|
|
70
|
+
# Before/after request handlers
|
|
71
|
+
self.before_request_funcs: List[Callable] = []
|
|
72
|
+
self.after_request_funcs: List[Callable] = []
|
|
73
|
+
self.teardown_request_funcs: List[Callable] = []
|
|
74
|
+
|
|
75
|
+
# Before/after app request handlers (for all requests)
|
|
76
|
+
self.before_app_request_funcs: List[Callable] = []
|
|
77
|
+
self.after_app_request_funcs: List[Callable] = []
|
|
78
|
+
self.teardown_app_request_funcs: List[Callable] = []
|
|
79
|
+
|
|
80
|
+
# Template context processors
|
|
81
|
+
self.app_context_processor_funcs: List[Callable] = []
|
|
82
|
+
self.context_processor_funcs: List[Callable] = []
|
|
83
|
+
|
|
84
|
+
def route(self, rule: str, **options) -> Callable:
|
|
85
|
+
"""
|
|
86
|
+
Blueprint route decorator (Flask-compatible).
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
rule: URL rule
|
|
90
|
+
**options: Route options including methods, defaults, etc.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Decorator function
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def decorator(f: Callable) -> Callable:
|
|
97
|
+
endpoint = options.pop("endpoint", f.__name__)
|
|
98
|
+
methods = options.pop("methods", ["GET"])
|
|
99
|
+
|
|
100
|
+
# Store route info for later registration
|
|
101
|
+
self.deferred_functions.append((rule, endpoint, f, methods))
|
|
102
|
+
return f
|
|
103
|
+
|
|
104
|
+
return decorator
|
|
105
|
+
|
|
106
|
+
def get(self, rule: str, **options) -> Callable:
|
|
107
|
+
"""Convenience decorator for GET routes."""
|
|
108
|
+
return self.route(rule, methods=["GET"], **options)
|
|
109
|
+
|
|
110
|
+
def post(self, rule: str, **options) -> Callable:
|
|
111
|
+
"""Convenience decorator for POST routes."""
|
|
112
|
+
return self.route(rule, methods=["POST"], **options)
|
|
113
|
+
|
|
114
|
+
def put(self, rule: str, **options) -> Callable:
|
|
115
|
+
"""Convenience decorator for PUT routes."""
|
|
116
|
+
return self.route(rule, methods=["PUT"], **options)
|
|
117
|
+
|
|
118
|
+
def delete(self, rule: str, **options) -> Callable:
|
|
119
|
+
"""Convenience decorator for DELETE routes."""
|
|
120
|
+
return self.route(rule, methods=["DELETE"], **options)
|
|
121
|
+
|
|
122
|
+
def patch(self, rule: str, **options) -> Callable:
|
|
123
|
+
"""Convenience decorator for PATCH routes."""
|
|
124
|
+
return self.route(rule, methods=["PATCH"], **options)
|
|
125
|
+
|
|
126
|
+
def head(self, rule: str, **options) -> Callable:
|
|
127
|
+
"""Convenience decorator for HEAD routes."""
|
|
128
|
+
return self.route(rule, methods=["HEAD"], **options)
|
|
129
|
+
|
|
130
|
+
def options(self, rule: str, **options) -> Callable:
|
|
131
|
+
"""Convenience decorator for OPTIONS routes."""
|
|
132
|
+
return self.route(rule, methods=["OPTIONS"], **options)
|
|
133
|
+
|
|
134
|
+
def before_request(self, f: Callable) -> Callable:
|
|
135
|
+
"""
|
|
136
|
+
Register function to run before each request to this blueprint.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
f: Function to run before request
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
The original function
|
|
143
|
+
"""
|
|
144
|
+
self.before_request_funcs.append(f)
|
|
145
|
+
return f
|
|
146
|
+
|
|
147
|
+
def after_request(self, f: Callable) -> Callable:
|
|
148
|
+
"""
|
|
149
|
+
Register function to run after each request to this blueprint.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
f: Function to run after request
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The original function
|
|
156
|
+
"""
|
|
157
|
+
self.after_request_funcs.append(f)
|
|
158
|
+
return f
|
|
159
|
+
|
|
160
|
+
def teardown_request(self, f: Callable) -> Callable:
|
|
161
|
+
"""
|
|
162
|
+
Register function to run after each request to this blueprint,
|
|
163
|
+
even if an exception occurred.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
f: Function to run on teardown
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
The original function
|
|
170
|
+
"""
|
|
171
|
+
self.teardown_request_funcs.append(f)
|
|
172
|
+
return f
|
|
173
|
+
|
|
174
|
+
def before_app_request(self, f: Callable) -> Callable:
|
|
175
|
+
"""
|
|
176
|
+
Register function to run before every request to the application.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
f: Function to run before request
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
The original function
|
|
183
|
+
"""
|
|
184
|
+
self.before_app_request_funcs.append(f)
|
|
185
|
+
return f
|
|
186
|
+
|
|
187
|
+
def after_app_request(self, f: Callable) -> Callable:
|
|
188
|
+
"""
|
|
189
|
+
Register function to run after every request to the application.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
f: Function to run after request
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
The original function
|
|
196
|
+
"""
|
|
197
|
+
self.after_app_request_funcs.append(f)
|
|
198
|
+
return f
|
|
199
|
+
|
|
200
|
+
def teardown_app_request(self, f: Callable) -> Callable:
|
|
201
|
+
"""
|
|
202
|
+
Register function to run after every request to the application,
|
|
203
|
+
even if an exception occurred.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
f: Function to run on teardown
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
The original function
|
|
210
|
+
"""
|
|
211
|
+
self.teardown_app_request_funcs.append(f)
|
|
212
|
+
return f
|
|
213
|
+
|
|
214
|
+
def errorhandler(self, code_or_exception: Union[int, type]) -> Callable:
|
|
215
|
+
"""
|
|
216
|
+
Register error handler for HTTP status codes or exceptions.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
code_or_exception: HTTP status code or exception class
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Decorator function
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def decorator(f: Callable) -> Callable:
|
|
226
|
+
self.error_handler_spec[code_or_exception] = f
|
|
227
|
+
return f
|
|
228
|
+
|
|
229
|
+
return decorator
|
|
230
|
+
|
|
231
|
+
def app_errorhandler(self, code_or_exception: Union[int, type]) -> Callable:
|
|
232
|
+
"""
|
|
233
|
+
Register application-wide error handler.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
code_or_exception: HTTP status code or exception class
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Decorator function
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
def decorator(f: Callable) -> Callable:
|
|
243
|
+
# This will be registered with the app when blueprint is registered
|
|
244
|
+
self.error_handler_spec[f"app_{code_or_exception}"] = f
|
|
245
|
+
return f
|
|
246
|
+
|
|
247
|
+
return decorator
|
|
248
|
+
|
|
249
|
+
def context_processor(self, f: Callable) -> Callable:
|
|
250
|
+
"""
|
|
251
|
+
Register template context processor for this blueprint.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
f: Context processor function
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
The original function
|
|
258
|
+
"""
|
|
259
|
+
self.context_processor_funcs.append(f)
|
|
260
|
+
return f
|
|
261
|
+
|
|
262
|
+
def app_context_processor(self, f: Callable) -> Callable:
|
|
263
|
+
"""
|
|
264
|
+
Register application-wide template context processor.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
f: Context processor function
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
The original function
|
|
271
|
+
"""
|
|
272
|
+
self.app_context_processor_funcs.append(f)
|
|
273
|
+
return f
|
|
274
|
+
|
|
275
|
+
def url_value_preprocessor(self, f: Callable) -> Callable:
|
|
276
|
+
"""
|
|
277
|
+
Register URL value preprocessor for this blueprint.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
f: Preprocessor function
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
The original function
|
|
284
|
+
"""
|
|
285
|
+
# TODO: Implement URL value preprocessing
|
|
286
|
+
return f
|
|
287
|
+
|
|
288
|
+
def url_defaults(self, f: Callable) -> Callable:
|
|
289
|
+
"""
|
|
290
|
+
Register URL defaults function for this blueprint.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
f: URL defaults function
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
The original function
|
|
297
|
+
"""
|
|
298
|
+
# TODO: Implement URL defaults
|
|
299
|
+
return f
|
|
300
|
+
|
|
301
|
+
def app_url_value_preprocessor(self, f: Callable) -> Callable:
|
|
302
|
+
"""
|
|
303
|
+
Register application-wide URL value preprocessor.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
f: Preprocessor function
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
The original function
|
|
310
|
+
"""
|
|
311
|
+
# TODO: Implement app URL value preprocessing
|
|
312
|
+
return f
|
|
313
|
+
|
|
314
|
+
def app_url_defaults(self, f: Callable) -> Callable:
|
|
315
|
+
"""
|
|
316
|
+
Register application-wide URL defaults function.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
f: URL defaults function
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
The original function
|
|
323
|
+
"""
|
|
324
|
+
# TODO: Implement app URL defaults
|
|
325
|
+
return f
|
|
326
|
+
|
|
327
|
+
def record(self, func: Callable) -> None:
|
|
328
|
+
"""
|
|
329
|
+
Record a function to be called when the blueprint is registered.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
func: Function to record
|
|
333
|
+
"""
|
|
334
|
+
# TODO: Implement blueprint recording system
|
|
335
|
+
pass
|
|
336
|
+
|
|
337
|
+
def record_once(self, func: Callable) -> None:
|
|
338
|
+
"""
|
|
339
|
+
Record a function to be called once when the blueprint is registered.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
func: Function to record
|
|
343
|
+
"""
|
|
344
|
+
# TODO: Implement blueprint recording system
|
|
345
|
+
pass
|
|
346
|
+
|
|
347
|
+
def make_setup_state(self, app, options, first_registration: bool = False):
|
|
348
|
+
"""
|
|
349
|
+
Create setup state for blueprint registration.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
app: Application instance
|
|
353
|
+
options: Registration options
|
|
354
|
+
first_registration: Whether this is the first registration
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
Setup state object
|
|
358
|
+
"""
|
|
359
|
+
return BlueprintSetupState(self, app, options, first_registration)
|
|
360
|
+
|
|
361
|
+
def register(self, app, options, first_registration: bool = False) -> None:
|
|
362
|
+
"""
|
|
363
|
+
Register blueprint with application.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
app: Application instance
|
|
367
|
+
options: Registration options
|
|
368
|
+
first_registration: Whether this is the first registration
|
|
369
|
+
"""
|
|
370
|
+
state = self.make_setup_state(app, options, first_registration)
|
|
371
|
+
|
|
372
|
+
if self.has_static_folder:
|
|
373
|
+
# Register static folder
|
|
374
|
+
state.add_url_rule(
|
|
375
|
+
f"{self.static_url_path}/<path:filename>",
|
|
376
|
+
endpoint="static",
|
|
377
|
+
view_func=app.send_static_file,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def has_static_folder(self) -> bool:
|
|
382
|
+
"""Check if blueprint has a static folder."""
|
|
383
|
+
return self.static_folder is not None
|
|
384
|
+
|
|
385
|
+
def send_static_file(self, filename: str):
|
|
386
|
+
"""
|
|
387
|
+
Send static file from blueprint's static folder.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
filename: Static file name
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Response with static file
|
|
394
|
+
"""
|
|
395
|
+
if not self.has_static_folder:
|
|
396
|
+
raise RuntimeError("Blueprint does not have a static folder")
|
|
397
|
+
|
|
398
|
+
# TODO: Implement static file serving
|
|
399
|
+
from .helpers import send_from_directory
|
|
400
|
+
|
|
401
|
+
return send_from_directory(self.static_folder, filename)
|
|
402
|
+
|
|
403
|
+
def open_resource(self, resource: str, mode: str = "rb"):
|
|
404
|
+
"""
|
|
405
|
+
Open resource file from blueprint.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
resource: Resource path
|
|
409
|
+
mode: File open mode
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
File object
|
|
413
|
+
"""
|
|
414
|
+
# TODO: Implement resource opening
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
def get_send_file_max_age(self, filename: Optional[str]) -> Optional[int]:
|
|
418
|
+
"""
|
|
419
|
+
Get max age for static files.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
filename: File name
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Max age in seconds
|
|
426
|
+
"""
|
|
427
|
+
# TODO: Implement static file max age
|
|
428
|
+
return None
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class BlueprintSetupState:
|
|
432
|
+
"""
|
|
433
|
+
State object for blueprint registration.
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
def __init__(
|
|
437
|
+
self,
|
|
438
|
+
blueprint: Blueprint,
|
|
439
|
+
app,
|
|
440
|
+
options: Dict[str, Any],
|
|
441
|
+
first_registration: bool = False,
|
|
442
|
+
):
|
|
443
|
+
"""
|
|
444
|
+
Initialize setup state.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
blueprint: Blueprint instance
|
|
448
|
+
app: Application instance
|
|
449
|
+
options: Registration options
|
|
450
|
+
first_registration: Whether this is the first registration
|
|
451
|
+
"""
|
|
452
|
+
self.blueprint = blueprint
|
|
453
|
+
self.app = app
|
|
454
|
+
self.options = options
|
|
455
|
+
self.first_registration = first_registration
|
|
456
|
+
|
|
457
|
+
self.url_prefix = options.get("url_prefix")
|
|
458
|
+
self.subdomain = options.get("subdomain")
|
|
459
|
+
self.url_defaults = options.get("url_defaults")
|
|
460
|
+
|
|
461
|
+
def add_url_rule(
|
|
462
|
+
self,
|
|
463
|
+
rule: str,
|
|
464
|
+
endpoint: Optional[str] = None,
|
|
465
|
+
view_func: Optional[Callable] = None,
|
|
466
|
+
**options,
|
|
467
|
+
) -> None:
|
|
468
|
+
"""
|
|
469
|
+
Add URL rule to application.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
rule: URL rule
|
|
473
|
+
endpoint: Endpoint name
|
|
474
|
+
view_func: View function
|
|
475
|
+
**options: Additional options
|
|
476
|
+
"""
|
|
477
|
+
if self.url_prefix is not None:
|
|
478
|
+
if rule:
|
|
479
|
+
rule = f"{self.url_prefix.rstrip('/')}/{rule.lstrip('/')}"
|
|
480
|
+
else:
|
|
481
|
+
rule = self.url_prefix
|
|
482
|
+
|
|
483
|
+
options.setdefault("subdomain", self.subdomain)
|
|
484
|
+
if endpoint is None:
|
|
485
|
+
endpoint = f"{self.blueprint.name}.{view_func.__name__}"
|
|
486
|
+
else:
|
|
487
|
+
endpoint = f"{self.blueprint.name}.{endpoint}"
|
|
488
|
+
|
|
489
|
+
defaults = self.url_defaults
|
|
490
|
+
if "defaults" in options:
|
|
491
|
+
if defaults:
|
|
492
|
+
defaults = {**defaults, **options["defaults"]}
|
|
493
|
+
else:
|
|
494
|
+
defaults = options["defaults"]
|
|
495
|
+
|
|
496
|
+
if defaults:
|
|
497
|
+
options["defaults"] = defaults
|
|
498
|
+
|
|
499
|
+
# Add rule to application
|
|
500
|
+
self.app.add_url_rule(rule, endpoint, view_func, **options)
|
|
Binary file
|