Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch from pythonfinder to findpython #930

Merged
merged 9 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
fail-fast: false
matrix:
python-version: [3.7, 3.8, 3.9, "3.10", "3.11.0-alpha - 3.11.0"]
os: [ubuntu-latest, macOS-latest, windows-latest]
os: [ubuntu-latest, windows-latest, macos-latest]
arch: [x64]
install-via: [pip]
include:
Expand Down Expand Up @@ -82,10 +82,6 @@ jobs:
run: |
echo "::set-output name=PY::$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')"
echo "::set-output name=PIP_CACHE::$(pip cache dir)"
- name: Fix Ubuntu Env
run: |
echo "LD_PRELOAD=/lib/x86_64-linux-gnu/libgcc_s.so.1" >> $GITHUB_ENV
if: runner.os == 'Linux'
- name: Cache PIP
uses: actions/cache@v2
with:
Expand All @@ -103,6 +99,7 @@ jobs:
- name: Install Dev Dependencies
run: |
pdm install -v -dGtest
pdm info
- name: Run Tests
run: pdm run pytest -n auto --cov pdm --cov-config=setup.cfg --cov-report=xml tests
- name: Upload coverage to Codecov
Expand Down
1 change: 1 addition & 0 deletions news/930.dep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Switch from `pythonfinder` to `findpython` as the Python version finder.
32 changes: 14 additions & 18 deletions pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions pdm/_vendor/halo/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"""
import codecs
import platform
import six
try:
from shutil import get_terminal_size
except ImportError:
Expand Down Expand Up @@ -87,10 +86,7 @@ def is_text_type(text):
bool
Whether parameter is a string or not
"""
if isinstance(text, six.text_type) or isinstance(text, six.string_types):
return True

return False
return isinstance(text, str)


