Coverage for src / taipanstack / core / compat.py: 100%

120 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-12 21:18 +0000

1""" 

2Python Version Compatibility and Feature Detection. 

3 

4This module provides runtime detection of Python version and available 

5performance features, enabling version-specific optimizations while 

6maintaining compatibility with Python 3.11+. 

7 

8Following Stack pillars: Security, Stability, Simplicity, Scalability, Compatibility. 

9""" 

10 

11import logging 

12import os 

13import sys 

14from dataclasses import dataclass 

15from enum import StrEnum 

16from typing import Final 

17 

18__all__ = [ 

19 "PY311", 

20 "PY312", 

21 "PY313", 

22 "PY314", 

23 "PY_VERSION", 

24 "PythonFeatures", 

25 "VersionTier", 

26 "get_features", 

27 "get_python_info", 

28 "is_experimental_enabled", 

29] 

30 

31logger = logging.getLogger(__name__) 

32 

33# ============================================================================= 

34# Version Constants 

35# ============================================================================= 

36 

37PY_VERSION: Final = sys.version_info 

38"""Current Python version tuple.""" 

39 

40PY311: Final[bool] = PY_VERSION >= (3, 11) 

41"""True if running Python 3.11 or higher.""" 

42 

43PY312: Final[bool] = PY_VERSION >= (3, 12) 

44"""True if running Python 3.12 or higher.""" 

45 

46PY313: Final[bool] = PY_VERSION >= (3, 13) 

47"""True if running Python 3.13 or higher.""" 

48 

49PY314: Final[bool] = PY_VERSION >= (3, 14) 

50"""True if running Python 3.14 or higher.""" 

51 

52 

53class VersionTier(StrEnum): 

54 """Python version tier for optimization profiles.""" 

55 

56 STABLE = "stable" # 3.11 - fully stable, conservative optimizations 

57 ENHANCED = "enhanced" # 3.12 - improved features, moderate optimizations 

58 MODERN = "modern" # 3.13 - JIT/free-threading available (experimental) 

59 CUTTING_EDGE = "cutting_edge" # 3.14+ - latest optimizations 

60 

61 

62# ============================================================================= 

63# Environment Variables for Experimental Features 

64# ============================================================================= 

65 

66ENV_ENABLE_EXPERIMENTAL = "STACK_ENABLE_EXPERIMENTAL" 

67"""Environment variable to enable experimental features (JIT, free-threading).""" 

68 

69ENV_OPTIMIZATION_LEVEL = "STACK_OPTIMIZATION_LEVEL" 

70"""Optimization level: 0=none, 1=safe, 2=aggressive (requires experimental).""" 

71 

72 

73# ============================================================================= 

74# Feature Detection Functions 

75# ============================================================================= 

76 

77 

78def _check_jit_available() -> bool: 

79 """Check if JIT compiler is available and enabled. 

80 

81 JIT is available in Python 3.13+ when built with --enable-experimental-jit. 

82 """ 

83 if not PY313: 

84 return False 

85 

86 try: 

87 # Check if the JIT module exists (Python 3.13+) 

88 # This is a build-time option, not all builds have it 

89 return hasattr(sys, "_jit") or "jit" in sys.flags.__class__.__annotations__ 

90 except (AttributeError, TypeError): 

91 return False 

92 

93 

94def _check_free_threading_available() -> bool: 

95 """Check if free-threading (no-GIL) build is being used. 

96 

97 Free-threading is available in Python 3.13+ experimental builds. 

98 """ 

99 if not PY313: 

100 return False 

101 

102 try: 

103 # In free-threaded builds, sys.flags.nogil is True 

104 # or the build was configured with --disable-gil 

105 if hasattr(sys.flags, "nogil"): 

106 return bool(sys.flags.nogil) 

107 

108 # Alternative check for 3.13+ 

109 import sysconfig # noqa: PLC0415 

110 

111 config_args = sysconfig.get_config_var("CONFIG_ARGS") or "" 

112 except (AttributeError, TypeError): 

113 return False 

114 else: 

115 return "--disable-gil" in config_args 

116 

117 

118def _check_mimalloc_available() -> bool: 

