Skip to content

Commit

Permalink
feat: move the python.path to its own config file (#1742)
Browse files Browse the repository at this point in the history
  • Loading branch information
frostming committed Mar 23, 2023
1 parent e2d3990 commit a475abe
Show file tree
Hide file tree
Showing 24 changed files with 80 additions and 82 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ caches/
.idea/
__pypackages__
.pdm.toml
.pdm-python
temp.py

# Pyannotate generated stubs
Expand Down
7 changes: 3 additions & 4 deletions docs/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,15 @@ The following configuration items can be retrieved and modified by [`pdm config`
| `install.cache_method` | Specify how to create links to the caches(`symlink` or `pth`) | `symlink` | Yes | |
| `install.parallel` | Whether to perform installation and uninstallation in parallel | `True` | Yes | `PDM_PARALLEL_INSTALL` |
| `project_max_depth` | The max depth to search for a project through the parents | 5 | No | `PDM_PROJECT_MAX_DEPTH` |
| `python.path` | The Python interpreter path | | Yes | `PDM_PYTHON` |
| `python.use_pyenv` | Use the pyenv interpreter | `True` | Yes | |
| `python.use_venv` | Install packages into the activated venv site packages instead of PEP 582 | `True` | Yes | `PDM_USE_VENV` |
| `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` |
| `pypi.ignore_stored_index` | Ignore the configured indexes | `False` | Yes | `PDM_IGNORE_STORED_INDEX` |
| `pypi.ca_certs` | Path to a PEM-encoded CA cert bundle (used for server cert verification) | The CA certificates from [certifi](https://pypi.org/project/certifi/) | Yes | |
| `pypi.client_cert` | Path to a PEM-encoded client cert and optional key | | Yes | |
| `pypi.client_key` | Path to a PEM-encoded client cert private key, if not in pypi.client_cert | | Yes | |
| `pypi.ca_certs` | Path to a PEM-encoded CA cert bundle (used for server cert verification) | The CA certificates from [certifi](https://pypi.org/project/certifi/) | No | |
| `pypi.client_cert` | Path to a PEM-encoded client cert and optional key | | No | |
| `pypi.client_key` | Path to a PEM-encoded client cert private key, if not in pypi.client_cert | | No | |
| `pypi.verify_ssl` | Verify SSL certificate when query PyPI | `True` | Yes | |
| `pypi.json_api` | Consult PyPI's JSON API for package metadata | `False` | Yes | `PDM_PYPI_JSON_API` |
| `strategy.save` | Specify how to save versions when a package is added | `minimum`(can be: `exact`, `wildcard`, `minimum`, `compatible`) | Yes | |
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/usage/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ By default, the configuration are changed globally, if you want to make the conf
pdm config --local pypi.url "https://test.pypi.org/simple"
```

Any local configurations will be stored in `.pdm.toml` under the project root directory.
Any local configurations will be stored in `pdm.toml` under the project root directory.

## Configuration files

The configuration files are searched in the following order:

1. `<PROJECT_ROOT>/.pdm.toml` - The project configuration
1. `<PROJECT_ROOT>/pdm.toml` - The project configuration
2. `<CONFIG_ROOT>/config.toml` - The home configuration
3. `<SITE_CONFIG_ROOT>/config.toml` - The site configuration

Expand All @@ -49,7 +49,7 @@ and `<SITE_CONFIG_ROOT>` is:
- `/Library/Preference/pdm` on MacOS as defined by [Apple File System Basics](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html)
- `C:\ProgramData\pdm\pdm` on Windows as defined in [Known folders](https://docs.microsoft.com/en-us/windows/win32/shell/known-folders)

If `-g/--global` option is used, the first item will be replaced by `<CONFIG_ROOT>/global-project/.pdm.toml`.
If `-g/--global` option is used, the first item will be replaced by `<CONFIG_ROOT>/global-project/pdm.toml`.

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

Expand Down
8 changes: 0 additions & 8 deletions docs/docs/usage/pep582.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,6 @@ to the Python library search path.

Now there are no built-in support or plugins for PEP 582 in most IDEs, you have to configure your tools manually.

PDM will write and store project-wide configurations in `.pdm.toml` and you are recommended to add following lines
in the `.gitignore`:

```
.pdm.toml
__pypackages__/
```

### PyCharm

Mark `__pypackages__/<major.minor>/lib` as [Sources Root](https://www.jetbrains.com/help/pycharm/configuring-project-structure.html#mark-dir-project-view).
Expand Down
11 changes: 7 additions & 4 deletions docs/docs/usage/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ You will need to answer a few questions, to help PDM to create a `pyproject.toml
## Choose a Python interpreter

At first, you need to choose a Python interpreter from a list of Python versions installed on your machine. The interpreter path
will be stored in the project config `.pdm.toml` and used by subsequent commands. You can also change it later with [`pdm use`](../reference/cli.md#exec-0--use).
will be stored in `.pdm-python` and used by subsequent commands. You can also change it later with [`pdm use`](../reference/cli.md#exec-0--use).

Alternatively, you can specify the Python interpreter path via `PDM_PYTHON` environment variable. When it is set, the path saved in `.pdm-python` will be ignored.

## Virtualenv or not

Expand Down Expand Up @@ -92,7 +94,7 @@ Also, when you are executing [`pdm init`](../reference/cli.md#exec-0--init) or [

## Working with version control

You **must** commit the `pyproject.toml` file. You **should** commit the `pdm.lock` file. **Do not** commit the `.pdm.toml` file.
You **must** commit the `pyproject.toml` file. You **should** commit the `pdm.lock` and `pdm.toml` file. **Do not** commit the `.pdm-python` file.

The `pyproject.toml` file must be committed as it contains the project's build metadata and dependencies needed for PDM.
It is also commonly used by other python tools for configuration. Read more about the `pyproject.toml` file at
Expand All @@ -101,8 +103,9 @@ It is also commonly used by other python tools for configuration. Read more abou
You should be committing the `pdm.lock` file, by doing so you ensure that all installers are using the same versions of dependencies.
To learn how to update dependencies see [update existing dependencies](./dependency.md#update-existing-dependencies).

It is not necessary to commit your `.pdm.toml` file as it contains configuration specific to your system.
If you are using git you can safely add `.pdm.toml` to your `.gitignore` file.
`pdm.toml` contains some project-wide configuration and it may be useful to commit it for sharing.

`.pdm-python` stores the **Python path** used by the **current** project and doesn't need to be shared.

## Show the current Python environment

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/usage/venv.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ When you run [`pdm init`](../reference/cli.md#exec-0--init) command, PDM will [a

Compared to [PEP 582](https://www.python.org/dev/peps/pep-0582/), virtual environments are considered more mature and have better support in the Python ecosystem as well as IDEs. Therefore, virtualenv is the default mode if not configured otherwise.

**Virtual environments will be used if the project interpreter(the interpreter stored in `.pdm.toml`, which can be checked by `pdm info`) is from a virtualenv.**
**Virtual environments will be used if the project interpreter(the interpreter stored in `.pdm-python`, which can be checked by `pdm info`) is from a virtualenv.**

## Virtualenv auto-creation

Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions news/1742.break.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Move the project python path to its own file, and rename the project config file as `pdm.toml` which can be committed to the VCS.
3 changes: 2 additions & 1 deletion src/pdm/cli/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,8 @@ def version_matcher(py_version: PythonInfo) -> bool:

if not save:
return selected_python
old_python = PythonInfo.from_path(project.config["python.path"]) if "python.path" in project.config else None
saved_python = project._saved_python
old_python = PythonInfo.from_path(saved_python) if saved_python else None
project.core.ui.echo(
f"Using Python interpreter: [success]{str(selected_python.path)}[/] ({selected_python.identifier})"
)
Expand Down
4 changes: 2 additions & 2 deletions src/pdm/cli/commands/venv/activate.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
)
raise SystemExit(1)
else:
# Use what is saved in .pdm.toml
interpreter = project.project_config.get("python.path")
# Use what is saved in .pdm-python
interpreter = project._saved_python
if not interpreter:
project.core.ui.echo(
"The project doesn't have a saved python.path. Run [success]pdm use[/] to pick one.",
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/commands/venv/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def pip_args(self, with_pip: bool) -> Iterable[str]:
@cached_property
def _resolved_interpreter(self) -> PythonInfo:
if not self.python:
saved_python = self.project.project_config.get("python.path")
saved_python = self.project._saved_python
if saved_python:
return PythonInfo.from_path(saved_python)
for py_version in self.project.find_interpreters(self.python):
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/commands/venv/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ListCommand(BaseCommand):
def handle(self, project: Project, options: argparse.Namespace) -> None:
project.core.ui.echo("Virtualenvs created with this project:\n")
for ident, venv in iter_venvs(project):
saved_python = project.project_config.get("python.path")
saved_python = project._saved_python
if saved_python and Path(saved_python).parent.parent == venv:
mark = "*"
else:
Expand Down
10 changes: 4 additions & 6 deletions src/pdm/cli/commands/venv/remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import shutil
from pathlib import Path

from pdm.project import Project
from pdm import termui
from pdm.cli.commands.base import BaseCommand
from pdm.cli.commands.venv.utils import iter_venvs
from pdm.cli.options import verbose_option
from pdm.project import Project


class RemoveCommand(BaseCommand):
Expand All @@ -29,11 +29,9 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
if ident == options.env:
if options.yes or termui.confirm(f"[warning]Will remove: [success]{venv}[/], continue?", default=True):
shutil.rmtree(venv)
if (
project.project_config.get("python.path")
and Path(project.project_config["python.path"]).parent.parent == venv
):
del project.project_config["python.path"]
saved_python = project._saved_python
if saved_python and Path(saved_python).parent.parent == venv:
project._saved_python = None
project.core.ui.echo("Removed successfully!")
break
else:
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/completions/pdm.fish
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ end
# global options
complete -c pdm -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -l config -d 'Specify another config file path(env var: PDM_CONFIG_FILE)'
complete -c pdm -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -l help -d 'show this help message and exit'
complete -c pdm -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -l ignore-python -d 'Ignore the Python path saved in the .pdm.toml config'
complete -c pdm -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -l ignore-python -d 'Ignore the Python path saved in .pdm-python'
complete -c pdm -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -l pep582 -d 'Print the command line to be eval\'d by the shell'
complete -c pdm -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -l verbose -d '-v for detailed output and -vv for more detailed'
complete -c pdm -n '__fish_pdm_a919b69078acdf0a_complete_no_subcommand' -l version -d 'Show version'
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/completions/pdm.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ _pdm() {
$arguments \
{-c,--config}'[Specify another config file path(env var: PDM_CONFIG_FILE)]' \
{-V,--version}'[Show the version and exit]' \
{-I,--ignore-python}'[Ignore the Python path saved in the .pdm.toml config]' \
{-I,--ignore-python}'[Ignore the Python path saved in .pdm-python]' \
'--pep582:Print the command line to be eval by the shell:shell:(zsh bash fish tcsh csh)' \
'*:: :->_subcmds' \
&& return 0
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ def no_isolation_callback(
"-I",
"--ignore-python",
action="store_true",
help="Ignore the Python path saved in the .pdm.toml config",
help="Ignore the Python path saved in .pdm-python",
)

prerelease_option = Option(
Expand Down
13 changes: 4 additions & 9 deletions src/pdm/project/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
def load_config(file_path: Path) -> dict[str, Any]:
"""Load a nested TOML document into key-value pairs
E.g. ["python"]["path"] will be loaded as "python.path" key.
E.g. ["python"]["use_venv"] will be loaded as "python.use_venv" key.
"""

def get_item(sub_data: Mapping[str, Any]) -> dict[str, Any]:
Expand Down Expand Up @@ -153,7 +153,6 @@ class Config(MutableMapping[str, str]):
"`symlink` or `pth` to create links to the cached installation",
"symlink",
),
"python.path": ConfigItem("The Python interpreter path", env_var="PDM_PYTHON"),
"python.use_pyenv": ConfigItem("Use the pyenv interpreter", True, coerce=ensure_boolean),
"python.use_venv": ConfigItem(
"Install packages into the activated venv site packages instead of PEP 582",
Expand All @@ -170,20 +169,16 @@ class Config(MutableMapping[str, str]):
"pypi.username": ConfigItem("The username to access PyPI", env_var="PDM_PYPI_USERNAME"),
"pypi.password": ConfigItem("The password to access PyPI", env_var="PDM_PYPI_PASSWORD"),
"pypi.ca_certs": ConfigItem(
"Path to a CA certificate bundle used for verifying the identity of the PyPI server",
"Path to a CA certificate bundle used for verifying the identity of the PyPI server", global_only=True
),
"pypi.ignore_stored_index": ConfigItem(
"Ignore the configured indexes",
False,
env_var="PDM_IGNORE_STORED_INDEX",
coerce=ensure_boolean,
),
"pypi.client_cert": ConfigItem(
"Path to client certificate file, or combined cert/key file",
),
"pypi.client_key": ConfigItem(
"Path to client cert keyfile, if not in pypi.client_cert",
),
"pypi.client_cert": ConfigItem("Path to client certificate file, or combined cert/key file", global_only=True),
"pypi.client_key": ConfigItem("Path to client cert keyfile, if not in pypi.client_cert", global_only=True),
"pypi.json_api": ConfigItem(
"Consult PyPI's JSON API for package metadata",
False,
Expand Down
30 changes: 24 additions & 6 deletions src/pdm/project/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import contextlib
import hashlib
import os
import re
Expand Down Expand Up @@ -132,7 +133,7 @@ def scripts(self) -> dict[str, str | dict[str, str]]:
@cached_property
def project_config(self) -> Config:
"""Read-and-writable configuration dict for project settings"""
return Config(self.root / ".pdm.toml")
return Config(self.root / "pdm.toml")

@property
def name(self) -> str | None:
Expand All @@ -152,7 +153,24 @@ def python(self) -> PythonInfo:
@python.setter
def python(self, value: PythonInfo) -> None:
self._python = value
self.project_config["python.path"] = value.path
self._saved_python = value.path.as_posix()

@property
def _saved_python(self) -> str | None:
if os.getenv("PDM_PYTHON"):
return os.getenv("PDM_PYTHON")
with contextlib.suppress(FileNotFoundError):
return self.root.joinpath(".pdm-python").read_text("utf-8").strip()
return None

@_saved_python.setter
def _saved_python(self, value: str | None) -> None:
python_file = self.root.joinpath(".pdm-python")
if value is None:
with contextlib.suppress(FileNotFoundError):
python_file.unlink()
return
python_file.write_text(value, "utf-8")

def resolve_interpreter(self) -> PythonInfo:
"""Get the Python interpreter path."""
Expand All @@ -166,12 +184,12 @@ def note(message: str) -> None:
self.core.ui.echo(message, style="warning", err=True)

config = self.config
if config.get("python.path") and not os.getenv("PDM_IGNORE_SAVED_PYTHON"):
saved_path = config["python.path"]
saved_path = self._saved_python
if saved_path and not os.getenv("PDM_IGNORE_SAVED_PYTHON"):
python = PythonInfo.from_path(saved_path)
if match_version(python):
return python
self.project_config.pop("python.path", None)
self._saved_python = None # Clear the saved path if it doesn't match

if config.get("python.use_venv") and not self.is_global:
# Resolve virtual environments from env-vars
Expand Down Expand Up @@ -636,7 +654,7 @@ def _get_python_finder(self) -> Finder:

finder = Finder(resolve_symlinks=True)
if self.config["python.use_venv"]:
finder._providers.insert(0, VenvProvider(self))
finder.add_provider(VenvProvider(self), 0)
return finder

# compatibility, shouldn't be used directly
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ def project_no_init(
python_path = find_python_in_path(sys.base_prefix)
if python_path is None:
raise ValueError("Unable to find a Python path")
p.project_config["python.path"] = python_path.as_posix()
p._saved_python = python_path.as_posix()
monkeypatch.delenv("VIRTUAL_ENV", raising=False)
monkeypatch.delenv("CONDA_PREFIX", raising=False)
monkeypatch.delenv("PEP582_PACKAGES", raising=False)
Expand Down
Loading

0 comments on commit a475abe

Please sign in to comment.