def decode_utf_8_text(text):
Expand Down
2 changes: 1 addition & 1 deletion pdm/builders/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def __init__(self, src_dir: str | Path, environment: Environment) -> None:
Otherwise, the environment of the host Python will be used.
"""
self._env = environment
self.executable = self._env.interpreter.executable
self.executable = self._env.interpreter.executable.as_posix()
self.src_dir = src_dir
self.isolated = environment.project.config["build_isolation"]
logger.debug("Preparing isolated env for PEP 517 build...")
Expand Down
9 changes: 4 additions & 5 deletions pdm/cli/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from argparse import Namespace
from collections import defaultdict
from itertools import chain
from pathlib import Path
from typing import Iterable, Mapping, Sequence, cast

import click
Expand Down Expand Up @@ -536,7 +535,7 @@ def do_use(
python = python.strip()

def version_matcher(py_version: PythonInfo) -> bool:
return project.python_requires.contains(str(py_version.version))
return project.python_requires.contains(str(py_version.version), True)

if not project.cache_dir.exists():
project.cache_dir.mkdir(parents=True)
Expand Down Expand Up @@ -584,7 +583,7 @@ def version_matcher(py_version: PythonInfo) -> bool:
)
selected_python = found_interpreters[int(selection)]
if python:
use_cache.set(python, selected_python.path)
use_cache.set(python, selected_python.path.as_posix())

old_python = project.python if "python.path" in project.config else None
project.core.ui.echo(
Expand All @@ -596,11 +595,11 @@ def version_matcher(py_version: PythonInfo) -> bool:
project.python = selected_python
if (
old_python
and Path(old_python.path) != Path(selected_python.path)
and old_python.path != selected_python.path
and not project.environment.is_global
):
project.core.ui.echo(termui.cyan("Updating executable scripts..."))
project.environment.update_shebangs(selected_python.executable)
project.environment.update_shebangs(selected_python.executable.as_posix())


def do_import(
Expand Down
6 changes: 3 additions & 3 deletions pdm/cli/commands/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
check_project_file(project)
interpreter = project.python
if options.python:
project.core.ui.echo(interpreter.executable)
project.core.ui.echo(str(interpreter.executable))
elif options.where:
project.core.ui.echo(project.root.as_posix())
project.core.ui.echo(str(project.root))
elif options.packages:
project.core.ui.echo(str(project.environment.packages_path))
elif options.env:
Expand All @@ -49,7 +49,7 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
(termui.cyan("PDM version:", bold=True), project.core.version),
(
termui.cyan("Python Interpreter:", bold=True),
interpreter.executable + f" ({interpreter.identifier})",
f"{interpreter.executable} ({interpreter.identifier})",
),
(termui.cyan("Project Root:", bold=True), project.root.as_posix()),
(
Expand Down
4 changes: 2 additions & 2 deletions pdm/installers/installers.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def install_wheel(
}
destination = InstallDestination(
scheme_dict=environment.get_paths(),
interpreter=environment.interpreter.executable,
interpreter=str(environment.interpreter.executable),
script_kind=_get_kind(environment),
)
_install_wheel(
Expand All @@ -187,7 +187,7 @@ def install_wheel_with_cache(
wheel_stem = Path(wheel).stem
cache_path = environment.project.cache("packages") / wheel_stem
package_cache = CachedPackage(cache_path)
interpreter = environment.interpreter.executable
interpreter = str(environment.interpreter.executable)
script_kind = _get_kind(environment)
supports_symlink = (
environment.project.config["install.cache_method"] == "symlink"
Expand Down
10 changes: 5 additions & 5 deletions pdm/models/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def get_finder(
sources = self.project.sources

python_version = self.interpreter.version_tuple
python_abi_tag = get_python_abi_tag(self.interpreter.executable)
python_abi_tag = get_python_abi_tag(str(self.interpreter.executable))
finder = get_finder(
sources,
self.project.cache_dir.as_posix(),
Expand All @@ -138,7 +138,7 @@ def get_working_set(self) -> WorkingSet:
@cached_property
def marker_environment(self) -> dict[str, str]:
"""Get environment for marker evaluation"""
return get_pep508_environment(self.interpreter.executable)
return get_pep508_environment(str(self.interpreter.executable))

def which(self, command: str) -> str | None:
"""Get the full path of the given executable against this environment."""
Expand All @@ -147,7 +147,7 @@ def which(self, command: str) -> str | None:
version = python[6:]
this_version = self.interpreter.version
if not version or str(this_version).startswith(version):
return self.interpreter.executable
return str(self.interpreter.executable)
# Fallback to use shutil.which to find the executable
this_path = self.get_paths()["scripts"]
python_root = os.path.dirname(self.interpreter.executable)
Expand Down Expand Up @@ -194,7 +194,7 @@ def pip_command(self) -> list[str]:
from pip import __file__ as pip_location

python_major = self.interpreter.major
executable = self.interpreter.executable
executable = str(self.interpreter.executable)
proc = subprocess.run(
[executable, "-Esm", "pip", "--version"], capture_output=True
)
Expand All @@ -217,7 +217,7 @@ class GlobalEnvironment(Environment):
is_global = True

def get_paths(self) -> dict[str, str]:
paths = get_sys_config_paths(self.interpreter.executable)
paths = get_sys_config_paths(str(self.interpreter.executable))
if is_venv_python(self.interpreter.executable):
python_xy = f"python{self.interpreter.identifier}"
paths["include"] = os.path.join(paths["data"], "include", "site", python_xy)
Expand Down
34 changes: 0 additions & 34 deletions pdm/models/in_process/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import functools
import json
import os
import platform
import subprocess
import sys
from pathlib import Path
from typing import Dict, Optional

Expand Down Expand Up @@ -50,35 +48,3 @@ def get_pep508_environment(executable: str) -> Dict[str, str]:
script = str(FOLDER_PATH / "pep508.py")
args = [executable, "-Es", script]
return json.loads(subprocess.check_output(args))


@functools.lru_cache()
def get_architecture(executable: str) -> str:
"""Get the architecture bits for the given python executable"""
if os.path.normpath(executable) == os.path.normpath(sys.executable):
return platform.architecture()[0]
bits, _ = platform.architecture(executable, "_DEFAULT")
if bits != "_DEFAULT":
return bits
# On non-Unix platforms that do not support 'file' command,
# platform.architecture(executable) cannot return the correct arch.
# Retrieve it in subprocess instead.
return (
subprocess.check_output(
[executable, "-Esc", "import platform;print(platform.architecture()[0])"]
)
.decode("utf8")
.strip()
)


@functools.lru_cache()
def get_underlying_executable(executable: str) -> str:
"""Find the real sys.executable under the wrapper script if any"""
return (
subprocess.check_output(
[executable, "-Esc", "import sys;print(sys.executable)"]
)
.decode("utf8")
.strip()
)
55 changes: 27 additions & 28 deletions pdm/models/python.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,70 @@
from __future__ import annotations

import dataclasses
import os
from pathlib import Path
from typing import Any

from findpython import PythonVersion
from packaging.version import Version
from pythonfinder import InvalidPythonVersion
from pythonfinder.models.python import PythonVersion

from pdm.exceptions import InvalidPyVersion
from pdm.models.in_process import get_architecture, get_underlying_executable
from pdm.utils import cached_property


@dataclasses.dataclass
class PythonInfo:
"""
A convenient helper class that holds all information of a Python interepreter.
"""

path: str
version: Version
executable: str = dataclasses.field(init=False)

def __post_init__(self) -> None:
executable = get_underlying_executable(self.path)
self.executable = Path(executable).as_posix()

@classmethod
def from_python_version(cls, py_version: PythonVersion) -> "PythonInfo":
return cls(path=py_version.executable, version=py_version.version)
def __init__(self, py_version: PythonVersion) -> None:
self._py_ver = py_version

@classmethod
def from_path(cls, path: str | Path) -> "PythonInfo":
try:
return cls.from_python_version(PythonVersion.from_path(str(path)))
except InvalidPythonVersion as e:
raise InvalidPyVersion(str(e)) from e
py_ver = PythonVersion(Path(path))
if py_ver.executable.exists() and py_ver.is_valid():
return cls(py_ver)
else:
raise InvalidPyVersion(f"Invalid Python interpreter: {path}")

def __hash__(self) -> int:
return hash(os.path.normcase(self.executable))
return hash(self._py_ver)

def __eq__(self, o: Any) -> bool:
if not isinstance(o, PythonInfo):
return False
return os.path.normcase(self.executable) == os.path.normcase(o.executable)
return self._py_ver == o._py_ver

@property
def path(self) -> Path:
return self._py_ver.executable

@property
def executable(self) -> Path:
return self._py_ver.interpreter

@property
def version(self) -> Version:
return self._py_ver.version

@property
def major(self) -> int:
return self.version.major
return self._py_ver.major

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

@property
def micro(self) -> int:
return self.version.micro
return self._py_ver.patch

@property
def version_tuple(self) -> tuple[int, ...]:
return (self.major, self.minor, self.micro)

@cached_property
@property
def is_32bit(self) -> bool:
return "32bit" in get_architecture(self.executable)
return "32bit" in self._py_ver.architecture

def for_tag(self) -> str:
return f"{self.major}{self.minor}"
Expand Down
Loading