Coverage for src / taipanstack / core / compat.py: 100%
111 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-23 14:54 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-23 14:54 +0000
1"""
2Python Version Compatibility and Feature Detection.
4This module provides runtime detection of Python version and available
5performance features, enabling version-specific optimizations while
6maintaining compatibility with Python 3.11+.
8Following Stack pillars: Security, Stability, Simplicity, Scalability, Compatibility.
9"""
11import logging
12import os
13import sys
14from dataclasses import dataclass
15from enum import StrEnum
16from typing import Final
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]
31logger = logging.getLogger(__name__)
33# =============================================================================
34# Version Constants
35# =============================================================================
37PY_VERSION: Final = sys.version_info
38"""Current Python version tuple."""
40PY311: Final[bool] = PY_VERSION >= (3, 11)
41"""True if running Python 3.11 or higher."""
43PY312: Final[bool] = PY_VERSION >= (3, 12)
44"""True if running Python 3.12 or higher."""
46PY313: Final[bool] = PY_VERSION >= (3, 13)
47"""True if running Python 3.13 or higher."""
49PY314: Final[bool] = PY_VERSION >= (3, 14)
50"""True if running Python 3.14 or higher."""
53class VersionTier(StrEnum):
54 """Python version tier for optimization profiles."""
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
62# =============================================================================
63# Environment Variables for Experimental Features
64# =============================================================================
66ENV_ENABLE_EXPERIMENTAL = "STACK_ENABLE_EXPERIMENTAL"
67"""Environment variable to enable experimental features (JIT, free-threading)."""
69ENV_OPTIMIZATION_LEVEL = "STACK_OPTIMIZATION_LEVEL"
70"""Optimization level: 0=none, 1=safe, 2=aggressive (requires experimental)."""
73# =============================================================================
74# Feature Detection Functions
75# =============================================================================
78def _check_jit_available() -> bool:
79 """Check if JIT compiler is available and enabled.
81 JIT is available in Python 3.13+ when built with --enable-experimental-jit.
82 """
83 if not PY313: # pragma: no cover
84 return False
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
94def _check_free_threading_available() -> bool:
95 """Check if free-threading (no-GIL) build is being used.
97 Free-threading is available in Python 3.13+ experimental builds.
98 """
99 if not PY313: # pragma: no cover
100 return False
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)
108 # Alternative check for 3.13+
109 import sysconfig # noqa: PLC0415
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
118def _check_mimalloc_available() -> bool:
119 """Check if mimalloc allocator is being used.
121 mimalloc is the default allocator in Python 3.13+ on some platforms.
122 """
123 if not PY313:
124 return False
126 try:
127 import sysconfig # noqa: PLC0415 - lazy import for optional module
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
136def _check_tail_call_interpreter() -> bool:
137 """Check if tail-call interpreter optimization is available.
139 Tail-call interpreter is available in Python 3.14+.
140 """
141 return PY314
144_cached_experimental_enabled: bool | None = None
147def is_experimental_enabled(*, force_refresh: bool = False) -> bool:
148 """Check if experimental features are explicitly enabled.
150 Features are cached after first detection for performance.
152 Args:
153 force_refresh: If True, re-detect instead of using cache.
155 Returns:
156 True if STACK_ENABLE_EXPERIMENTAL=1 is set.
158 """
159 global _cached_experimental_enabled # noqa: PLW0603
161 if _cached_experimental_enabled is not None and not force_refresh:
162 return _cached_experimental_enabled
164 value = os.environ.get(ENV_ENABLE_EXPERIMENTAL, "").lower()
165 _cached_experimental_enabled = value in {"1", "true", "yes", "on"}
166 return _cached_experimental_enabled
169_cached_optimization_level: int | None = None
172def get_optimization_level(*, force_refresh: bool = False) -> int:
173 """Get the configured optimization level.
175 Features are cached after first detection for performance.
177 Args:
178 force_refresh: If True, re-detect instead of using cache.
180 Returns:
181 0 = No optimizations
182 1 = Safe optimizations only (default)
183 2 = Aggressive optimizations (requires experimental)
185 """
186 global _cached_optimization_level # noqa: PLW0603
188 if _cached_optimization_level is not None and not force_refresh:
189 return _cached_optimization_level
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
197 return _cached_optimization_level
200# =============================================================================
201# Feature Data Classes
202# =============================================================================
205@dataclass(frozen=True, slots=True)
206class PythonFeatures:
207 """Available Python features based on version and build configuration.
209 This dataclass is immutable and optimized for performance with slots.
210 """
212 version: tuple[int, int, int]
213 version_string: str
214 tier: VersionTier
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
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+
231 # Experimental features enabled
232 experimental_enabled: bool = False
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 }
257# =============================================================================
258# Main Detection Functions
259# =============================================================================
261# Cache the features after first detection
262_cached_features: PythonFeatures | None = None
265def get_features(*, force_refresh: bool = False) -> PythonFeatures:
266 """Detect and return available Python features.
268 Features are cached after first detection for performance.
270 Args:
271 force_refresh: If True, re-detect features instead of using cache.
273 Returns:
274 PythonFeatures dataclass with all detected features.
276 """
277 global _cached_features # noqa: PLW0603 - intentional cache pattern
279 if _cached_features is not None and not force_refresh:
280 return _cached_features
282 # Determine version tier
283 if PY314:
284 tier = VersionTier.CUTTING_EDGE
285 elif PY313:
286 tier = VersionTier.MODERN
287 elif PY312:
288 tier = VersionTier.ENHANCED
289 else:
290 tier = VersionTier.STABLE
292 experimental = is_experimental_enabled(force_refresh=force_refresh)
294 # Build features (only if experimental is enabled for safety)
295 has_jit = _check_jit_available() if experimental else False
296 has_free_threading = _check_free_threading_available() if experimental else False
297 has_mimalloc = _check_mimalloc_available() # Safe to detect
299 features = PythonFeatures(
300 version=(PY_VERSION.major, PY_VERSION.minor, PY_VERSION.micro),
301 version_string=f"{PY_VERSION.major}.{PY_VERSION.minor}.{PY_VERSION.micro}",
302 tier=tier,
303 # Build features
304 has_jit=has_jit,
305 has_free_threading=has_free_threading,
306 has_mimalloc=has_mimalloc,
307 has_tail_call_interpreter=_check_tail_call_interpreter(),
308 # Language features
309 has_exception_groups=PY311,
310 has_self_type=PY311,
311 has_type_params=PY312,
312 has_fstring_improvements=PY312,
313 has_override_decorator=PY312,
314 has_deprecated_decorator=PY313,
315 has_deferred_annotations=PY314,
316 # Experimental
317 experimental_enabled=experimental,
318 )
320 _cached_features = features
322 # Log detected features at DEBUG level
323 logger.debug(
324 "Python %s detected (tier=%s, experimental=%s): %r",
325 features.version_string,
326 tier.value,
327 experimental,
328 features,
329 )
331 return features
334def get_python_info() -> dict[str, object]:
335 """Get comprehensive Python runtime information.
337 Returns:
338 Dictionary with version, platform, and feature information.
340 """
341 import platform # noqa: PLC0415 - lazy import for optional module
343 features = get_features()
345 return {
346 "version": features.version_string,
347 "version_tuple": features.version,
348 "tier": features.tier.value,
349 "implementation": platform.python_implementation(),
350 "platform": platform.platform(),
351 "compiler": platform.python_compiler(),
352 "features": features.to_dict(),
353 "optimization_level": get_optimization_level(),
354 }