119 """Check if mimalloc allocator is being used. 

120 

121 mimalloc is the default allocator in Python 3.13+ on some platforms. 

122 """ 

123 if not PY313: 

124 return False 

125 

126 try: 

127 import sysconfig # noqa: PLC0415 - lazy import for optional module 

128 

129 # Check if built with mimalloc 

130 config_args = sysconfig.get_config_var("CONFIG_ARGS") or "" 

131 return "mimalloc" in config_args.lower() 

132 except (AttributeError, TypeError): 

133 return False 

134 

135 

136def _check_tail_call_interpreter() -> bool: 

137 """Check if tail-call interpreter optimization is available. 

138 

139 Tail-call interpreter is available in Python 3.14+. 

140 """ 

141 return PY314 

142 

143 

144_cached_experimental_enabled: bool | None = None 

145 

146 

147def is_experimental_enabled(*, force_refresh: bool = False) -> bool: 

148 """Check if experimental features are explicitly enabled. 

149 

150 Features are cached after first detection for performance. 

151 

152 Args: 

153 force_refresh: If True, re-detect instead of using cache. 

154 

155 Returns: 

156 True if STACK_ENABLE_EXPERIMENTAL=1 is set. 

157 

158 """ 

159 global _cached_experimental_enabled # noqa: PLW0603 

160 

161 if _cached_experimental_enabled is not None and not force_refresh: 

162 return _cached_experimental_enabled 

163 

164 value = os.environ.get(ENV_ENABLE_EXPERIMENTAL, "").lower() 

165 _cached_experimental_enabled = value in {"1", "true", "yes", "on"} 

166 return _cached_experimental_enabled 

167 

168 

169_cached_optimization_level: int | None = None 

170 

171 

172def get_optimization_level(*, force_refresh: bool = False) -> int: 

173 """Get the configured optimization level. 

174 

175 Features are cached after first detection for performance. 

176 

177 Args: 

178 force_refresh: If True, re-detect instead of using cache. 

179 

180 Returns: 

181 0 = No optimizations 

182 1 = Safe optimizations only (default) 

183 2 = Aggressive optimizations (requires experimental) 

184 

185 """ 

186 global _cached_optimization_level # noqa: PLW0603 

187 

188 if _cached_optimization_level is not None and not force_refresh: 

189 return _cached_optimization_level 

190 

191 try: 

192 level = int(os.environ.get(ENV_OPTIMIZATION_LEVEL, "1")) 

193 _cached_optimization_level = max(0, min(2, level)) # Clamp to 0-2 

194 except ValueError: 

195 _cached_optimization_level = 1 

196 

197 return _cached_optimization_level 

198 

199 

200# ============================================================================= 

201# Feature Data Classes 

202# ============================================================================= 

203 

204 

205@dataclass(frozen=True, slots=True) 

206class PythonFeatures: 

207 """Available Python features based on version and build configuration. 

208 

209 This dataclass is immutable and optimized for performance with slots. 

210 """ 

211 

212 version: tuple[int, int, int] 

213 version_string: str 

214 tier: VersionTier 

215 

216 # Build features 

217 has_jit: bool = False 

218 has_free_threading: bool = False 

219 has_mimalloc: bool = False 

220 has_tail_call_interpreter: bool = False 

221 

222 # Language features by version 

223 has_exception_groups: bool = False # 3.11+ 

224 has_self_type: bool = False # 3.11+ 

225 has_type_params: bool = False # 3.12+ 

226 has_fstring_improvements: bool = False # 3.12+ 

227 has_override_decorator: bool = False # 3.12+ 

228 has_deprecated_decorator: bool = False # 3.13+ 

229 has_deferred_annotations: bool = False # 3.14+ 

230 

231 # Experimental features enabled 

232 experimental_enabled: bool = False 

233 

234 def to_dict(self) -> dict[str, object]: 

235 """Convert to dictionary for serialization.""" 

