Source code for vcorelib.python

"""
A module for working with paths to python interpreters and virtual
environments.
"""

# built-in
from os import environ as _environ
from pathlib import Path as _Path
from shutil import which as _which
from sys import executable as _executable
from sys import version_info as _version_info
from typing import NamedTuple

# internal
from vcorelib.paths import Pathlike as _Pathlike
from vcorelib.paths import normalize as _normalize

# third-party
from vcorelib.task.subprocess.run import is_windows as _is_windows

CURRENT_PYTHON = f"{_version_info[0]}.{_version_info[1]}"


def python_version() -> str:
    """Get the version of Python to use."""
    return _environ.get("PYTHON_VERSION", CURRENT_PYTHON)


def python_entry(version: str = None) -> str:
    """Attempt to get a Python entry-point as a string."""

    result = None

    if version is None:
        version = python_version()

    options = [f"python{version}{'.exe' if _is_windows() else ''}"]

    # Use the current executable as a candidate if it's the right version.
    if version.startswith(CURRENT_PYTHON):
        result = str(_executable)
        options.append(result)

    for option in options:
        if _which(option) is not None:
            result = option
            break

    assert result is not None, f"Couldn't find 'python{version}'!"
    return result


def venv_name(version: str = None) -> str:
    """Get the name for a virtual environment to use."""
    if version is None:
        version = python_version()
    return f"venv{version}"


def venv_dir(cwd: _Pathlike, version: str = None) -> _Path:
    """Get the path for a virtual environment to use."""
    return _normalize(cwd).joinpath(venv_name(version))


def venv_bin(
    cwd: _Pathlike, program: str = None, version: str = None
) -> _Path:
    """Get the path to a virtual environment's script directory."""

    path = venv_dir(cwd, version).joinpath(
        "Scripts" if _is_windows() else "bin"
    )
    if program is not None:
        path = path.joinpath(f"{program}{'.exe' if _is_windows() else ''}")
    return path


[docs] class StrToBool(NamedTuple): """A container for results when converting strings to boolean.""" result: bool valid: bool def __bool__(self) -> bool: """Determine boolean status.""" return self.result and self.valid
[docs] @staticmethod def check(data: str) -> bool: """Check a string for a boolean 'true' value.""" return bool(StrToBool.parse(data))
[docs] @staticmethod def parse(data: str) -> "StrToBool": """Parse a string to boolean.""" data = data.lower() is_true = data == "true" resolved = is_true or data == "false" return StrToBool(is_true, resolved)