Coverage for nexios\decorators.py: 56%
55 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-21 20:31 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-21 20:31 +0100
1from typing import List, Dict, Any, TypeVar, Union, Callable, Type, Tuple
2from .http.request import Request
3from .http.response import NexiosResponse
4from .http.request import Request
5import typing
6from functools import wraps
7from .types import HandlerType
9F = TypeVar("F", bound=HandlerType)
12class RouteDecorator:
13 """Base class for all route decorators"""
15 def __init__(self, **kwargs: Dict[str, Any]):
16 pass
18 def __call__(self, handler: HandlerType) -> Any:
19 raise NotImplementedError("Handler not set")
21 def __get__(self, obj: typing.Any, objtype: typing.Any = None):
22 if obj is None:
23 return self
24 return self.__class__(obj) # type:ignore
27class allowed_methods(RouteDecorator):
28 def __init__(self, methods: List[str]) -> None:
29 super().__init__()
30 self.allowed_methods: List[str] = [method.upper() for method in methods]
32 def __call__(self, handler: F) -> F:
33 if getattr(handler, "_is_wrapped", False):
34 return handler
36 @wraps(handler)
37 async def wrapper(*args: List[Any], **kwargs: Dict[str, Any]) -> Any:
38 *_, request, response = args # Ensure request and response are last
40 if not isinstance(request, Request) or not isinstance(
41 response, NexiosResponse
42 ):
43 raise TypeError("Expected request and response as the first arguments")
45 if request.method.upper() not in self.allowed_methods:
46 return response.json(
47 {
48 "error": f"Method {request.method} not allowed",
49 "allowed_methods": self.allowed_methods,
50 },
51 status_code=405,
52 headers={"Allow": ", ".join(self.allowed_methods)},
53 )
55 return await handler(*args, **kwargs) # type:ignore
57 wrapper._is_wrapped = True # type: ignore
58 return wrapper # type: ignore
61class catch_exception(RouteDecorator):
62 """Decorator to catch specific exceptions and handle them with a custom handler"""
64 def __init__(
65 self,
66 exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]],
67 handler: Callable[[Exception, Request, NexiosResponse], Any],
68 ) -> None:
69 super().__init__()
70 if not isinstance(exceptions, tuple):
71 exceptions = (exceptions,)
72 self.exceptions = exceptions
73 self.exception_handler = handler
75 def __call__(self, handler: F) -> F:
76 if getattr(handler, "_is_wrapped", False):
77 return handler
79 @wraps(handler)
80 async def wrapper(*args: List[Any], **kwargs: Dict[str, Any]) -> Any:
81 try:
82 return await handler(*args, **kwargs) # type: ignore
83 except self.exceptions as e:
84 *_, request, response = args
86 if not isinstance(request, Request) or not isinstance(
87 response, NexiosResponse
88 ):
89 raise TypeError(
90 "Expected request and response as the last arguments"
91 )
93 return self.exception_handler(request, response, e) # type:ignore
95 wrapper._is_wrapped = True # type: ignore
96 return wrapper # type: ignore