Skip to content

Commit

Permalink
refactor: project files (#1499)
Browse files Browse the repository at this point in the history
* refactor: project files

* ensure the file exists

* add news

* fix tests

* update test cases

* reload the lockfile
  • Loading branch information
frostming authored Nov 8, 2022
1 parent e480d2c commit 332ff64
Show file tree
Hide file tree
Showing 35 changed files with 534 additions and 540 deletions.
1 change: 1 addition & 0 deletions news/1499.refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactor the project module, extract the TOML reading and writing related logic into classes.
25 changes: 10 additions & 15 deletions src/pdm/cli/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def resolve_candidates_from_lockfile(

def check_lockfile(project: Project, raise_not_exist: bool = True) -> str | None:
"""Check if the lock file exists and is up to date. Return the update strategy."""
if not project.lockfile_file.exists():
if not project.lockfile.exists:
if raise_not_exist:
raise ProjectError("Lock file does not exist, nothing to install")
project.core.ui.echo("Lock file does not exist", style="warning", err=True)
Expand Down Expand Up @@ -249,7 +249,7 @@ def do_add(
if (
group == "default"
or not dev
and group not in project.tool_settings.get("dev-dependencies", {})
and group not in project.pyproject.settings.get("dev-dependencies", {})
):
if editables:
raise PdmUsageError(
Expand Down Expand Up @@ -295,7 +295,7 @@ def do_add(
save_version_specifiers({group: deps_to_update}, resolved, save)
if not dry_run:
project.add_dependencies(deps_to_update, group, dev)
project.write_lockfile(project.lockfile, False)
project.write_lockfile(project.lockfile._data, False)
hooks.try_emit("post_lock", resolution=resolved, dry_run=dry_run)
_populate_requirement_names(group_deps)
if sync:
Expand Down Expand Up @@ -400,8 +400,7 @@ def do_update(
save_version_specifiers(updated_deps, resolved, save)
for group, deps in updated_deps.items():
project.add_dependencies(deps, group, dev or False)
lockfile = project.lockfile
project.write_lockfile(lockfile, False)
project.write_lockfile(project.lockfile._data, False)
if sync or dry_run:
do_sync(
project,
Expand Down Expand Up @@ -463,7 +462,7 @@ def do_remove(
cast(Array, deps).multiline(True)

if not dry_run:
project.write_pyproject()
project.pyproject.write()
do_lock(project, "reuse", dry_run=dry_run, hooks=hooks)
if sync:
do_sync(
Expand Down Expand Up @@ -557,12 +556,8 @@ def do_init(
readme.write_text(f"# {name}\n\n{description}\n")
data["project"]["readme"] = readme.name # type: ignore
get_specifier(python_requires)
if not project.pyproject:
project._pyproject = data
else:
project._pyproject["project"] = data["project"] # type: ignore
project._pyproject["build-system"] = data["build-system"] # type: ignore
project.write_pyproject()
project.pyproject._data.update(data)
project.pyproject.write()
hooks.try_emit("post_init")


Expand Down Expand Up @@ -701,7 +696,7 @@ def do_import(
if options is None:
options = Namespace(dev=False, group=None)
project_data, settings = FORMATS[key].convert(project, filename, options)
pyproject = project.pyproject or tomlkit.document()
pyproject = project.pyproject._data

if "tool" not in pyproject or "pdm" not in pyproject["tool"]: # type: ignore
pyproject.setdefault("tool", {})["pdm"] = tomlkit.table()
Expand All @@ -727,15 +722,15 @@ def do_import(
"requires": ["pdm-pep517>=1.0.0"],
"build-backend": "pdm.pep517.api",
}
project.pyproject = cast(dict, pyproject)

if "requires-python" not in pyproject["project"]:
python_version = f"{project.python.major}.{project.python.minor}"
pyproject["project"]["requires-python"] = f">={python_version}"
project.core.ui.echo(
"The project's [primary]requires-python[/] has been set to [primary]>="
f"{python_version}[/]. You can change it later if necessary."
)
project.write_pyproject()
project.pyproject.write()


def ask_for_import(project: Project) -> None:
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
from pdm.cli.commands.venv.utils import get_venv_python

hooks = HookManager(project, options.skip)
if project.pyproject_file.exists():
if project.pyproject.exists:
project.core.ui.echo(
"pyproject.toml already exists, update it now.", style="primary"
)
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
)

def handle(self, project: Project, options: argparse.Namespace) -> None:
if not project.meta and termui.is_interactive():
if not project.pyproject.is_valid and termui.is_interactive():
actions.ask_for_import(project)

hooks = HookManager(project, options.skip)
Expand Down
6 changes: 4 additions & 2 deletions src/pdm/cli/commands/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ def handle(self, project: Project, options: argparse.Namespace) -> None:
else:
if not project.name:
raise PdmUsageError("This project is not a package")
metadata = project.meta
package = normalize_name(metadata.name)
package = normalize_name(project.name)
metadata = (
project.make_self_candidate().prepare(project.environment).metadata
)
latest_stable = None
assert metadata
project_info = ProjectInfo(metadata)
Expand Down
14 changes: 7 additions & 7 deletions src/pdm/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
)
from pdm.models.specifiers import get_specifier
from pdm.project import Project
from pdm.utils import is_path_relative_to, url_to_path
from pdm.utils import is_path_relative_to, normalize_name, url_to_path

if TYPE_CHECKING:
from resolvelib.resolvers import RequirementInformation, ResolutionImpossible
Expand Down Expand Up @@ -310,8 +310,8 @@ def add_package_to_reverse_tree(
def package_is_project(package: Package, project: Project) -> bool:
return (
not project.environment.is_global
and bool(project.name)
and package.name == project.meta.project_name.lower()
and project.name is not None
and package.name == normalize_name(project.name)
)


Expand Down Expand Up @@ -526,11 +526,11 @@ def save_version_specifiers(

def check_project_file(project: Project) -> None:
"""Check the existence of the project file and throws an error on failure."""
if not project.meta:
if not project.pyproject.is_valid:
raise ProjectError(
"The pyproject.toml has not been initialized yet. You can do this "
"by running [success]`pdm init`[/]."
)
) from None


def find_importable_files(project: Project) -> Iterable[tuple[str, Path]]:
Expand Down Expand Up @@ -630,8 +630,8 @@ def translate_groups(
project: Project, default: bool, dev: bool, groups: Iterable[str]
) -> list[str]:
"""Translate default, dev and groups containing ":all" into a list of groups"""
optional_groups = set(project.meta.optional_dependencies or [])
dev_groups = set(project.tool_settings.get("dev-dependencies", []))
optional_groups = set(project.pyproject.metadata.get("optional-dependencies", {}))
dev_groups = set(project.pyproject.settings.get("dev-dependencies", {}))
groups_set = set(groups)
if dev is None:
dev = True
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def ensure_project(
)

if getattr(options, "lockfile", None):
project.lockfile_file = options.lockfile
project.set_lockfile(options.lockfile)
return project

def create_project(
Expand Down
2 changes: 1 addition & 1 deletion src/pdm/formats/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def export(
for item in sorted(set(candidate.hashes.values())): # type: ignore
lines.append(f" \\\n --hash={item}")
lines.append("\n")
sources = project.tool_settings.get("source", [])
sources = project.pyproject.settings.get("source", [])
for source in sources:
url = expand_env_vars_in_auth(source["url"])
prefix = "--index-url" if source["name"] == "pypi" else "--extra-index-url"
Expand Down
8 changes: 4 additions & 4 deletions src/pdm/installers/synchronizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from pdm.models.candidates import Candidate, make_candidate
from pdm.models.environment import Environment
from pdm.models.requirements import Requirement, parse_requirement, strip_extras
from pdm.utils import is_editable
from pdm.utils import is_editable, normalize_name

if TYPE_CHECKING:
from rich.progress import Progress
Expand Down Expand Up @@ -130,8 +130,8 @@ def __init__(
keys = []
if (
self.install_self
and getattr(
self.environment.project.meta.config, "editable_backend", "path"
and self.environment.project.pyproject.settings.get("build", {}).get(
"editable_backend", "path"
)
== "editables"
and "editables" not in candidates
Expand Down Expand Up @@ -174,7 +174,7 @@ def get_manager(self) -> InstallManager:
def self_key(self) -> str | None:
name = self.environment.project.name
if name:
return self.environment.project.meta.project_name.lower()
return normalize_name(name)
return name

def _should_update(self, dist: Distribution, can: Candidate) -> bool:
Expand Down
28 changes: 15 additions & 13 deletions src/pdm/models/candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from pdm import termui
from pdm.builders import EditableBuilder, WheelBuilder
from pdm.compat import importlib_metadata as im
from pdm.exceptions import BuildError, CandidateNotFound, InvalidPyVersion
from pdm.exceptions import BuildError, CandidateNotFound, InvalidPyVersion, ProjectError
from pdm.models.requirements import (
FileRequirement,
Requirement,
Expand All @@ -25,7 +25,7 @@
)
from pdm.models.setup import Setup
from pdm.models.specifiers import PySpecSet
from pdm.project.metadata import MutableMetadata, SetupDistribution
from pdm.project.project_file import PyProject
from pdm.utils import (
cached_property,
cd,
Expand Down Expand Up @@ -451,11 +451,13 @@ def prepare_metadata(self) -> im.Distribution:
pyproject_toml = self._unpacked_dir / "pyproject.toml"
if pyproject_toml.exists():
try:
metadata = MutableMetadata.from_file(pyproject_toml)
except ValueError:
metadata = PyProject(
pyproject_toml, ui=self.environment.project.core.ui
).metadata.unwrap()
except ProjectError:
termui.logger.warn("Failed to parse pyproject.toml")
else:
dynamic_fields = metadata.dynamic or []
dynamic_fields = metadata.get("dynamic", [])
# Use the parse result only when all are static
if set(dynamic_fields).isdisjoint(
{
Expand All @@ -467,14 +469,14 @@ def prepare_metadata(self) -> im.Distribution:
}
):
setup = Setup(
name=metadata.name,
summary=metadata.description,
version=metadata.version,
install_requires=metadata.dependencies or [],
extras_require=metadata.optional_dependencies or {},
python_requires=metadata.requires_python or None,
name=metadata.get("name"),
summary=metadata.get("description"),
version=metadata.get("version"),
install_requires=metadata.get("dependencies", []),
extras_require=metadata.get("optional-dependencies", {}),
python_requires=metadata.get("requires-python"),
)
return SetupDistribution(setup)
return setup.as_dist()
# If all fail, try building the source to get the metadata
builder = EditableBuilder if self.req.editable else WheelBuilder
try:
Expand All @@ -495,7 +497,7 @@ def prepare_metadata(self) -> im.Distribution:
termui.logger.warn(message)
warnings.warn(message, RuntimeWarning)
setup = Setup()
return SetupDistribution(setup)
return setup.as_dist()
else:
return im.PathDistribution(Path(cast(str, self._metadata_dir)))

Expand Down
6 changes: 3 additions & 3 deletions src/pdm/models/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ def get_finder(
ignore_compatibility=ignore_compatibility,
no_binary=os.getenv("PDM_NO_BINARY", "").split(","),
only_binary=os.getenv("PDM_ONLY_BINARY", "").split(","),
respect_source_order=self.project.tool_settings.get("resolution", {}).get(
"respect-source-order", False
),
respect_source_order=self.project.pyproject.settings.get(
"resolution", {}
).get("respect-source-order", False),
verbosity=self.project.core.ui.verbosity,
)
try:
Expand Down
29 changes: 2 additions & 27 deletions src/pdm/models/project_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@
from email.message import Message
from typing import TYPE_CHECKING, Any, Iterator, cast

from pdm.pep517.metadata import Metadata

if TYPE_CHECKING:
from pdm.compat import Distribution


class ProjectInfo:
def __init__(self, metadata: Distribution | Metadata) -> None:
def __init__(self, metadata: Distribution) -> None:
self.latest_stable_version = ""
self.installed_version = ""
if isinstance(metadata, Metadata):
self._parsed = self._parse_self(metadata)
else:
self._parsed = self._parse(metadata)
self._parsed = self._parse(metadata)

def _parse(self, data: Distribution) -> dict[str, Any]:
metadata = cast(Message, data.metadata)
Expand Down Expand Up @@ -48,26 +43,6 @@ def _parse(self, data: Distribution) -> dict[str, Any]:
"project-urls": [": ".join(parts) for parts in project_urls.items()],
}

def _parse_self(self, metadata: Metadata) -> dict[str, Any]:
license_expression = getattr(metadata, "license_expression", None)
if license_expression is None:
license_expression = getattr(metadata, "license", "")
return {
"name": str(metadata.name),
"version": str(metadata.version),
"summary": str(metadata.description),
"author": str(metadata.author),
"email": str(metadata.author_email),
"license": str(license_expression),
"requires-python": str(metadata.requires_python),
"platform": "",
"keywords": ", ".join(metadata.keywords or []),
"homepage": "",
"project-urls": [
": ".join(parts) for parts in (metadata.project_urls or {}).items()
],
}

def __getitem__(self, key: str) -> Any:
return self._parsed[key]

Expand Down
Loading

0 comments on commit 332ff64

Please sign in to comment.