236 return { 

237 "version": ".".join(map(str, self.version)), 

238 "tier": self.tier.value, 

239 "features": { 

240 "jit": self.has_jit, 

241 "free_threading": self.has_free_threading, 

242 "mimalloc": self.has_mimalloc, 

243 "tail_call_interpreter": self.has_tail_call_interpreter, 

244 }, 

245 "language": { 

246 "exception_groups": self.has_exception_groups, 

247 "self_type": self.has_self_type, 

248 "type_params": self.has_type_params, 

249 "override_decorator": self.has_override_decorator, 

250 "deprecated_decorator": self.has_deprecated_decorator, 

251 "deferred_annotations": self.has_deferred_annotations, 

252 }, 

253 "experimental_enabled": self.experimental_enabled, 

254 } 

255 

256 

257def _get_version_tier() -> VersionTier: 

258 """Determine the version tier based on Python version.""" 

259 if PY314: 

260 return VersionTier.CUTTING_EDGE 

261 if PY313: 

262 return VersionTier.MODERN 

263 if PY312: 

264 return VersionTier.ENHANCED 

265 return VersionTier.STABLE 

266 

267 

268def _get_build_features(experimental: bool) -> dict[str, bool]: 

269 """Determine build features based on experimental flag.""" 

270 return { 

271 "has_jit": _check_jit_available() if experimental else False, 

272 "has_free_threading": _check_free_threading_available() 

273 if experimental 

274 else False, 

275 "has_mimalloc": _check_mimalloc_available(), 

276 "has_tail_call_interpreter": _check_tail_call_interpreter(), 

277 } 

278 

279 

280def _get_language_features() -> dict[str, bool]: 

281 """Determine language features based on Python version.""" 

282 return { 

283 "has_exception_groups": PY311, 

284 "has_self_type": PY311, 

285 "has_type_params": PY312, 

286 "has_fstring_improvements": PY312, 

287 "has_override_decorator": PY312, 

288 "has_deprecated_decorator": PY313, 

289 "has_deferred_annotations": PY314, 

290 } 

291 

292 

293# ============================================================================= 

294# Main Detection Functions 

295# ============================================================================= 

296 

297# Cache the features after first detection 

298_cached_features: PythonFeatures | None = None 

299 

300 

301def get_features(*, force_refresh: bool = False) -> PythonFeatures: 

302 """Detect and return available Python features. 

303 

304 Features are cached after first detection for performance. 

305 

306 Args: 

307 force_refresh: If True, re-detect features instead of using cache. 

308 

309 Returns: 

310 PythonFeatures dataclass with all detected features. 

311 

312 """ 

313 global _cached_features # noqa: PLW0603 - intentional cache pattern 

314 

315 if _cached_features is not None and not force_refresh: 

316 return _cached_features 

317 

318 tier = _get_version_tier() 

319 experimental = is_experimental_enabled(force_refresh=force_refresh) 

320 build_feats = _get_build_features(experimental) 

321 lang_feats = _get_language_features() 

322 

323 features = PythonFeatures( 

324 version=(PY_VERSION.major, PY_VERSION.minor, PY_VERSION.micro), 

325 version_string=f"{PY_VERSION.major}.{PY_VERSION.minor}.{PY_VERSION.micro}", 

326 tier=tier, 

327 **build_feats, 

328 **lang_feats, 

329 experimental_enabled=experimental, 

330 ) 

331 

332 _cached_features = features 

333 

334 # Log detected features at DEBUG level 

335 logger.debug( 

336 "Python %s detected (tier=%s, experimental=%s): %r", 

337 features.version_string, 

338 tier.value, 

339 experimental, 

340 features, 

341 ) 

342 

343 return features 

344 

345 

346def get_python_info() -> dict[str, object]: 

347 """Get comprehensive Python runtime information. 

348 

349 Returns: 

350 Dictionary with version, platform, and feature information. 

351 

352 """ 

353 import platform # noqa: PLC0415 - lazy import for optional module 

354 

355 features = get_features() 

356 

357 return { 

358 "version": features.version_string, 

359 "version_tuple": features.version, 

360 "tier": features.tier.value, 

361 "implementation": platform.python_implementation(), 

362 "platform": platform.platform(), 

363 "compiler": platform.python_compiler(), 

364 "features": features.to_dict(), 

365 "optimization_level": get_optimization_level(), 

366 }