Coverage for src/lexigram/web/module.py: 39%
33 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:37 +0800
1"""Web module for dependency injection."""
3from __future__ import annotations
5from typing import Any
7from lexigram.contracts.web import WebRateLimiterProtocol
8from lexigram.di.module import DynamicModule, Module, module
9from lexigram.web.config import ServerConfig, WebConfig # ServerConfig used in stub()
10from lexigram.web.di.provider import WebProvider
13@module()
14class WebModule(Module):
15 """Web module with HTTP provider."""
17 @classmethod
18 def configure(
19 cls,
20 controllers: list[type] | None = None,
21 discover: list[str] | tuple[str, ...] | None = None,
22 host: str | None = None,
23 port: int | None = None,
24 **kwargs: Any,
25 ) -> DynamicModule:
26 """Create a WebModule with explicit configuration.
28 Configuration (CORS, CSRF, server host/port, etc.) is loaded from the
29 ``[web]`` section of ``application.yaml`` by the orchestrator and injected
30 into the provider at registration time. Pass a ``web_config`` kwarg
31 only if you need to bypass YAML entirely.
33 Args:
34 controllers: Controller classes to register with the web server.
35 discover: Package paths to scan for controllers and merge with
36 any explicitly provided controller classes.
37 host: Override the server host (builds a ``WebConfig`` internally).
38 port: Override the server port (builds a ``WebConfig`` internally).
39 **kwargs: Additional keyword arguments forwarded to
40 :class:`~lexigram.web.di.provider.WebProvider`.
41 """
42 resolved_controllers = list(controllers or [])
43 if discover:
44 from lexigram.web.routing.discovery import discover_controllers
46 for controller in discover_controllers(list(discover)):
47 if controller not in resolved_controllers:
48 resolved_controllers.append(controller)
50 if (host is not None or port is not None) and "web_config" not in kwargs:
51 server_kwargs: dict[str, Any] = {}
52 if host is not None:
53 server_kwargs["host"] = host
54 if port is not None:
55 server_kwargs["port"] = port
56 kwargs["web_config"] = WebConfig(server=ServerConfig(**server_kwargs))
58 from lexigram.web.admin.contributor import WebAdminContributor
59 from lexigram.web.admin.handlers.active_connections import (
60 ActiveConnectionsWidgetHandler,
61 )
62 from lexigram.web.admin.handlers.request_rate import RequestRateWidgetHandler
63 from lexigram.web.admin.handlers.server_status import ServerStatusWidgetHandler
65 return DynamicModule(
66 module=cls,
67 providers=[WebProvider(controllers=resolved_controllers, **kwargs)],
68 is_global=True,
69 exports=[
70 WebProvider,
71 WebRateLimiterProtocol,
72 WebAdminContributor,
73 ServerStatusWidgetHandler,
74 ActiveConnectionsWidgetHandler,
75 RequestRateWidgetHandler,
76 ],
77 )
79 @classmethod
80 def stub(cls, config: Any = None) -> DynamicModule:
81 """Return a no-op WebModule suitable for unit testing.
83 Registers WebProvider with no controllers and test-safe
84 configuration. No real HTTP server is started.
86 Args:
87 config: Optional test configuration override.
89 Returns:
90 A DynamicModule with in-memory web configuration.
91 """
92 web_config = WebConfig(server=ServerConfig(host="127.0.0.1", port=8000))
93 return DynamicModule(
94 module=cls,
95 providers=[WebProvider(web_config=web_config, controllers=[])],
96 is_global=True,
97 exports=[WebProvider, WebRateLimiterProtocol],
98 )
101__all__ = ["WebModule"]