Coverage for src/lexigram/web/__init__.py: 89%

18 statements  

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

1"""Lexigram Web - Progressive Web Framework for Lexigram""" 

2 

3from __future__ import annotations 

4 

5import importlib.metadata 

6from typing import TYPE_CHECKING, Any 

7 

8__path__ = __import__("pkgutil").extend_path(__path__, __name__) 

9 

10from lexigram.web.constants import __version__ as __version__ 

11 

12if TYPE_CHECKING: 

13 from lexigram.web.config import WebConfig 

14 from lexigram.web.di.provider import WebProvider 

15 from lexigram.web.di.rate_limit import RateLimitProvider 

16 

17 

18# ============================================================================= 

19# Lazy Loading to Avoid Circular Imports 

20# ============================================================================= 

21 

22_LAZY_IMPORTS: dict[str, tuple[str, str]] = { 

23 # Discovery 

24 "discover_controllers": ("lexigram.web.routing.discovery", "discover_controllers"), 

25 # Config & Provider 

26 "WebConfig": ("lexigram.web.config", "WebConfig"), 

27 "WebProvider": ("lexigram.web.di.provider", "WebProvider"), 

28 "RateLimitProvider": ("lexigram.web.di.rate_limit", "RateLimitProvider"), 

29 # Modules 

30 "WebModule": ("lexigram.web.module", "WebModule"), 

31 # Routing 

32 "Controller": ("lexigram.web.routing.controllers", "Controller"), 

33 "CQRSController": ("lexigram.web.routing.cqrs", "CQRSController"), 

34 "ControllerRegistry": ( 

35 "lexigram.web.routing.controller_registry", 

36 "ControllerRegistry", 

37 ), 

38 "GenericController": ("lexigram.web.routing.controller", "GenericController"), 

39 "Router": ("lexigram.web.routing.router", "Router"), 

40 "RouteRegistry": ("lexigram.web.routing.registry", "RouteRegistry"), 

41 "VersionExtractor": ("lexigram.web.routing.versioning", "VersionExtractor"), 

42 "ApiVersionMetadata": ("lexigram.web.routing.versioning", "ApiVersionMetadata"), 

43 "api_version": ("lexigram.web.routing.versioning", "api_version"), 

44 # Parameters 

45 "body": ("lexigram.web.routing.parameters", "body"), 

46 "controller": ("lexigram.web.routing.controller_registry", "controller"), 

47 "controller_registry": ( 

48 "lexigram.web.routing.controller_registry", 

49 "controller_registry", 

50 ), 

51 "cookie": ("lexigram.web.routing.parameters", "cookie"), 

52 "file": ("lexigram.web.routing.parameters", "file"), 

53 "form": ("lexigram.web.routing.parameters", "form"), 

54 "get_version": ("lexigram.web.routing.versioning", "get_version"), 

55 "header": ("lexigram.web.routing.parameters", "header"), 

56 "path": ("lexigram.web.routing.parameters", "path"), 

57 "query": ("lexigram.web.routing.parameters", "query"), 

58 "register_controller": ("lexigram.web.routing.registry", "register_controller"), 

59 "route_registry": ("lexigram.web.routing.registry", "route_registry"), 

60 "version": ("lexigram.web.routing.versioning", "version"), 

61 # HTTP Methods (from decorators) 

62 "delete": ("lexigram.web.routing.decorators", "delete"), 

63 "get": ("lexigram.web.routing.decorators", "get"), 

64 "head": ("lexigram.web.routing.decorators", "head"), 

65 "options": ("lexigram.web.routing.decorators", "options"), 

66 "patch": ("lexigram.web.routing.decorators", "patch"), 

67 "post": ("lexigram.web.routing.decorators", "post"), 

68 "put": ("lexigram.web.routing.decorators", "put"), 

69 "trace": ("lexigram.web.routing.decorators", "trace"), 

70 "websocket": ("lexigram.web.routing.decorators", "websocket"), 

71 "websocket_handler": ("lexigram.web.websocket.decorators", "websocket_handler"), 

72 # Transport 

73 "Response": ("lexigram.web.transport.responses", "Response"), 

74 "FastJSONResponse": ("lexigram.web.transport.responses", "FastJSONResponse"), 

75 "HTMXResponse": ("lexigram.web.transport.responses", "HTMXResponse"), 

76 "HTMLContent": ("lexigram.web.transport.responses", "HTMLContent"), 

77 "JSONResponse": ("lexigram.web.transport.responses", "JSONResponse"), 

78 "HTMLResponse": ("lexigram.web.transport.responses", "HTMLResponse"), 

79 "RedirectResponse": ("lexigram.web.transport.responses", "RedirectResponse"), 

80 "StreamingResponse": ("lexigram.web.transport.responses", "StreamingResponse"), 

81 "FileResponse": ("lexigram.web.transport.responses", "FileResponse"), 

82 "WebSocket": ("lexigram.web.transport.websockets", "WebSocket"), 

83 # Template rendering 

84 "render_template": ("lexigram.web.templates.core", "render_template"), 

85 # Response helpers 

86 "json_response": ("lexigram.web.transport.responses", "json_response"), 

87 "html_response": ("lexigram.web.transport.responses", "html_response"), 

88 "redirect_response": ("lexigram.web.transport.responses", "redirect_response"), 

89 "streaming_response": ("lexigram.web.transport.responses", "streaming_response"), 

90 "file_response": ("lexigram.web.transport.responses", "file_response"), 

91 # Middleware 

92 "DefaultMiddlewareStack": ( 

93 "lexigram.web.middleware.stack", 

94 "DefaultMiddlewareStack", 

95 ), 

96 "InputSanitizationMiddleware": ( 

97 "lexigram.web.middleware.sanitization", 

98 "InputSanitizationMiddleware", 

99 ), 

100 # Security 

101 "RoleGuard": ("lexigram.web.security.guards", "RoleGuard"), 

102 "guard": ("lexigram.web.security.shortcuts", "guard"), 

103 "roles": ("lexigram.web.security.shortcuts", "roles"), 

104 # Exceptions 

105 "HTTPError": ("lexigram.web.exceptions", "HTTPError"), 

106 "NotFoundError": ("lexigram.web.exceptions", "NotFoundError"), 

107 "ConflictError": ("lexigram.web.exceptions", "ConflictError"), 

108 "RateLimitError": ("lexigram.web.exceptions", "RateLimitError"), 

109 "TooManyConnectionsError": ("lexigram.web.exceptions", "TooManyConnectionsError"), 

110 "InternalServerError": ("lexigram.web.exceptions", "InternalServerError"), 

111 # Background tasks 

112 "background": ("lexigram.web.background.decorator", "background"), 

113 "BackgroundTasks": ("lexigram.web.background.tasks", "BackgroundTasks"), 

114 # Errors 

115 "ProblemDetail": ("lexigram.web.errors.problem_detail", "ProblemDetail"), 

116 # Request state helpers 

117 "get_request_id": ("lexigram.web.dependencies.functions", "get_request_id"), 

118 "get_current_user_optional": ( 

119 "lexigram.web.dependencies.functions", 

120 "get_current_user_optional", 

121 ), 

122 "get_current_user_required": ( 

123 "lexigram.web.dependencies.functions", 

124 "get_current_user_required", 

125 ), 

126 # Rate limiting sugar 

127 "throttle": ("lexigram.web.integrations.throttle", "throttle"), 

128 "RateLimitModule": ("lexigram.web.integrations.throttle", "RateLimitModule"), 

129 # Quickstart 

130 "app": ("lexigram.web.quickstart", "app"), 

131 # DI decorators — quickstart-aware versions that also register in the 

132 # quickstart service registry so classes defined in any scope are discovered. 

133 "singleton": ("lexigram.web.quickstart", "singleton"), 

134 "injectable": ("lexigram.web.quickstart", "injectable"), 

135 "transient": ("lexigram.di", "transient"), 

136 # Result bridge 

137 "ResultResponseMapper": ( 

138 "lexigram.web.routing.result_bridge", 

139 "ResultResponseMapper", 

140 ), 

141 "error_status": ("lexigram.web.routing.result_bridge", "error_status"), 

142 # Protocols (web-layer pipe + interceptor) 

143 "ParamMetadata": ("lexigram.web.protocols", "ParamMetadata"), 

144 "PipeProtocol": ("lexigram.web.protocols", "PipeProtocol"), 

145 "ExecutionContextProtocol": ("lexigram.web.protocols", "ExecutionContextProtocol"), 

146 "CallHandlerProtocol": ("lexigram.web.protocols", "CallHandlerProtocol"), 

147 "WebInterceptorProtocol": ("lexigram.web.protocols", "WebInterceptorProtocol"), 

148 "WebInterceptorBase": ("lexigram.web.protocols", "WebInterceptorBase"), 

149 # Provider protocols (decoupling) 

150 "WebAppAccessorProtocol": ("lexigram.web.protocols", "WebAppAccessorProtocol"), 

151 "ControllerSourceProtocol": ("lexigram.web.protocols", "ControllerSourceProtocol"), 

152 "ConfigAccessorProtocol": ("lexigram.web.protocols", "ConfigAccessorProtocol"), 

153 "ProviderResourcesProtocol": ( 

154 "lexigram.web.protocols", 

155 "ProviderResourcesProtocol", 

156 ), 

157 "WebProviderProtocol": ("lexigram.web.protocols", "WebProviderProtocol"), 

158 # Hooks 

159 "WebRequestReceivedHook": ("lexigram.web.hooks", "WebRequestReceivedHook"), 

160 "WebResponsePreparedHook": ("lexigram.web.hooks", "WebResponsePreparedHook"), 

161 "WebServerStartedHook": ("lexigram.web.hooks", "WebServerStartedHook"), 

162 "WebServerStoppedHook": ("lexigram.web.hooks", "WebServerStoppedHook"), 

163 # Core re-exports 

164 "Result": ("lexigram.result", "Result"), 

165 "Ok": ("lexigram.result", "Ok"), 

166 "Err": ("lexigram.result", "Err"), 

167} 

168 

169 

170def __getattr__(name: str) -> Any: 

171 """Lazy load attributes to avoid circular imports.""" 

172 if name in _LAZY_IMPORTS: 

173 import importlib 

174 

175 module_path, attr_name = _LAZY_IMPORTS[name] 

176 module = importlib.import_module(module_path) 

177 value = getattr(module, attr_name) 

178 # Cache in module globals so __getattr__ isn't called again for this name 

179 globals()[name] = value 

180 return value 

181 raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 

182 

183 

184def __dir__() -> list[str]: 

185 """List available attributes for IDE support.""" 

186 return list(__all__) + list(_LAZY_IMPORTS.keys()) 

187 

188 

189__all__ = list(_LAZY_IMPORTS)