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

42 statements  

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

1"""Configuration protocol for the Lexigram framework. 

2 

3Defines the runtime-checkable protocol that all configuration implementations 

4must satisfy. This enables DI-based config access without coupling to concrete 

5config classes (Pydantic, DomainModel, etc.). 

6""" 

7 

8from __future__ import annotations 

9 

10from dataclasses import dataclass 

11from enum import StrEnum 

12import os 

13from typing import Any, Protocol, TypeVar, runtime_checkable 

14 

15T = TypeVar("T") 

16 

17 

18class Environment(StrEnum): 

19 """Deployment environment discriminator. 

20 

21 Use ``Environment.from_env()`` to read the current environment from the 

22 ``LEX_ENV`` (or ``APP_ENV``) environment variable. 

23 """ 

24 

25 DEVELOPMENT = "development" 

26 STAGING = "staging" 

27 PRODUCTION = "production" 

28 TEST = "test" 

29 

30 @classmethod 

31 def from_env(cls, default: Environment = "development") -> Environment: # type: ignore[assignment] 

32 """Read the active environment from ``LEX_ENV`` or ``APP_ENV``. 

33 

34 Args: 

35 default: Fallback when neither variable is set. 

36 

37 Returns: 

38 The resolved ``Environment`` member. 

39 """ 

40 raw = os.environ.get("LEX_ENV") or os.environ.get("APP_ENV") or default 

41 try: 

42 return cls(raw.lower()) 

43 except ValueError: 

44 return cls(default) 

45 

46 

47@dataclass(frozen=True) 

48class ConfigIssue: 

49 """A single configuration validation issue. 

50 

51 Attributes: 

52 field: Dot-notation path to the offending field (e.g. ``"db.url"``). 

53 message: Human-readable description of the problem. 

54 severity: ``"error"`` blocks startup; ``"warning"`` is informational. 

55 suggestion: Optional remediation hint shown to operators. 

56 """ 

57 

58 field: str 

59 message: str 

60 severity: str = "error" 

61 suggestion: str = "" 

62 

63 

64@runtime_checkable 

65class ConfigProtocol(Protocol): 

66 """Protocol for configuration access across the framework. 

67 

68 Any configuration object (BaseConfig, LexigramConfig, or custom 

69 implementations) can satisfy this protocol by implementing ``get()``, 

70 ``get_section()``, and ``has_section()``. 

71 

72 Example:: 

73 

74 config = container.resolve(ConfigProtocol) 

75 db_url = config.get("database.url", "sqlite:///default.db") 

76 logging = config.get_section("logging", LoggingConfig) 

77 """ 

78 

79 @property 

80 def environment(self) -> Environment: 

81 """The active deployment environment.""" 

82 ... 

83 

84 @property 

85 def is_production(self) -> bool: 

86 """Return ``True`` when the active environment is production.""" 

87 ... 

88 

89 @property 

90 def is_development(self) -> bool: 

91 """Return ``True`` when the active environment is development.""" 

92 ... 

93 

94 @property 

95 def is_testing(self) -> bool: 

96 """Return ``True`` when the active environment is testing.""" 

97 ... 

98 

99 @property 

100 def is_staging(self) -> bool: 

101 """Return ``True`` when the active environment is staging.""" 

102 ... 

103 

104 @property 

105 def is_debug(self) -> bool: 

106 """Return ``True`` when debug mode is enabled.""" 

107 ... 

108 

109 def get(self, key: str, default: Any = None) -> Any: 

110 """Get a configuration value by dot-notation key. 

111 

112 Args: 

113 key: Configuration key (e.g. ``"app.name"`` or ``"database.url"``). 

114 default: Value returned when the key is not found. 

115 

116 Returns: 

117 The configuration value, or *default* if not found. 

118 """ 

119 ... 

120 

121 def get_section( 

122 self, 

123 name: str, 

124 model_cls: type[T] | None = None, 

125 ) -> T | dict[str, Any]: 

126 """Get a typed configuration section. 

127 

128 Args: 

129 name: Section name (e.g. ``"logging"``). 

130 model_cls: Optional model class to coerce the section into. 

131 

132 Returns: 

133 A model instance when *model_cls* is provided, otherwise a raw 

134 dict or the attribute value. 

135 """ 

136 ... 

137 

138 def has_section(self, name: str) -> bool: 

139 """Check whether a configuration section exists. 

140 

141 Args: 

142 name: Section name to check. 

143 

144 Returns: 

145 True if the section is present. 

146 """ 

147 ... 

148 

149 

150__all__ = ["ConfigIssue", "ConfigProtocol", "Environment"]