Coverage for src/lexigram/web/routing/controller_registry.py: 43%

37 statements  

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

1"""Controller registration utilities for Lexigram Web Framework. 

2 

3This module provides the ControllerRegistry. 

4 

5Note: ControllerRegistry is the canonical source for controller classes. 

6RouteRegistry (in registry.py) extends this concept to track route metadata. 

7Both registries work together: ControllerRegistry stores the controller classes 

8while RouteRegistry stores the resolved route paths and methods. 

9""" 

10 

11from __future__ import annotations 

12 

13from typing import Any 

14 

15from lexigram.logging import get_logger 

16from lexigram.primitives.registry import Registry 

17 

18logger = get_logger(__name__) 

19 

20 

21class ControllerRegistry(Registry[str, type]): 

22 """Registry for managing controller registrations. 

23 

24 Canonical source for controllers: ControllerRegistry. 

25 Use ControllerRegistry for controller-level operations (class registration). 

26 Use RouteRegistry for route-level operations (path, method, handler). 

27 """ 

28 

29 def __init__(self) -> None: 

30 super().__init__(name="controllers") 

31 

32 def register( 

33 self, 

34 key: str | type, 

35 value: type | None = None, 

36 *, 

37 allow_overwrite: bool | None = None, 

38 ) -> type | Any: 

39 """Register a controller class. 

40 

41 Args: 

42 key: Component key or class 

43 value: The controller class to register 

44 allow_overwrite: Whether to allow overwriting an existing registration 

45 

46 Returns: 

47 The registered controller class 

48 """ 

49 controller_class: type | None 

50 if isinstance(key, type): 

51 controller_class = key 

52 name = getattr(controller_class, "__name__", str(controller_class)) 

53 else: 

54 name = key 

55 controller_class = value 

56 

57 if controller_class is None: 

58 # Decorator usage: @registry.register("name") 

59 def decorator(cls: type) -> type: 

60 super(ControllerRegistry, self).register(name, cls) 

61 return cls 

62 

63 return decorator 

64 

65 super().register(name, controller_class) 

66 return controller_class 

67 

68 def get(self, name: str) -> type | None: # type: ignore[override] 

69 """Get a controller class by name.""" 

70 return super().get(name) 

71 

72 def list_controllers(self) -> list[str]: 

73 """List all registered controller names.""" 

74 return list(super().keys()) 

75 

76 def get_all_controllers(self) -> list[type]: 

77 """Get all registered controller classes.""" 

78 return list(super().values()) 

79 

80 def clear(self) -> None: 

81 """Clear all registrations. 

82 

83 Use in tests only to prevent test pollution between test cases. 

84 """ 

85 super().clear() 

86 

87 

88def controller(name: str | None = None) -> Any: 

89 """Decorator to automatically register a controller class. 

90 

91 Args: 

92 name: Optional controller name, defaults to class name 

93 

94 Example: 

95 @controller() 

96 class UserController(Controller): 

97 @get("/users") 

98 async def get_users(self): 

99 return {"users": []} 

100 """ 

101 

102 def decorator(cls: type) -> type: 

103 # Register with controller registry 

104 controller_name = name or cls.__name__ 

105 controller_registry.register(controller_name, cls) 

106 

107 logger.debug( 

108 "Auto-registered controller %s from %s.%s", 

109 controller_name, 

110 cls.__module__, 

111 cls.__name__, 

112 ) 

113 return cls 

114 

115 return decorator 

116 

117 

118# Global controller registry instance 

119controller_registry = ControllerRegistry()