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

feat: new findpython #2099

Merged
merged 2 commits into from
Jul 12, 2023
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
1 change: 1 addition & 0 deletions docs/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ The following configuration items can be retrieved and modified by [`pdm config`
| `project_max_depth` | The max depth to search for a project through the parents | 5 | No | `PDM_PROJECT_MAX_DEPTH` |
| `python.use_pyenv` | Use the pyenv interpreter | `True` | Yes | |
| `python.use_venv` | Use virtual environments when available | `True` | Yes | `PDM_USE_VENV` |
| `python.providers` | List of python provider names for findpython | All providers supported by findpython | Yes | |
| `pypi.url` | The URL of PyPI mirror | `https://pypi.org/simple` | Yes | `PDM_PYPI_URL` |
| `pypi.username` | The username to access PyPI | | Yes | `PDM_PYPI_USERNAME` |
| `pypi.password` | The password to access PyPI | | Yes | `PDM_PYPI_PASSWORD` |
Expand Down
18 changes: 18 additions & 0 deletions docs/docs/usage/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@ If `-g/--global` option is used, the first item will be replaced by `<CONFIG_ROO

You can find all available configuration items in [Configuration Page](../reference/configuration.md).

## Configure the Python finder

By default, PDM will try to find Python interpreters in the following sources:

- `venv`: The PDM virtualenv location
- `path`: The `PATH` environment variable
- `pyenv`: The [pyenv](https://github.com/pyenv/pyenv) install root
- `rye`: The [rye](https://rye-up.com/) toolchain install root
- `asdf`: The [asdf](https://asdf-vm.com/) python install root
- `winreg`: The Windows registry

You can unselect some of them or change the order by setting `python.providers` config key:

```bash
pdm config python.providers rye # Rye source only
pdm config python.providers pyenv,asdf # pyenv and asdf
```

## Allow prereleases in resolution result

By default, `pdm`'s dependency resolver will ignore prereleases unless there are no stable versions for the given version range of a dependency. This behavior can be changed by setting `allow_prereleases` to `true` in `[tool.pdm]` table:
Expand Down
1 change: 1 addition & 0 deletions news/2099.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow to change the python providers from the config.
10 changes: 5 additions & 5 deletions pdm.lock

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

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies = [
"pyproject-hooks",
"requests-toolbelt",
"unearth>=0.9.0",
"findpython>=0.2.2",
"findpython>=0.3.0",
"tomlkit>=0.11.1,<1",
"shellingham>=1.3.2",
"python-dotenv>=0.15",
Expand Down
13 changes: 12 additions & 1 deletion src/pdm/cli/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
self._list_config(project, options)

def _get_config(self, project: Project, options: argparse.Namespace) -> None:
from findpython import ALL_PROVIDERS

if options.key in project.project_config.deprecated: # pragma: no cover
project.core.ui.echo(
f"DEPRECATED: the config has been renamed to {project.project_config.deprecated[options.key]}",
Expand All @@ -87,6 +89,8 @@ def _get_config(self, project: Project, options: argparse.Namespace) -> None:
value = project.global_config[options.key]
if options.key.endswith(".password"):
value = "[i]<hidden>[/i]"
elif options.key == "python.providers" and not value:
value = ["venv", *ALL_PROVIDERS]
project.core.ui.echo(value)

def _set_config(self, project: Project, options: argparse.Namespace) -> None:
Expand All @@ -100,6 +104,8 @@ def _set_config(self, project: Project, options: argparse.Namespace) -> None:
config[options.key] = options.value

def _show_config(self, config: Mapping[str, Any], supersedes: Mapping[str, Any]) -> None:
from findpython import ALL_PROVIDERS

assert Config.site is not None
for key in sorted(config):
deprecated = ""
Expand Down Expand Up @@ -133,7 +139,12 @@ def _show_config(self, config: Mapping[str, Any], supersedes: Mapping[str, Any])
style=extra_style,
verbosity=termui.Verbosity.DETAIL,
)
value = "[i]<hidden>[/]" if key.endswith("password") else config[key]
if key.endswith("password"):
value: Any = "[i]<hidden>[/i]"
else:
value = config[key]
if key == "python.providers" and not value:
value = ["venv", *ALL_PROVIDERS]
self.ui.echo(
f"[primary]{canonical_key}[/]{deprecated} = {value}",
style=extra_style,
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/commands/use.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def select_python(

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

if venv:
Expand Down
16 changes: 6 additions & 10 deletions src/pdm/cli/commands/venv/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

import base64
import hashlib
import typing as t
from pathlib import Path
from typing import Iterable, TypeVar

from findpython import PythonVersion
from findpython.providers import BaseProvider
from findpython import BaseProvider, PythonVersion

from pdm.exceptions import PdmUsageError
from pdm.models.venv import VirtualEnv
Expand Down Expand Up @@ -34,7 +33,7 @@ def get_venv_prefix(project: Project) -> str:
return f"{path.name}-{name_hash}-"


def iter_venvs(project: Project) -> Iterable[tuple[str, VirtualEnv]]:
def iter_venvs(project: Project) -> t.Iterable[tuple[str, VirtualEnv]]:
"""Return an iterable of venv paths associated with the project"""
in_project_venv = get_in_project_venv(project.root)
if in_project_venv is not None:
Expand All @@ -48,28 +47,25 @@ def iter_venvs(project: Project) -> Iterable[tuple[str, VirtualEnv]]:
yield ident, venv


def iter_central_venvs(project: Project) -> Iterable[tuple[str, Path]]:
def iter_central_venvs(project: Project) -> t.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]) -> T | None: # pragma: no cover
def create(cls) -> t.Self | None:
return None

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

Expand Down
8 changes: 4 additions & 4 deletions src/pdm/models/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,21 @@ def path(self) -> Path:
def executable(self) -> Path:
return self._py_ver.interpreter

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

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

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

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

@property
def version_tuple(self) -> tuple[int, ...]:
Expand Down
10 changes: 10 additions & 0 deletions src/pdm/project/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ def ensure_boolean(val: Any) -> bool:
return bool(val) and val.lower() not in ("false", "no", "0")


def split_by_comma(val: list[str] | str) -> list[str]:
"""Split a string value by comma"""
if isinstance(val, str):
return [v.strip() for v in val.split(",")]
return val


DEFAULT_PYPI_INDEX = "https://pypi.org/simple"


Expand Down Expand Up @@ -155,6 +162,9 @@ class Config(MutableMapping[str, str]):
"`symlink` or `pth` to create links to the cached installation",
"symlink",
),
"python.providers": ConfigItem(
"List of python provider names for findpython", default=[], coerce=split_by_comma
),
"python.use_pyenv": ConfigItem("Use the pyenv interpreter", True, coerce=ensure_boolean),
"python.use_venv": ConfigItem(
"Use virtual environments when available", True, env_var="PDM_USE_VENV", coerce=ensure_boolean
Expand Down
8 changes: 5 additions & 3 deletions src/pdm/project/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,9 +671,11 @@ def _get_python_finder(self) -> Finder:

from pdm.cli.commands.venv.utils import VenvProvider

finder = Finder(resolve_symlinks=True)
if self.config["python.use_venv"]:
finder.add_provider(VenvProvider(self), 0)
providers: list[str] = self.config["python.providers"]
finder = Finder(resolve_symlinks=True, selected_providers=providers or None)
if self.config["python.use_venv"] and (not providers or "venv" in providers):
venv_pos = providers.index("venv") if providers else 0
finder.add_provider(VenvProvider(self), venv_pos)
return finder

# compatibility, shouldn't be used directly
Expand Down