Coverage for src / lexigram / contracts / core / module.py: 0%

42 statements  

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

1"""Module protocol definitions for the Lexigram framework. 

2 

3These protocols define the structural contracts for modules at the 

4contract layer. External packages (``lexigram-web``, ``lexigram-sql``, 

5etc.) can type-hint against these protocols without importing 

6``lexigram`` core. 

7 

8The concrete implementations (``Module``, ``DynamicModule``, 

9``ModuleMetadata``, ``ModuleCompiler``) live in 

10``lexigram.di.module`` within the core package. 

11""" 

12 

13from __future__ import annotations 

14 

15from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable 

16 

17if TYPE_CHECKING: 

18 from collections.abc import Sequence 

19 

20 

21@runtime_checkable 

22class ModuleMetadataProtocol(Protocol): 

23 """Structural protocol for module metadata descriptors. 

24 

25 Satisfied by :class:`lexigram.di.module.ModuleMetadata`. 

26 """ 

27 

28 @property 

29 def name(self) -> str: ... 

30 

31 @property 

32 def providers(self) -> tuple[type, ...]: ... 

33 

34 @property 

35 def imports(self) -> tuple[type | Any, ...]: ... 

36 

37 @property 

38 def exports(self) -> tuple[type, ...]: ... 

39 

40 @property 

41 def controllers(self) -> tuple[type, ...]: ... 

42 

43 @property 

44 def is_global(self) -> bool: ... 

45 

46 

47@runtime_checkable 

48class ModuleProtocol(Protocol): 

49 """Structural protocol for module classes. 

50 

51 A class satisfies this protocol if it has a ``__lexigram_module__`` 

52 attribute containing module metadata. This is automatically 

53 attached by the ``@module()`` decorator. 

54 

55 Usage in type hints:: 

56 

57 def process_module(mod: ModuleProtocol) -> None: 

58 meta = mod.__lexigram_module__ 

59 print(f"Module {meta.name} has {len(meta.providers)} providers") 

60 """ 

61 

62 @property 

63 def __lexigram_module__(self) -> ModuleMetadataProtocol: ... 

64 

65 

66@runtime_checkable 

67class DynamicModuleProtocol(Protocol): 

68 """Structural protocol for dynamic module descriptors. 

69 

70 Satisfied by :class:`lexigram.di.module.DynamicModule`. 

71 Dynamic modules are returned by factory methods like 

72 ``Module.configure()`` and fully replace the static ``@module()`` 

73 metadata for the referenced module class. 

74 """ 

75 

76 @property 

77 def module(self) -> type: 

78 """The module class this descriptor configures (identity key).""" 

79 ... 

80 

81 @property 

82 def providers(self) -> list[type | Any]: 

83 """Provider classes or pre-constructed instances.""" 

84 ... 

85 

86 @property 

87 def imports(self) -> list[type | Any]: 

88 """Module classes or nested dynamic module descriptors.""" 

89 ... 

90 

91 @property 

92 def exports(self) -> list[type]: 

93 """Contract types visible to importing modules.""" 

94 ... 

95 

96 @property 

97 def controllers(self) -> list[type]: 

98 """Controller classes for this module.""" 

99 ... 

100 

101 @property 

102 def is_global(self) -> bool: 

103 """Whether exports are universally visible.""" 

104 ... 

105 

106 @property 

107 def resolved_name(self) -> str: 

108 """Module name (explicit or derived from module class).""" 

109 ... 

110 

111 

112@runtime_checkable 

113class CompiledModuleGraphProtocol(Protocol): 

114 """Structural protocol for the compiled module graph. 

115 

116 Satisfied by :class:`lexigram.di.module.CompiledModuleGraph`. 

117 """ 

118 

119 @property 

120 def provider_order(self) -> Sequence[Any]: 

121 """Providers in registration order.""" 

122 ... 

123 

124 @property 

125 def global_exports(self) -> frozenset[type]: 

126 """Union of all global module exports.""" 

127 ... 

128 

129 @property 

130 def warnings(self) -> list[str]: 

131 """Non-fatal issues detected during compilation.""" 

132 ... 

133 

134 def is_visible(self, from_module: type, service_type: type) -> bool: 

135 """Check if a service type is visible to a module.""" 

136 ... 

137 

138 def get_module_names(self) -> list[str]: 

139 """Return all module names in the graph.""" 

140 ... 

141 

142 

143@runtime_checkable 

144class ModuleCompilerProtocol(Protocol): 

145 """Protocol for the module graph compiler. 

146 

147 Satisfied by :class:`lexigram.di.module.ModuleCompiler`. 

148 """ 

149 

150 def compile( 

151 self, 

152 root_modules: list[type | Any], 

153 standalone_providers: list[Any] | None = None, 

154 ) -> CompiledModuleGraphProtocol: 

155 """Compile the module graph. 

156 

157 Args: 

158 root_modules: Module classes and/or dynamic module instances. 

159 standalone_providers: Provider instances not belonging to 

160 any module. 

161 

162 Returns: 

163 A compiled module graph ready for the orchestrator. 

164 """ 

165 ... 

166 

167 

168@runtime_checkable 

169class ModuleRegistryProtocol(Protocol): 

170 """Protocol for runtime module ownership tracking. 

171 

172 Satisfied by :class:`lexigram.di.module.ModuleRegistry`. 

173 """ 

174 

175 def register_ownership( 

176 self, 

177 service_type: type, 

178 module_class: type | None, 

179 provider_name: str | None = None, 

180 ) -> None: 

181 """Record that a service type was registered by a module.""" 

182 ... 

183 

184 def get_owner(self, service_type: type) -> type | None: 

185 """Get the module that owns a service type.""" 

186 ... 

187 

188 def get_module_services(self, module_class: type) -> frozenset[type]: 

189 """Get all service types registered by a module.""" 

190 ... 

191 

192 def validate_exports( 

193 self, 

194 graph: CompiledModuleGraphProtocol, 

195 container: Any | None = None, 

196 ) -> list[str]: 

197 """Validate that declared exports were actually registered.""" 

198 ... 

199 

200 

201__all__ = [ 

202 "CompiledModuleGraphProtocol", 

203 "DynamicModuleProtocol", 

204 "ModuleCompilerProtocol", 

205 "ModuleMetadataProtocol", 

206 "ModuleProtocol", 

207 "ModuleRegistryProtocol", 

208]