Skip to content

Commit

Permalink
feat: add a new setting package-type to distinguish application and…
Browse files Browse the repository at this point in the history
… library (#2394)
  • Loading branch information
frostming committed Nov 23, 2023
1 parent d37a7af commit 9d2d622
Show file tree
Hide file tree
Showing 11 changed files with 40 additions and 29 deletions.
1 change: 1 addition & 0 deletions news/2394.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
We now use the `package-type` field in the `tool.pdm` table to differentiate between library and application projects.
2 changes: 1 addition & 1 deletion src/pdm/cli/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def do_sync(
clean=clean,
dry_run=dry_run,
no_editable=no_editable,
install_self=not no_self and bool(project.name),
install_self=not no_self and project.is_library,
reinstall=reinstall,
only_keep=only_keep,
fail_fast=fail_fast,
Expand Down
4 changes: 2 additions & 2 deletions src/pdm/cli/commands/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ def do_add(
if editables:
raise PdmUsageError("Cannot add editables to the default or optional dependency group")
for r in [parse_requirement(line, True) for line in editables] + [parse_requirement(line) for line in packages]:
if project.name and normalize_name(project.name) == r.key and not r.extras:
project.core.ui.warn(f"Package [req]{project.name}[/] is the project itself.")
if project.is_library and normalize_name(name := project.name) == r.key and not r.extras:
project.core.ui.warn(f"Package [req]{name}[/] is the project itself.")
continue
if r.is_file_or_url:
r.relocate(project.backend) # type: ignore[attr-defined]
Expand Down
12 changes: 7 additions & 5 deletions src/pdm/cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ def get_metadata_from_input(self, project: Project, options: argparse.Namespace)
from pdm.formats.base import array_of_inline_tables, make_array, make_inline_table

is_library = options.lib
name = self.ask("Project name", project.root.name)
version = self.ask("Project version", "0.1.0")
if not is_library and self.interactive:
is_library = termui.confirm(
"Is the project a library that is installable?\n"
Expand All @@ -119,8 +121,6 @@ def get_metadata_from_input(self, project: Project, options: argparse.Namespace)
build_backend: type[BuildBackend] | None = None
python = project.python
if is_library:
name = self.ask_project(project)
version = self.ask("Project version", "0.1.0")
description = self.ask("Project description", "")
if options.backend:
build_backend = get_backend(options.backend)
Expand All @@ -141,7 +141,7 @@ def get_metadata_from_input(self, project: Project, options: argparse.Namespace)
build_backend = DEFAULT_BACKEND
default_python_requires = f">={python.major}.{python.minor}"
else:
name, version, description = "", "", ""
description = ""
default_python_requires = f"=={python.major}.{python.minor}.*"
license = self.ask("License(SPDX name)", "MIT")

Expand All @@ -154,16 +154,18 @@ def get_metadata_from_input(self, project: Project, options: argparse.Namespace)
"project": {
"name": name,
"version": version,
"description": description,
"authors": array_of_inline_tables([{"name": author, "email": email}]),
"license": make_inline_table({"text": license}),
"dependencies": make_array([], True),
},
"tool": {"pdm": {"package-type": "library" if is_library else "application"}},
}

if python_requires and python_requires != "*":
get_specifier(python_requires)
data["project"]["requires-python"] = python_requires
data["project"]["requires-python"] = python_requires # type: ignore[index]
if description:
data["project"]["description"] = description # type: ignore[index]
if build_backend is not None:
data["build-system"] = cast(dict, build_backend.build_system())

Expand Down
4 changes: 2 additions & 2 deletions src/pdm/cli/commands/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
latest_stable = next(filter(filter_stable, best_match.applicable), None)
metadata = latest.prepare(project.environment).metadata
else:
if not project.name:
raise PdmUsageError("This project is not a package")
if not project.is_library:
raise PdmUsageError("This project is not a library")
package = normalize_name(project.name)
metadata = project.make_self_candidate(False).prepare(project.environment).prepare_metadata(True)
latest_stable = None
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ def add_package_to_reverse_tree(


def package_is_project(package: Package, project: Project) -> bool:
return project.name is not None and package.name == normalize_name(project.name)
return project.is_library and package.name == normalize_name(project.name)


def _format_forward_dependency_graph(
Expand Down
5 changes: 2 additions & 3 deletions src/pdm/models/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def _find_candidates(self, requirement: Requirement, minimal_version: bool) -> I
def is_this_package(self, requirement: Requirement) -> bool:
"""Whether the requirement is the same as this package"""
project = self.environment.project
return requirement.is_named and project.name is not None and requirement.key == normalize_name(project.name)
return requirement.is_named and project.is_library and requirement.key == normalize_name(project.name)

def make_this_candidate(self, requirement: Requirement) -> Candidate:
"""Make a candidate for this package.
Expand All @@ -147,7 +147,6 @@ def make_this_candidate(self, requirement: Requirement) -> Candidate:
from unearth import Link

project = self.environment.project
assert project.name
link = Link.from_path(project.root)
candidate = make_candidate(requirement, project.name, link=link)
candidate.prepare(self.environment).metadata
Expand Down Expand Up @@ -287,7 +286,7 @@ def _get_dependency_from_local_package(self, candidate: Candidate) -> CandidateI
"""Adds the local package as a candidate only if the candidate
name is the same as the local package."""
project = self.environment.project
if not project.name or candidate.name != project.name:
if not project.is_library or candidate.name != project.name:
raise CandidateInfoNotFound(candidate) from None

reqs = project.pyproject.metadata.get("dependencies", [])
Expand Down
6 changes: 5 additions & 1 deletion src/pdm/project/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def project_config(self) -> Config:
return config

@property
def name(self) -> str | None:
def name(self) -> str:
return self.pyproject.metadata.get("name")

@property
Expand Down Expand Up @@ -679,3 +679,7 @@ def meta(self) -> dict[str, Any]:
def tool_settings(self) -> dict[str, Any]:
deprecation_warning("project.tool_settings is deprecated, use project.pyproject.settings instead", stacklevel=2)
return self.pyproject.settings

@property
def is_library(self) -> bool:
return bool(self.name) and self.pyproject.settings.get("package-type", "library") == "library"
4 changes: 3 additions & 1 deletion src/pdm/resolver/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ def resolve(
mapping = cast(Dict[str, Candidate], result.mapping)
mapping.pop("python", None)

local_name = normalize_name(repository.environment.project.name) if repository.environment.project.name else None
local_name = (
normalize_name(repository.environment.project.name) if repository.environment.project.is_library else None
)
for key, candidate in list(result.mapping.items()):
if key is None:
continue
Expand Down
27 changes: 15 additions & 12 deletions tests/cli/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


def test_init_validate_python_requires(project_no_init, pdm):
result = pdm(["init"], input="\n\n\n\n\n3.7\n", obj=project_no_init)
result = pdm(["init"], input="\n\n\n\n\n\n\n3.7\n", obj=project_no_init)
assert result.exit_code != 0
assert "InvalidSpecifier" in result.stderr

Expand All @@ -18,19 +18,20 @@ def test_init_command(project_no_init, pdm, mocker):
"pdm.cli.commands.init.get_user_email_from_git",
return_value=("Testing", "[email protected]"),
)
pdm(["init"], input="\n\n\n\n\n\n", strict=True, obj=project_no_init)
pdm(["init"], input="\ntest-project\n\n\n\n\n\n\n", strict=True, obj=project_no_init)
python_version = f"{project_no_init.python.major}.{project_no_init.python.minor}"
data = {
"project": {
"authors": [{"email": "[email protected]", "name": "Testing"}],
"dependencies": [],
"description": "",
"description": "Default template for PDM package",
"license": {"text": "MIT"},
"name": "",
"name": "test-project",
"requires-python": f"=={python_version}.*",
"readme": "README.md",
"version": "",
"version": "0.1.0",
},
"tool": {"pdm": {"package-type": "application"}},
}

with open(project_no_init.root.joinpath("pyproject.toml"), "rb") as fp:
Expand All @@ -44,7 +45,7 @@ def test_init_command_library(project_no_init, pdm, mocker):
)
result = pdm(
["init"],
input="\ny\ntest-project\n\nTest Project\n1\n\n\n\n\n",
input="\ntest-project\n\ny\nTest Project\n1\n\n\n\n\n",
obj=project_no_init,
)
assert result.exit_code == 0
Expand All @@ -61,6 +62,7 @@ def test_init_command_library(project_no_init, pdm, mocker):
"version": "0.1.0",
},
"build-system": {"build-backend": "setuptools.build_meta", "requires": ["setuptools>=61", "wheel"]},
"tool": {"pdm": {"package-type": "library"}},
}

with open(project_no_init.root.joinpath("pyproject.toml"), "rb") as fp:
Expand Down Expand Up @@ -89,13 +91,14 @@ def test_init_non_interactive(project_no_init, pdm, mocker):
"project": {
"authors": [{"email": "[email protected]", "name": "Testing"}],
"dependencies": [],
"description": "",
"description": "Default template for PDM package",
"license": {"text": "MIT"},
"name": "",
"name": project_no_init.root.name,
"requires-python": f"=={python_version}.*",
"readme": "README.md",
"version": "",
"version": "0.1.0",
},
"tool": {"pdm": {"package-type": "application"}},
}

with open(project_no_init.root.joinpath("pyproject.toml"), "rb") as fp:
Expand All @@ -105,7 +108,7 @@ def test_init_non_interactive(project_no_init, pdm, mocker):
def test_init_auto_create_venv(project_no_init, pdm, mocker):
mocker.patch("pdm.models.python.PythonInfo.get_venv", return_value=None)
project_no_init.project_config["python.use_venv"] = True
result = pdm(["init"], input="\n\n\n\n\n\n\n", obj=project_no_init)
result = pdm(["init"], input="\n\n\n\n\n\n\n\n\n", obj=project_no_init)
assert result.exit_code == 0
assert project_no_init.python.executable.parent.parent == project_no_init.root / ".venv"
assert ".pdm-python" in (project_no_init.root / ".gitignore").read_text()
Expand All @@ -116,7 +119,7 @@ def test_init_auto_create_venv_specify_python(project_no_init, pdm, mocker):
project_no_init.project_config["python.use_venv"] = True
result = pdm(
["init", f"--python={PYTHON_VERSION}"],
input="\n\n\n\n\n\n",
input="\n\n\n\n\n\n\n\n",
obj=project_no_init,
)
assert result.exit_code == 0
Expand All @@ -127,7 +130,7 @@ def test_init_auto_create_venv_answer_no(project_no_init, pdm, mocker):
mocker.patch("pdm.models.python.PythonInfo.get_venv", return_value=None)
creator = mocker.patch("pdm.cli.commands.venv.backends.Backend.create")
project_no_init.project_config["python.use_venv"] = True
result = pdm(["init"], input="\nn\n\n\n\n\n\n\n", obj=project_no_init)
result = pdm(["init"], input="\nn\n\n\n\n\n\n\n\n\n", obj=project_no_init)
assert result.exit_code == 0
creator.assert_not_called()
assert project_no_init.python.executable.parent.parent != project_no_init.root / ".venv"
2 changes: 1 addition & 1 deletion tests/test_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
def test_post_init_signal(project_no_init, pdm):
mock_handler = mock.Mock()
with signals.post_init.connected_to(mock_handler):
result = pdm(["init"], input="\n\n\n\n\n\n", obj=project_no_init)
result = pdm(["init"], input="\n\n\n\n\n\n\n\n", obj=project_no_init)
assert result.exit_code == 0
mock_handler.assert_called_once_with(project_no_init, hooks=mock.ANY)

Expand Down

0 comments on commit 9d2d622

Please sign in to comment.