Coverage for src / lexigram / contracts / exceptions / container.py: 0%

64 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-15 18:57 +0800

1"""Container and dependency injection exception classes.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from typing import Any 

7 

8from lexigram.contracts.exceptions.base import LexigramError 

9 

10 

11class ContainerError(LexigramError): 

12 """Base container error.""" 

13 

14 _code = "LEX_ERR_DI_001" 

15 

16 def __init__(self, message: str = "Container error", **kwargs: Any) -> None: 

17 super().__init__(message, **kwargs) 

18 

19 

20class DependencyError(ContainerError): 

21 """Dependency resolution error.""" 

22 

23 _code = "LEX_ERR_DI_002" 

24 

25 def __init__(self, message: str = "Dependency error", **kwargs: Any) -> None: 

26 super().__init__(message, **kwargs) 

27 

28 

29class CircularDependencyError(DependencyError): 

30 """Circular dependency detected.""" 

31 

32 _code = "LEX_ERR_DI_003" 

33 

34 def __init__( 

35 self, 

36 message: str = "Circular dependency detected", 

37 **kwargs: Any, 

38 ) -> None: 

39 kwargs.setdefault( 

40 "hint", 

41 "Check constructor parameters of the classes in the cycle for mutual references.", 

42 ) 

43 super().__init__(message, **kwargs) 

44 

45 

46class UnresolvableDependencyError(DependencyError): 

47 """Cannot resolve dependency.""" 

48 

49 _code = "LEX_ERR_DI_004" 

50 

51 def __init__( 

52 self, 

53 message: str = "Cannot resolve dependency", 

54 dependency: str | None = None, 

55 **kwargs: Any, 

56 ) -> None: 

57 details = kwargs.get("details", {}) 

58 if dependency: 

59 details["dependency"] = dependency 

60 kwargs["details"] = details 

61 kwargs.setdefault( 

62 "hint", 

63 "Verify the type is registered in a Provider and all its dependencies are resolvable.", 

64 ) 

65 super().__init__(message, **kwargs) 

66 

67 

68class RegistrationError(ContainerError): 

69 """Service registration error.""" 

70 

71 _code = "LEX_ERR_DI_005" 

72 

73 def __init__(self, message: str = "Registration error", **kwargs: Any) -> None: 

74 kwargs.setdefault( 

75 "hint", 

76 "Ensure the service is registered in a Provider.register() before the container boots.", 

77 ) 

78 super().__init__(message, **kwargs) 

79 

80 

81class ContainerBuildError(ContainerError): 

82 """Container build/configuration error.""" 

83 

84 _code = "LEX_ERR_DI_006" 

85 

86 def __init__( 

87 self, 

88 message: str = "Container build error", 

89 **kwargs: Any, 

90 ) -> None: 

91 super().__init__(message, **kwargs) 

92 

93 

94class ProtocolValidationError(ContainerError): 

95 """Protocol validation error.""" 

96 

97 _code = "LEX_ERR_DI_007" 

98 

99 def __init__( 

100 self, 

101 message: str = "Protocol validation failed", 

102 **kwargs: Any, 

103 ) -> None: 

104 super().__init__(message, **kwargs) 

105 

106 

107class ContainerValidationError(ContainerBuildError): 

108 """Container dependency graph validation failed before boot. 

109 

110 Raised when ``Container.validate()`` discovers issues (missing 

111 dependencies, circular references, scope violations) during the 

112 pre-boot validation phase. The ``issues`` attribute contains the 

113 full list of problem descriptions. 

114 """ 

115 

116 _code = "LEX_ERR_DI_008" 

117 

118 def __init__( 

119 self, 

120 issues: list[str], 

121 message: str | None = None, 

122 **kwargs: Any, 

123 ) -> None: 

124 self.issues = issues 

125 formatted = "; ".join(issues) 

126 msg = message or f"Container validation failed: {formatted}" 

127 kwargs.setdefault("details", {"issues": issues}) 

128 super().__init__(msg, **kwargs) 

129 

130 

131class ScopedResolutionError(UnresolvableDependencyError): 

132 """Attempted to resolve a scoped service outside of an active scope. 

133 

134 Raised when a service registered with ``container.scoped()`` is resolved 

135 directly on the root container instead of inside a 

136 ``container.create_scope()`` block. 

137 """ 

138 

139 _code = "LEX_ERR_DI_009" 

140 

141 def __init__( 

142 self, 

143 message: str = "Cannot resolve scoped service outside of an active scope", 

144 service: str | None = None, 

145 **kwargs: Any, 

146 ) -> None: 

147 details = kwargs.get("details", {}) 

148 if service: 

149 details["service"] = service 

150 kwargs["details"] = details 

151 super().__init__(message, dependency=service, **kwargs) 

152 

153 

154@dataclass(frozen=True) 

155class OrphanedRegistration: 

156 """Represents a potentially orphaned service registration. 

157 

158 An orphaned registration is a service that is registered in the 

159 container but is never depended upon by any other registered service 

160 and is not in the container's exports list. 

161 """ 

162 

163 service_key: str 

164 """The service type name or key.""" 

165 

166 registered_at: str 

167 """The location/module where the service was registered.""" 

168 

169 suggestion: str 

170 """A suggestion for resolving the orphan.""" 

171 

172 

173__all__ = [ 

174 "CircularDependencyError", 

175 "ContainerBuildError", 

176 "ContainerError", 

177 "ContainerValidationError", 

178 "DependencyError", 

179 "OrphanedRegistration", 

180 "ProtocolValidationError", 

181 "RegistrationError", 

182 "ScopedResolutionError", 

183 "UnresolvableDependencyError", 

184]