Coverage for src/taipanstack/core/compat.py: 100%
118 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 15:01 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 15:01 +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 platform
14import sys
15import sysconfig
16from dataclasses import dataclass
17from enum import StrEnum
18from typing import Final
20__all__ = [
21 "PY311",
22 "PY312",
23 "PY313",
24 "PY314",
25 "PY_VERSION",
26 "PythonFeatures",
27 "VersionTier",
28 "get_features",
29 "get_python_info",
30 "is_experimental_enabled",
31]
33logger = logging.getLogger(__name__)
35# =============================================================================
36# Version Constants
37# =============================================================================
39PY_VERSION: Final = sys.version_info
40"""Current Python version tuple."""
42PY311: Final[bool] = PY_VERSION >= (3, 11)
43"""True if running Python 3.11 or higher."""
45PY312: Final[bool] = PY_VERSION >= (3, 12)
46"""True if running Python 3.12 or higher."""
48PY313: Final[bool] = PY_VERSION >= (3, 13)
49"""True if running Python 3.13 or higher."""
51PY314: Final[bool] = PY_VERSION >= (3, 14)
52"""True if running Python 3.14 or higher."""
55class VersionTier(StrEnum):
56 """Python version tier for optimization profiles."""
58 STABLE = "stable" # 3.11 - fully stable, conservative optimizations
59 ENHANCED = "enhanced" # 3.12 - improved features, moderate optimizations
60 MODERN = "modern" # 3.13 - JIT/free-threading available (experimental)
61 CUTTING_EDGE = "cutting_edge" # 3.14+ - latest optimizations
64# =============================================================================
65# Environment Variables for Experimental Features
66# =============================================================================
68ENV_ENABLE_EXPERIMENTAL = "STACK_ENABLE_EXPERIMENTAL"
69"""Environment variable to enable experimental features (JIT, free-threading)."""
71ENV_OPTIMIZATION_LEVEL = "STACK_OPTIMIZATION_LEVEL"
72"""Optimization level: 0=none, 1=safe, 2=aggressive (requires experimental)."""
75# =============================================================================
76# Feature Detection Functions
77# =============================================================================
80def _check_jit_available() -> bool:
81 """Check if JIT compiler is available and enabled.
83 JIT is available in Python 3.13+ when built with --enable-experimental-jit.
84 """
85 if not PY313:
86 return False
88 try:
89 # Check if the JIT module exists (Python 3.13+)
90 # This is a build-time option, not all builds have it
91 return hasattr(sys, "_jit") or "jit" in sys.flags.__class__.__annotations__
92 except (AttributeError, TypeError):
93 return False
96def _check_free_threading_available() -> bool:
97 """Check if free-threading (no-GIL) build is being used.
99 Free-threading is available in Python 3.13+ experimental builds.
100 """
101 if not PY313:
102 return False
104 try:
105 # In free-threaded builds, sys.flags.nogil is True
106 # or the build was configured with --disable-gil
107 if hasattr(sys.flags, "nogil"):
108 return bool(sys.flags.nogil)
110 # Alternative check for 3.13+
112 config_args = sysconfig.get_config_var("CONFIG_ARGS") or ""
113 except (AttributeError, TypeError):
114 return False
115 else:
116 return "--disable-gil" in config_args
119def _check_mimalloc_available() -> bool:
120 """Check if mimalloc allocator is being used.
122 mimalloc is the default allocator in Python 3.13+ on some platforms.
123 """
124 if not PY313:
125 return False
127 try:
128 # Check if built with mimalloc
129 config_args = sysconfig.get_config_var("CONFIG_ARGS") or ""
130 return "mimalloc" in config_args.lower()
131 except (AttributeError, TypeError):
132 return False
135def _check_tail_call_interpreter() -> bool:
136 """Check if tail-call interpreter optimization is available.
138 Tail-call interpreter is available in Python 3.14+.
139 """
140 return PY314
143_cached_experimental_enabled: bool | None = None
146def is_experimental_enabled(*, force_refresh: bool = False) -> bool:
147 """Check if experimental features are explicitly enabled.
149 Features are cached after first detection for performance.
151 Args:
152 force_refresh: If True, re-detect instead of using cache.
154 Returns:
155 True if STACK_ENABLE_EXPERIMENTAL=1 is set.
157 """
158 global _cached_experimental_enabled # noqa: PLW0603
160 if _cached_experimental_enabled is not None and not force_refresh:
161 return _cached_experimental_enabled
163 value = os.environ.get(ENV_ENABLE_EXPERIMENTAL, "").lower()
164 _cached_experimental_enabled = value in {"1", "true", "yes", "on"}
165 return _cached_experimental_enabled
168_cached_optimization_level: int | None = None
171def get_optimization_level(*, force_refresh: bool = False) -> int:
172 """Get the configured optimization level.
174 Features are cached after first detection for performance.
176 Args:
177 force_refresh: If True, re-detect instead of using cache.
179 Returns:
180 0 = No optimizations
181 1 = Safe optimizations only (default)
182 2 = Aggressive optimizations (requires experimental)
184 """
185 global _cached_optimization_level # noqa: PLW0603
187 if _cached_optimization_level is not None and not force_refresh:
188 return _cached_optimization_level
190 try:
191 level = int(os.environ.get(ENV_OPTIMIZATION_LEVEL, "1"))
192 _cached_optimization_level = max(0, min(2, level)) # Clamp to 0-2
193 except ValueError:
194 _cached_optimization_level = 1
196 return _cached_optimization_level
199# =============================================================================
200# Feature Data Classes
201# =============================================================================
204@dataclass(frozen=True, slots=True)
205class PythonFeatures:
206 """Available Python features based on version and build configuration.
208 This dataclass is immutable and optimized for performance with slots.
209 """
211 version: tuple[int, int, int]
212 version_string: str
213 tier: VersionTier
215 # Build features
216 has_jit: bool = False
217 has_free_threading: bool = False
218 has_mimalloc: bool = False
219 has_tail_call_interpreter: bool = False
221 # Language features by version
222 has_exception_groups: bool = False # 3.11+
223 has_self_type: bool = False # 3.11+
224 has_type_params: bool = False # 3.12+
225 has_override_decorator: bool = False # 3.12+
226 has_deprecated_decorator: bool = False # 3.13+
227 has_deferred_annotations: bool = False # 3.14+
229 # Experimental features enabled
230 experimental_enabled: bool = False
232 def to_dict(self) -> dict[str, object]:
233 """Convert to dictionary for serialization."""
234 return {
235 "version": ".".join(map(str, self.version)),
236 "tier": self.tier.value,
237 "features": {
238 "jit": self.has_jit,
239 "free_threading": self.has_free_threading,
240 "mimalloc": self.has_mimalloc,
241 "tail_call_interpreter": self.has_tail_call_interpreter,
242 },
243 "language": {
244 "exception_groups": self.has_exception_groups,
245 "self_type": self.has_self_type,
246 "type_params": self.has_type_params,
247 "override_decorator": self.has_override_decorator,
248 "deprecated_decorator": self.has_deprecated_decorator,
249 "deferred_annotations": self.has_deferred_annotations,
250 },
251 "experimental_enabled": self.experimental_enabled,
252 }
255def _get_version_tier() -> VersionTier:
256 """Determine the version tier based on Python version."""
257 if PY314:
258 return VersionTier.CUTTING_EDGE
259 if PY313:
260 return VersionTier.MODERN
261 if PY312:
262 return VersionTier.ENHANCED
263 return VersionTier.STABLE
266def _get_build_features(experimental: bool) -> dict[str, bool]:
267 """Determine build features based on experimental flag."""
268 return {
269 "has_jit": _check_jit_available() if experimental else False,
270 "has_free_threading": _check_free_threading_available()
271 if experimental
272 else False,
273 "has_mimalloc": _check_mimalloc_available(),
274 "has_tail_call_interpreter": _check_tail_call_interpreter(),
275 }
278def _get_language_features() -> dict[str, bool]:
279 """Determine language features based on Python version."""
280 return {
281 "has_exception_groups": PY311,
282 "has_self_type": PY311,
283 "has_type_params": PY312,
284 "has_override_decorator": PY312,
285 "has_deprecated_decorator": PY313,
286 "has_deferred_annotations": PY314,
287 }
290# =============================================================================
291# Main Detection Functions
292# =============================================================================
294# Cache the features after first detection
295_cached_features: PythonFeatures | None = None
298def get_features(*, force_refresh: bool = False) -> PythonFeatures:
299 """Detect and return available Python features.
301 Features are cached after first detection for performance.
303 Args:
304 force_refresh: If True, re-detect features instead of using cache.
306 Returns:
307 PythonFeatures dataclass with all detected features.
309 """
310 global _cached_features # noqa: PLW0603 - intentional cache pattern
312 if _cached_features is not None and not force_refresh:
313 return _cached_features
315 tier = _get_version_tier()
316 experimental = is_experimental_enabled(force_refresh=force_refresh)
317 build_feats = _get_build_features(experimental)
318 lang_feats = _get_language_features()
320 features = PythonFeatures(
321 version=(PY_VERSION.major, PY_VERSION.minor, PY_VERSION.micro),
322 version_string=f"{PY_VERSION.major}.{PY_VERSION.minor}.{PY_VERSION.micro}",
323 tier=tier,
324 **build_feats,
325 **lang_feats,
326 experimental_enabled=experimental,
327 )
329 _cached_features = features
331 # Log detected features at DEBUG level
332 logger.debug(
333 "Python %s detected (tier=%s, experimental=%s): %r",
334 features.version_string,
335 tier.value,
336 experimental,
337 features,
338 )
340 return features
343def get_python_info() -> dict[str, object]:
344 """Get comprehensive Python runtime information.
346 Returns:
347 Dictionary with version, platform, and feature information.
349 """
350 features = get_features()
352 return {
353 "version": features.version_string,
354 "version_tuple": features.version,
355 "tier": features.tier.value,
356 "implementation": platform.python_implementation(),
357 "platform": platform.platform(),
358 "compiler": platform.python_compiler(),
359 "features": features.to_dict(),
360 "optimization_level": get_optimization_level(),
361 }