core-framework 0.12.1__py3-none-any.whl → 0.12.2__py3-none-any.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.
core/middleware.py ADDED
@@ -0,0 +1,774 @@
1
+ """
2
+ Sistema de Middleware Plugável - Django-style.
3
+
4
+ Permite configurar middlewares de forma declarativa, similar ao MIDDLEWARE do Django.
5
+
6
+ Uso:
7
+ # Em settings ou core.toml
8
+ MIDDLEWARE = [
9
+ "core.auth.AuthenticationMiddleware",
10
+ "core.tenancy.TenantMiddleware",
11
+ "myapp.middleware.CustomMiddleware",
12
+ ]
13
+
14
+ # Ou via configuração
15
+ from core.middleware import configure_middleware
16
+
17
+ configure_middleware([
18
+ "core.auth.AuthenticationMiddleware",
19
+ ("myapp.middleware.RateLimitMiddleware", {"requests_per_minute": 60}),
20
+ ])
21
+
22
+ O framework carrega e aplica os middlewares na ordem especificada.
23
+
24
+ Criando middlewares customizados:
25
+ from core.middleware import BaseMiddleware
26
+
27
+ class MyMiddleware(BaseMiddleware):
28
+ async def before_request(self, request):
29
+ # Executado antes da view
30
+ request.state.custom_data = "hello"
31
+
32
+ async def after_request(self, request, response):
33
+ # Executado depois da view
34
+ response.headers["X-Custom"] = "value"
35
+ return response
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ import importlib
41
+ import warnings
42
+ from abc import ABC
43
+ from dataclasses import dataclass, field
44
+ from typing import Any, TYPE_CHECKING
45
+
46
+ from starlette.middleware.base import BaseHTTPMiddleware
47
+ from starlette.requests import Request
48
+ from starlette.responses import Response
49
+
50
+ if TYPE_CHECKING:
51
+ from collections.abc import Callable, Awaitable
52
+ from fastapi import FastAPI
53
+
54
+
55
+ # =============================================================================
56
+ # Base Middleware Class
57
+ # =============================================================================
58
+
59
+ class BaseMiddleware(BaseHTTPMiddleware):
60
+ """
61
+ Classe base para criar middlewares de forma simplificada.
62
+
63
+ Herde desta classe e implemente os métodos que precisar:
64
+
65
+ - before_request(request): Executado antes da view
66
+ - after_request(request, response): Executado depois da view
67
+ - on_error(request, exc): Executado quando ocorre exceção
68
+
69
+ Exemplo:
70
+ from core.middleware import BaseMiddleware
71
+
72
+ class TimingMiddleware(BaseMiddleware):
73
+ '''Mede tempo de execução das requests.'''
74
+
75
+ async def before_request(self, request):
76
+ import time
77
+ request.state.start_time = time.time()
78
+
79
+ async def after_request(self, request, response):
80
+ import time
81
+ duration = time.time() - request.state.start_time
82
+ response.headers["X-Response-Time"] = f"{duration:.3f}s"
83
+ return response
84
+
85
+ class AuthMiddleware(BaseMiddleware):
86
+ '''Autentica usuários.'''
87
+
88
+ def __init__(self, app, user_model=None):
89
+ super().__init__(app)
90
+ self.user_model = user_model
91
+
92
+ async def before_request(self, request):
93
+ request.state.user = await self.authenticate(request)
94
+ """
95
+
96
+ # Nome legível do middleware (para logs e debug)
97
+ name: str = "BaseMiddleware"
98
+
99
+ # Ordem de execução (menor = executa primeiro)
100
+ # Middlewares com mesma ordem executam na ordem de registro
101
+ order: int = 100
102
+
103
+ # Paths para ignorar (não executar middleware)
104
+ exclude_paths: list[str] = []
105
+
106
+ # Paths para incluir (executar apenas nesses)
107
+ # Se vazio, executa em todos (exceto exclude_paths)
108
+ include_paths: list[str] = []
109
+
110
+ def __init__(
111
+ self,
112
+ app: "Callable[[Request], Awaitable[Response]]",
113
+ **kwargs: Any,
114
+ ) -> None:
115
+ """
116
+ Inicializa o middleware.
117
+
118
+ Args:
119
+ app: Próximo app/middleware na cadeia
120
+ **kwargs: Configurações customizadas
121
+ """
122
+ super().__init__(app)
123
+
124
+ # Aplica kwargs como atributos
125
+ for key, value in kwargs.items():
126
+ setattr(self, key, value)
127
+
128
+ async def dispatch(
129
+ self,
130
+ request: Request,
131
+ call_next: "Callable[[Request], Awaitable[Response]]",
132
+ ) -> Response:
133
+ """
134
+ Processa a request através do middleware.
135
+
136
+ Não sobrescreva este método diretamente.
137
+ Use before_request, after_request e on_error.
138
+ """
139
+ # Verifica se deve processar esta request
140
+ if not self._should_process(request.url.path):
141
+ return await call_next(request)
142
+
143
+ try:
144
+ # Before request hook
145
+ result = await self.before_request(request)
146
+
147
+ # Se before_request retornar Response, use-a diretamente
148
+ if isinstance(result, Response):
149
+ return result
150
+
151
+ # Chama próximo middleware/view
152
+ response = await call_next(request)
153
+
154
+ # After request hook
155
+ response = await self.after_request(request, response)
156
+
157
+ return response
158
+
159
+ except Exception as exc:
160
+ # Error hook
161
+ error_response = await self.on_error(request, exc)
162
+ if error_response is not None:
163
+ return error_response
164
+ raise
165
+
166
+ def _should_process(self, path: str) -> bool:
167
+ """Verifica se deve processar esta path."""
168
+ # Se include_paths definido, só processa esses
169
+ if self.include_paths:
170
+ return any(path.startswith(p) for p in self.include_paths)
171
+
172
+ # Verifica exclude_paths
173
+ if self.exclude_paths:
174
+ return not any(path.startswith(p) for p in self.exclude_paths)
175
+
176
+ return True
177
+
178
+ async def before_request(self, request: Request) -> Response | None:
179
+ """
180
+ Hook executado antes da view.
181
+
182
+ Args:
183
+ request: Request objeto
184
+
185
+ Returns:
186
+ None para continuar, ou Response para retornar diretamente
187
+ """
188
+ pass
189
+
190
+ async def after_request(self, request: Request, response: Response) -> Response:
191
+ """
192
+ Hook executado depois da view.
193
+
194
+ Args:
195
+ request: Request objeto
196
+ response: Response da view
197
+
198
+ Returns:
199
+ Response (pode ser modificada)
200
+ """
201
+ return response
202
+
203
+ async def on_error(self, request: Request, exc: Exception) -> Response | None:
204
+ """
205
+ Hook executado quando ocorre exceção.
206
+
207
+ Args:
208
+ request: Request objeto
209
+ exc: Exceção que ocorreu
210
+
211
+ Returns:
212
+ Response para retornar, ou None para re-raise
213
+ """
214
+ return None
215
+
216
+
217
+ # =============================================================================
218
+ # Middleware Registry
219
+ # =============================================================================
220
+
221
+ @dataclass
222
+ class MiddlewareConfig:
223
+ """Configuração de um middleware."""
224
+
225
+ # Classe ou path string do middleware
226
+ middleware: str | type
227
+
228
+ # Kwargs para passar ao middleware
229
+ kwargs: dict[str, Any] = field(default_factory=dict)
230
+
231
+ # Se está habilitado
232
+ enabled: bool = True
233
+
234
+ # Nome para identificação (auto-gerado se None)
235
+ name: str | None = None
236
+
237
+ def __post_init__(self):
238
+ if self.name is None:
239
+ if isinstance(self.middleware, str):
240
+ self.name = self.middleware.split(".")[-1]
241
+ else:
242
+ self.name = self.middleware.__name__
243
+
244
+
245
+ # Registry global de middlewares configurados
246
+ _middleware_registry: list[MiddlewareConfig] = []
247
+
248
+ # Middlewares built-in disponíveis (atalhos)
249
+ _builtin_middlewares: dict[str, str] = {
250
+ # Auth
251
+ "auth": "core.auth.middleware.AuthenticationMiddleware",
252
+ "authentication": "core.auth.middleware.AuthenticationMiddleware",
253
+ "optional_auth": "core.auth.middleware.OptionalAuthenticationMiddleware",
254
+
255
+ # Tenancy
256
+ "tenant": "core.tenancy.TenantMiddleware",
257
+ "tenancy": "core.tenancy.TenantMiddleware",
258
+
259
+ # Common
260
+ "cors": "starlette.middleware.cors.CORSMiddleware",
261
+ "gzip": "starlette.middleware.gzip.GZipMiddleware",
262
+ "https_redirect": "starlette.middleware.httpsredirect.HTTPSRedirectMiddleware",
263
+ "trusted_host": "starlette.middleware.trustedhost.TrustedHostMiddleware",
264
+ }
265
+
266
+
267
+ def _resolve_middleware_class(middleware: str | type) -> type:
268
+ """
269
+ Resolve middleware string para classe.
270
+
271
+ Args:
272
+ middleware: String path ou classe direta
273
+
274
+ Returns:
275
+ Classe do middleware
276
+
277
+ Raises:
278
+ ImportError: Se não encontrar
279
+ """
280
+ if isinstance(middleware, type):
281
+ return middleware
282
+
283
+ # Verifica se é atalho built-in
284
+ if middleware in _builtin_middlewares:
285
+ middleware = _builtin_middlewares[middleware]
286
+
287
+ # Importa dinamicamente
288
+ try:
289
+ module_path, class_name = middleware.rsplit(".", 1)
290
+ module = importlib.import_module(module_path)
291
+ return getattr(module, class_name)
292
+ except (ValueError, ImportError, AttributeError) as e:
293
+ raise ImportError(
294
+ f"Could not import middleware '{middleware}'. "
295
+ f"Make sure the module and class exist. Error: {e}"
296
+ )
297
+
298
+
299
+ def register_middleware(
300
+ middleware: str | type,
301
+ kwargs: dict[str, Any] | None = None,
302
+ enabled: bool = True,
303
+ name: str | None = None,
304
+ ) -> None:
305
+ """
306
+ Registra um middleware no registry global.
307
+
308
+ Args:
309
+ middleware: Classe ou path string do middleware
310
+ kwargs: Argumentos para o middleware
311
+ enabled: Se está habilitado
312
+ name: Nome opcional
313
+
314
+ Example:
315
+ register_middleware("core.auth.AuthenticationMiddleware")
316
+ register_middleware(MyMiddleware, {"option": "value"})
317
+ """
318
+ config = MiddlewareConfig(
319
+ middleware=middleware,
320
+ kwargs=kwargs or {},
321
+ enabled=enabled,
322
+ name=name,
323
+ )
324
+ _middleware_registry.append(config)
325
+
326
+
327
+ def unregister_middleware(name_or_class: str | type) -> bool:
328
+ """
329
+ Remove um middleware do registry.
330
+
331
+ Args:
332
+ name_or_class: Nome ou classe do middleware
333
+
334
+ Returns:
335
+ True se removido, False se não encontrado
336
+ """
337
+ global _middleware_registry
338
+
339
+ for i, config in enumerate(_middleware_registry):
340
+ if config.name == name_or_class or config.middleware == name_or_class:
341
+ _middleware_registry.pop(i)
342
+ return True
343
+
344
+ return False
345
+
346
+
347
+ def get_registered_middlewares() -> list[MiddlewareConfig]:
348
+ """Retorna lista de middlewares registrados."""
349
+ return _middleware_registry.copy()
350
+
351
+
352
+ def clear_middleware_registry() -> None:
353
+ """Limpa o registry de middlewares."""
354
+ global _middleware_registry
355
+ _middleware_registry = []
356
+
357
+
358
+ # =============================================================================
359
+ # Configuration Functions
360
+ # =============================================================================
361
+
362
+ def configure_middleware(
363
+ middlewares: list[str | type | tuple[str | type, dict[str, Any]]],
364
+ clear_existing: bool = True,
365
+ ) -> None:
366
+ """
367
+ Configura middlewares de forma declarativa, estilo Django.
368
+
369
+ Args:
370
+ middlewares: Lista de middlewares para registrar
371
+ clear_existing: Se True, limpa registry antes
372
+
373
+ Example:
374
+ configure_middleware([
375
+ # String path
376
+ "core.auth.AuthenticationMiddleware",
377
+
378
+ # Com kwargs
379
+ ("myapp.RateLimitMiddleware", {"requests_per_minute": 60}),
380
+
381
+ # Classe direta
382
+ MyCustomMiddleware,
383
+
384
+ # Built-in shortcut
385
+ "auth", # = core.auth.AuthenticationMiddleware
386
+
387
+ # Built-in com kwargs
388
+ ("gzip", {"minimum_size": 500}),
389
+ ])
390
+ """
391
+ if clear_existing:
392
+ clear_middleware_registry()
393
+
394
+ for item in middlewares:
395
+ if isinstance(item, tuple):
396
+ middleware, kwargs = item
397
+ register_middleware(middleware, kwargs)
398
+ else:
399
+ register_middleware(item)
400
+
401
+
402
+ def apply_middlewares(app: "FastAPI") -> "FastAPI":
403
+ """
404
+ Aplica todos os middlewares registrados ao app.
405
+
406
+ Args:
407
+ app: FastAPI app
408
+
409
+ Returns:
410
+ App com middlewares aplicados
411
+ """
412
+ # Ordena por prioridade (se BaseMiddleware)
413
+ configs = get_registered_middlewares()
414
+
415
+ # Aplica em ordem reversa (primeiro registrado = mais externo)
416
+ for config in reversed(configs):
417
+ if not config.enabled:
418
+ continue
419
+
420
+ try:
421
+ middleware_class = _resolve_middleware_class(config.middleware)
422
+ app.add_middleware(middleware_class, **config.kwargs)
423
+ except ImportError as e:
424
+ warnings.warn(f"Failed to load middleware: {e}", RuntimeWarning)
425
+
426
+ return app
427
+
428
+
429
+ # =============================================================================
430
+ # Middleware Stack Info (Debug/Introspection)
431
+ # =============================================================================
432
+
433
+ def get_middleware_stack_info(app: Any) -> list[dict[str, Any]]:
434
+ """
435
+ Retorna informações sobre a stack de middlewares.
436
+
437
+ Útil para debug e introspection.
438
+
439
+ Args:
440
+ app: FastAPI ou CoreApp
441
+
442
+ Returns:
443
+ Lista com info de cada middleware
444
+ """
445
+ info = []
446
+
447
+ # Obtém FastAPI app se CoreApp
448
+ if hasattr(app, "app"):
449
+ app = app.app
450
+
451
+ # Percorre middleware stack
452
+ current = getattr(app, "middleware_stack", None)
453
+
454
+ while current is not None:
455
+ middleware_info = {
456
+ "class": type(current).__name__,
457
+ "module": type(current).__module__,
458
+ }
459
+
460
+ # Tenta obter atributos úteis
461
+ if hasattr(current, "name"):
462
+ middleware_info["name"] = current.name
463
+ if hasattr(current, "order"):
464
+ middleware_info["order"] = current.order
465
+ if hasattr(current, "exclude_paths"):
466
+ middleware_info["exclude_paths"] = current.exclude_paths
467
+
468
+ info.append(middleware_info)
469
+
470
+ # Próximo na stack
471
+ current = getattr(current, "app", None)
472
+
473
+ # Para quando chegar ao app final
474
+ if not hasattr(current, "middleware_stack") and not isinstance(current, BaseHTTPMiddleware):
475
+ if current is not None:
476
+ info.append({
477
+ "class": type(current).__name__,
478
+ "module": type(current).__module__,
479
+ "is_app": True,
480
+ })
481
+ break
482
+
483
+ return info
484
+
485
+
486
+ def print_middleware_stack(app: Any) -> None:
487
+ """
488
+ Imprime a stack de middlewares formatada.
489
+
490
+ Args:
491
+ app: FastAPI ou CoreApp
492
+ """
493
+ info = get_middleware_stack_info(app)
494
+
495
+ print("\n📦 Middleware Stack:")
496
+ print("=" * 50)
497
+
498
+ for i, mw in enumerate(info):
499
+ is_app = mw.get("is_app", False)
500
+ prefix = " └─ " if is_app else f" {i+1}. "
501
+ name = mw.get("name", mw["class"])
502
+
503
+ if is_app:
504
+ print(f"{prefix}[APP] {name}")
505
+ else:
506
+ print(f"{prefix}{name}")
507
+ if "exclude_paths" in mw and mw["exclude_paths"]:
508
+ print(f" exclude: {mw['exclude_paths']}")
509
+
510
+ print("=" * 50)
511
+
512
+
513
+ # =============================================================================
514
+ # Pre-built Middleware Classes
515
+ # =============================================================================
516
+
517
+ class TimingMiddleware(BaseMiddleware):
518
+ """
519
+ Middleware que mede tempo de resposta.
520
+
521
+ Adiciona header X-Response-Time com duração em segundos.
522
+
523
+ Usage:
524
+ configure_middleware([
525
+ "core.middleware.TimingMiddleware",
526
+ ])
527
+ """
528
+
529
+ name = "TimingMiddleware"
530
+ order = 10 # Executa cedo para medir tempo total
531
+
532
+ async def before_request(self, request: Request) -> None:
533
+ import time
534
+ request.state._timing_start = time.perf_counter()
535
+
536
+ async def after_request(self, request: Request, response: Response) -> Response:
537
+ import time
538
+ start = getattr(request.state, "_timing_start", None)
539
+ if start is not None:
540
+ duration = time.perf_counter() - start
541
+ response.headers["X-Response-Time"] = f"{duration:.4f}s"
542
+ return response
543
+
544
+
545
+ class RequestIDMiddleware(BaseMiddleware):
546
+ """
547
+ Middleware que adiciona ID único a cada request.
548
+
549
+ Útil para tracing e logs.
550
+
551
+ Usage:
552
+ configure_middleware([
553
+ "core.middleware.RequestIDMiddleware",
554
+ ])
555
+ """
556
+
557
+ name = "RequestIDMiddleware"
558
+ order = 5 # Executa muito cedo
559
+
560
+ # Nome do header para ID
561
+ header_name: str = "X-Request-ID"
562
+
563
+ async def before_request(self, request: Request) -> None:
564
+ import uuid
565
+
566
+ # Usa ID do header se fornecido, senão gera novo
567
+ request_id = request.headers.get(self.header_name)
568
+ if not request_id:
569
+ request_id = str(uuid.uuid4())
570
+
571
+ request.state.request_id = request_id
572
+
573
+ async def after_request(self, request: Request, response: Response) -> Response:
574
+ request_id = getattr(request.state, "request_id", None)
575
+ if request_id:
576
+ response.headers[self.header_name] = request_id
577
+ return response
578
+
579
+
580
+ class LoggingMiddleware(BaseMiddleware):
581
+ """
582
+ Middleware que loga requests.
583
+
584
+ Usage:
585
+ configure_middleware([
586
+ ("core.middleware.LoggingMiddleware", {"log_body": False}),
587
+ ])
588
+ """
589
+
590
+ name = "LoggingMiddleware"
591
+ order = 20
592
+
593
+ # Se deve logar body da request
594
+ log_body: bool = False
595
+
596
+ # Se deve logar headers
597
+ log_headers: bool = False
598
+
599
+ # Logger name
600
+ logger_name: str = "core.requests"
601
+
602
+ async def before_request(self, request: Request) -> None:
603
+ import logging
604
+ import time
605
+
606
+ request.state._log_start = time.perf_counter()
607
+
608
+ logger = logging.getLogger(self.logger_name)
609
+
610
+ msg = f"→ {request.method} {request.url.path}"
611
+ if request.query_params:
612
+ msg += f"?{request.query_params}"
613
+
614
+ logger.info(msg)
615
+
616
+ if self.log_headers:
617
+ logger.debug(f" Headers: {dict(request.headers)}")
618
+
619
+ async def after_request(self, request: Request, response: Response) -> Response:
620
+ import logging
621
+ import time
622
+
623
+ logger = logging.getLogger(self.logger_name)
624
+
625
+ start = getattr(request.state, "_log_start", None)
626
+ duration = ""
627
+ if start:
628
+ duration = f" [{time.perf_counter() - start:.3f}s]"
629
+
630
+ logger.info(f"← {response.status_code}{duration}")
631
+
632
+ return response
633
+
634
+ async def on_error(self, request: Request, exc: Exception) -> None:
635
+ import logging
636
+
637
+ logger = logging.getLogger(self.logger_name)
638
+ logger.error(f"✗ Error: {type(exc).__name__}: {exc}")
639
+
640
+ return None # Re-raise
641
+
642
+
643
+ class MaintenanceModeMiddleware(BaseMiddleware):
644
+ """
645
+ Middleware para modo de manutenção.
646
+
647
+ Retorna 503 para todas as requests quando ativado.
648
+
649
+ Usage:
650
+ configure_middleware([
651
+ ("core.middleware.MaintenanceModeMiddleware", {
652
+ "enabled": False, # Ative quando precisar
653
+ "message": "Site em manutenção",
654
+ "allowed_ips": ["127.0.0.1"],
655
+ }),
656
+ ])
657
+ """
658
+
659
+ name = "MaintenanceModeMiddleware"
660
+ order = 1 # Executa primeiro
661
+
662
+ # Se modo manutenção está ativo
663
+ maintenance_enabled: bool = False
664
+
665
+ # Mensagem de manutenção
666
+ message: str = "Service temporarily unavailable for maintenance"
667
+
668
+ # IPs permitidos mesmo em manutenção
669
+ allowed_ips: list[str] = []
670
+
671
+ # Paths permitidos mesmo em manutenção (ex: /health)
672
+ allowed_paths: list[str] = ["/health", "/healthz"]
673
+
674
+ async def before_request(self, request: Request) -> Response | None:
675
+ if not self.maintenance_enabled:
676
+ return None
677
+
678
+ # Verifica paths permitidos
679
+ if any(request.url.path.startswith(p) for p in self.allowed_paths):
680
+ return None
681
+
682
+ # Verifica IPs permitidos
683
+ client_ip = request.client.host if request.client else None
684
+ if client_ip in self.allowed_ips:
685
+ return None
686
+
687
+ # Retorna 503
688
+ from starlette.responses import JSONResponse
689
+ return JSONResponse(
690
+ status_code=503,
691
+ content={
692
+ "detail": self.message,
693
+ "code": "maintenance_mode",
694
+ },
695
+ )
696
+
697
+
698
+ class SecurityHeadersMiddleware(BaseMiddleware):
699
+ """
700
+ Middleware que adiciona headers de segurança.
701
+
702
+ Usage:
703
+ configure_middleware([
704
+ "core.middleware.SecurityHeadersMiddleware",
705
+ ])
706
+ """
707
+
708
+ name = "SecurityHeadersMiddleware"
709
+ order = 15
710
+
711
+ # Headers a adicionar
712
+ headers: dict[str, str] = {
713
+ "X-Content-Type-Options": "nosniff",
714
+ "X-Frame-Options": "DENY",
715
+ "X-XSS-Protection": "1; mode=block",
716
+ "Referrer-Policy": "strict-origin-when-cross-origin",
717
+ }
718
+
719
+ # Se deve adicionar HSTS (apenas para HTTPS)
720
+ enable_hsts: bool = False
721
+ hsts_max_age: int = 31536000 # 1 ano
722
+
723
+ async def after_request(self, request: Request, response: Response) -> Response:
724
+ for header, value in self.headers.items():
725
+ response.headers[header] = value
726
+
727
+ # HSTS apenas para HTTPS
728
+ if self.enable_hsts and request.url.scheme == "https":
729
+ response.headers["Strict-Transport-Security"] = f"max-age={self.hsts_max_age}"
730
+
731
+ return response
732
+
733
+
734
+ # =============================================================================
735
+ # Built-in middleware shortcuts - update registry
736
+ # =============================================================================
737
+
738
+ _builtin_middlewares.update({
739
+ "timing": "core.middleware.TimingMiddleware",
740
+ "request_id": "core.middleware.RequestIDMiddleware",
741
+ "logging": "core.middleware.LoggingMiddleware",
742
+ "maintenance": "core.middleware.MaintenanceModeMiddleware",
743
+ "security_headers": "core.middleware.SecurityHeadersMiddleware",
744
+ })
745
+
746
+
747
+ # =============================================================================
748
+ # Exports
749
+ # =============================================================================
750
+
751
+ __all__ = [
752
+ # Base class
753
+ "BaseMiddleware",
754
+
755
+ # Configuration
756
+ "MiddlewareConfig",
757
+ "configure_middleware",
758
+ "register_middleware",
759
+ "unregister_middleware",
760
+ "get_registered_middlewares",
761
+ "clear_middleware_registry",
762
+ "apply_middlewares",
763
+
764
+ # Debug
765
+ "get_middleware_stack_info",
766
+ "print_middleware_stack",
767
+
768
+ # Pre-built middlewares
769
+ "TimingMiddleware",
770
+ "RequestIDMiddleware",
771
+ "LoggingMiddleware",
772
+ "MaintenanceModeMiddleware",
773
+ "SecurityHeadersMiddleware",
774
+ ]