utils.py 2.5 KB
Newer Older
F
Frost Ming 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
import base64
import hashlib
import sys
from pathlib import Path
from typing import Iterable, Optional, Tuple, Type, TypeVar
from findpython import PythonVersion

from findpython.providers import BaseProvider

from pdm.project import Project

IS_WIN = sys.platform == "win32"
BIN_DIR = "Scripts" if IS_WIN else "bin"


def hash_path(path: str) -> str:
    """Generate a hash for the given path."""
    return base64.urlsafe_b64encode(hashlib.md5(path.encode()).digest()).decode()[:8]


def get_in_project_venv_python(root: Path) -> Optional[Path]:
    """Get the python interpreter path of venv-in-project"""
    for possible_dir in (".venv", "venv", "env"):
        venv_python = get_venv_python(root / possible_dir)
        if venv_python.exists():
            return venv_python
    return None


def get_venv_prefix(project: Project) -> str:
    """Get the venv prefix for the project"""
    path = project.root
    name_hash = hash_path(path.as_posix())
    return f"{path.name}-{name_hash}-"


def iter_venvs(project: Project) -> Iterable[Tuple[str, Path]]:
    """Return an iterable of venv paths associated with the project"""
    in_project_venv_python = get_in_project_venv_python(project.root)
    if in_project_venv_python is not None:
        yield "in-project", Path(in_project_venv_python).parent.parent
    venv_prefix = get_venv_prefix(project)
    venv_parent = Path(project.config["venv.location"])
    for venv in venv_parent.glob(f"{venv_prefix}*"):
        ident = venv.name[len(venv_prefix) :]
        yield ident, venv


def get_venv_python(venv: Path) -> Path:
    """Get the interpreter path inside the given venv."""
    suffix = ".exe" if IS_WIN else ""
    return venv / BIN_DIR / f"python{suffix}"


def iter_central_venvs(project: Project) -> Iterable[Tuple[str, Path]]:
    """Return an iterable of all managed venvs and their paths."""
    venv_parent = Path(project.config["venv.location"])
    for venv in venv_parent.glob("*"):
        ident = venv.name
        yield ident, venv


T = TypeVar("T", bound=BaseProvider)


class VenvProvider(BaseProvider):
    """A Python provider for project venv pythons"""

    def __init__(self, project: Project) -> None:
        self.project = project

    @classmethod
    def create(cls: Type[T]) -> Optional[T]:  # pragma: no cover
        return None

    def find_pythons(self) -> Iterable[PythonVersion]:
        for _, venv in iter_venvs(self.project):
            python = get_venv_python(venv)
            if python.exists():
                yield PythonVersion(python, _interpreter=python, keep_symlink=True)