Coverage for src/lexigram/web/filters/decorators.py: 42%

19 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 04:37 +0800

1"""Exception filter decorators for Lexigram Framework. 

2 

3Provides @use_filters for applying exception filters to controllers and handlers. 

4""" 

5 

6from __future__ import annotations 

7 

8from collections.abc import Callable 

9import inspect 

10from typing import Any, TypeVar, cast 

11 

12from lexigram.contracts.web.protocols import ExceptionFilterProtocol 

13 

14T = TypeVar("T") 

15 

16 

17def use_filters(*filter_instances: ExceptionFilterProtocol) -> Callable[[T], T]: 

18 """Decorator to apply exception filters to a class or method. 

19 

20 Filters handle exceptions raised during the request lifecycle. 

21 Filters applied at the class level are inherited by all handler methods. 

22 

23 Example:: 

24 

25 @use_filters(HttpExceptionFilter()) 

26 class UserController(Controller): 

27 @get("/users/{id}") 

28 async def get(self, id: str): 

29 ... 

30 

31 Args: 

32 *filter_instances: ExceptionFilterProtocol instances to apply. 

33 

34 Returns: 

35 The decorated class or function. 

36 """ 

37 

38 def decorator(target: T) -> T: 

39 if inspect.isclass(target): 

40 # Apply to all methods of the class 

41 for name, method in inspect.getmembers( 

42 target, 

43 predicate=inspect.isfunction, 

44 ): 

45 if not name.startswith("_"): 

46 existing: list[ExceptionFilterProtocol] = ( 

47 getattr(method, "__filters__", None) or [] 

48 ) 

49 cast("Any", method).__filters__ = list(filter_instances) + existing 

50 return cast("T", target) 

51 

52 # Apply to a single function/method 

53 existing_filters: list[ExceptionFilterProtocol] = ( 

54 getattr(target, "__filters__", None) or [] 

55 ) 

56 cast("Any", target).__filters__ = existing_filters + list(filter_instances) 

57 return target 

58 

59 return decorator 

60 

61 

62__all__ = ["use_filters"]