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/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