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
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-12 21:18 +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:
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:
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 }
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
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 }
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 }
293# =============================================================================
294# Main Detection Functions
295# =============================================================================
297# Cache the features after first detection
298_cached_features: PythonFeatures | None = None
301def get_features(*, force_refresh: bool = False) -> PythonFeatures:
302 """Detect and return available Python features.
304 Features are cached after first detection for performance.
306 Args:
307 force_refresh: If True, re-detect features instead of using cache.
309 Returns:
310 PythonFeatures dataclass with all detected features.
312 """
313 global _cached_features # noqa: PLW0603 - intentional cache pattern
315 if _cached_features is not None and not force_refresh:
316 return _cached_features
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()
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 )
332 _cached_features = features
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 )
343 return features
346def get_python_info() -> dict[str, object]:
347 """Get comprehensive Python runtime information.
349 Returns:
350 Dictionary with version, platform, and feature information.
352 """
353 import platform # noqa: PLC0415 - lazy import for optional module
355 features = get_features()
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 }