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

new command: pdm use #24

Merged
merged 2 commits into from
Feb 9, 2020
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,4 @@ dmypy.json
caches/
.idea/
__pypackages__
.pdm.toml
36 changes: 35 additions & 1 deletion pdm/cli/actions.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import itertools
import shutil
from pathlib import Path
from typing import Dict, Iterable, Optional, Sequence

from pkg_resources import safe_name

import click
import halo
import pythonfinder
import tomlkit
from pdm.builders import SdistBuilder, WheelBuilder
from pdm.context import context
from pdm.exceptions import ProjectError
from pdm.exceptions import NoPythonVersion, ProjectError
from pdm.installers import Synchronizer, format_dist
from pdm.models.candidates import Candidate, identify
from pdm.models.requirements import parse_requirement, strip_extras
from pdm.models.specifiers import bump_version, get_specifier
from pdm.project import Project
from pdm.resolver import BaseProvider, EagerUpdateProvider, ReusePinProvider, resolve
from pdm.resolver.reporters import SpinnerReporter
from pdm.utils import get_python_version


def format_lockfile(mapping, fetched_dependencies, summary_collection):
Expand Down Expand Up @@ -386,3 +389,34 @@ def do_init(
project._pyproject.setdefault("tool", {})["pdm"] = data["tool"]["pdm"]
project._pyproject["build-system"] = data["build-system"]
project.write_pyproject()


def do_use(project: Project, python: str) -> None:
"""Use the specified python version and save in project config.
The python can be a version string or interpreter path.
"""
if Path(python).is_absolute():
python_path = python
else:
python_path = shutil.which(python)
if not python_path:
finder = pythonfinder.Finder()
try:
python_path = finder.find_python_version(python).path.as_posix()
except AttributeError:
raise NoPythonVersion(f"Python {python} is not found on the system.")

python_version = ".".join(map(str, get_python_version(python_path)))
if not project.python_requires.contains(python_version):
raise NoPythonVersion(
"The target Python version {} doesn't satisfy "
"the Python requirement: {}".format(python_version, project.python_requires)
)
context.io.echo(
"Using Python interpreter: {} ({})".format(
context.io.green(python_path), python_version
)
)

project.config["python"] = Path(python_path).as_posix()
project.config.save_config()
8 changes: 8 additions & 0 deletions pdm/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,11 @@ def init(project):
author = click.prompt(f"Author name", default=git_user)
email = click.prompt(f"Author email", default=git_email)
actions.do_init(project, name, version, license, author, email)


@cli.command()
@click.argument("python")
@pass_project
def use(project, python):
"""Use the given python version as base interpreter."""
actions.do_use(project, python)
19 changes: 16 additions & 3 deletions pdm/models/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def __init__(self, python_requires: PySpecSet, config: Config) -> None:
@cached_property
def python_executable(self) -> str:
"""Get the Python interpreter path."""
path = None
if self.config["python"]:
path = self.config["python"]
try:
Expand All @@ -96,11 +97,23 @@ def python_executable(self) -> str:
for python in finder.find_all_python_versions():
version = ".".join(map(str, get_python_version(python.path.as_posix())))
if self.python_requires.contains(version):
return python.path.as_posix()
path = python.path.as_posix()
if self.python_requires.contains(".".join(map(str, sys.version_info[:3]))):
return sys.executable
path = sys.executable
if path:
python_version = ".".join(map(str, get_python_version(path)))
context.io.echo(
"Using Python interpreter: {} ({})".format(
context.io.green(path), python_version
)
)
self.config["python"] = Path(path).as_posix()
self.config.save_config()
return path
raise NoPythonVersion(
"No python matching {} is found on the system.".format(self.python_requires)
"No Python that satisfies {} is found on the system.".format(
self.python_requires
)
)

def get_paths(self) -> Dict[str, str]:
Expand Down
2 changes: 1 addition & 1 deletion pdm/project/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def save_config(self, is_global: bool = False) -> None:
temp = temp.setdefault(part, {})
temp[last] = value

with file_path.open(encoding="utf-8") as fp:
with file_path.open("w", encoding="utf-8") as fp:
fp.write(tomlkit.dumps(toml_data))
self._dirty.clear()

Expand Down
18 changes: 18 additions & 0 deletions tests/cli/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import functools
import os
import shutil
from pathlib import Path

import pytest
from click.testing import CliRunner
Expand Down Expand Up @@ -91,3 +93,19 @@ def test_uncaught_error(invoke, mocker):

result = invoke(["list", "-v"])
assert isinstance(result.exception, RuntimeError)


def test_use_command(project, invoke):
python_path = Path(shutil.which("python")).as_posix()
result = invoke(["use", "python"], obj=project)
assert result.exit_code == 0
config_content = project.root.joinpath(".pdm.toml").read_text()
assert python_path in config_content

result = invoke(["use", python_path], obj=project)
assert result.exit_code == 0

project.tool_settings["python_requires"] = ">=3.6"
project.write_pyproject()
result = invoke(["use", "2.7"], obj=project)
assert result.exit_code == 1