Source code for ralph.runtime.environment

"""Runtime environment discovery helpers."""

from __future__ import annotations

import os
import sys
from dataclasses import dataclass
from pathlib import Path
from types import MappingProxyType
from typing import TYPE_CHECKING, Protocol

from ralph.runtime._version_info import PythonVersionInfo

if TYPE_CHECKING:
    from collections.abc import Mapping
if TYPE_CHECKING:

    class _VersionInfoProtocol(Protocol):
        @property
        def major(self) -> int: ...

        @property
        def minor(self) -> int: ...

        @property
        def micro(self) -> int: ...

        @property
        def releaselevel(self) -> str: ...

        @property
        def serial(self) -> int: ...

    class _ImplementationProtocol(Protocol):
        @property
        def name(self) -> str: ...

    class SysModuleProtocol(Protocol):
        """Subset of the sys module interface required for runtime environment detection."""

        @property
        def version_info(self) -> _VersionInfoProtocol: ...

        @property
        def version(self) -> str: ...

        @property
        def executable(self) -> str: ...

        @property
        def prefix(self) -> str: ...

        @property
        def base_prefix(self) -> str: ...

        @property
        def exec_prefix(self) -> str: ...

        @property
        def base_exec_prefix(self) -> str: ...

        @property
        def implementation(self) -> _ImplementationProtocol: ...


[docs] @dataclass(frozen=True) class RuntimeEnvironment: """Snapshot of the active Python runtime environment.""" python: PythonVersionInfo executable: Path prefix: Path base_prefix: Path exec_prefix: Path base_exec_prefix: Path in_virtualenv: bool virtualenv_path: Path | None env: Mapping[str, str]
[docs] def get(self, name: str, default: str | None = None) -> str | None: """Return an environment variable from the captured snapshot.""" return self.env.get(name, default)
_VIRTUAL_ENV_KEYS = ("VIRTUAL_ENV", "CONDA_PREFIX") def _normalize_env(env: Mapping[str, str] | None) -> Mapping[str, str]: captured = dict(os.environ if env is None else env) return MappingProxyType(captured)
[docs] def detect_virtualenv_path( env: Mapping[str, str] | None = None, *, sys_module: SysModuleProtocol = sys, ) -> Path | None: """Return the detected virtual environment path, if any.""" captured_env = _normalize_env(env) explicit_path = next( (captured_env[key] for key in _VIRTUAL_ENV_KEYS if captured_env.get(key)), None, ) if explicit_path: return Path(explicit_path) if sys_module.prefix != sys_module.base_prefix: return Path(sys_module.prefix) return None
[docs] def is_virtualenv( env: Mapping[str, str] | None = None, *, sys_module: SysModuleProtocol = sys, ) -> bool: """Return whether the current interpreter is running inside a virtual environment.""" return detect_virtualenv_path(env, sys_module=sys_module) is not None
[docs] def detect_runtime_environment( env: Mapping[str, str] | None = None, *, sys_module: SysModuleProtocol = sys, ) -> RuntimeEnvironment: """Capture a structured snapshot of the active Python runtime.""" captured_env = _normalize_env(env) virtualenv_path = detect_virtualenv_path(captured_env, sys_module=sys_module) return RuntimeEnvironment( python=PythonVersionInfo.from_sys(sys_module), executable=Path(sys_module.executable), prefix=Path(sys_module.prefix), base_prefix=Path(sys_module.base_prefix), exec_prefix=Path(sys_module.exec_prefix), base_exec_prefix=Path(sys_module.base_exec_prefix), in_virtualenv=virtualenv_path is not None, virtualenv_path=virtualenv_path, env=captured_env, )
__all__ = [ "RuntimeEnvironment", "SysModuleProtocol", "detect_runtime_environment", "detect_virtualenv_path", "is_virtualenv", ]