Coverage for postrfp/shared/decorators.py: 100%

37 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-22 21:34 +0000

1import logging 

2from typing import TypeVar, Callable 

3 

4from pydantic import BaseModel 

5from webob.response import Response 

6 

7from .exceptions import RoutingError 

8from .constants import HANDLER_PREFIX 

9 

10log = logging.getLogger(__name__) 

11 

12T = TypeVar("T", bound=Callable[..., BaseModel | dict | list | Response | None]) 

13 

14 

15def _set_exposed(handler: T) -> None: 

16 fname = handler.__name__ 

17 if not HANDLER_PREFIX.match(fname): 

18 raise RoutingError( 

19 f"{fname} is not a valid name for a handler function." 

20 " Must start with get, post, put or delete" 

21 ) 

22 setattr(handler, "http_exposed", True) 

23 

24 

25def _check_docstring(handler: T) -> None: 

26 """ 

27 Check that the http function has a docstring suitable for publication 

28 in the resulting openapi spec. 

29 """ 

30 docstring = handler.__doc__ 

31 if not docstring or len(docstring.strip()) < 10: 

32 log.error(f'{handler.__module__} "{handler.__name__}" is missing a docstring.') 

33 

34 

35def http(handler: T) -> T: 

36 """ 

37 Decorator to set exposed=True on a handler callable 

38 """ 

39 _set_exposed(handler) 

40 _check_docstring(handler) 

41 return handler 

42 

43 

44def http_etag(handler: T) -> T: 

45 """ 

46 Decorator to set attributes exposed=True and etag=True on a handler callable 

47 """ 

48 _set_exposed(handler) 

49 _check_docstring(handler) 

50 setattr(handler, "etag", True) 

51 return handler 

52 

53 

54class http_auth: 

55 """ 

56 Decorator to mark a handler as HTTP accessible and associate a permission name. 

57 

58 The permission name is stored on the handler as ``required_permission``. 

59 """ 

60 

61 def __init__(self, permission: str, deny_restricted: bool = False) -> None: 

62 if not isinstance(permission, str) or not permission: 

63 raise ValueError("permission must be a non-empty string") 

64 self.permission = permission 

65 self.deny_restricted = deny_restricted 

66 

67 def __call__(self, handler: T) -> T: 

68 # Mark the handler as HTTP accessible and check for docstring 

69 decorated = http(handler) 

70 # Apply the permission attributes passed as decorator arguments 

71 setattr(decorated, "required_permission", self.permission) 

72 setattr(decorated, "deny_restricted", self.deny_restricted) 

73 

74 return decorated