Skip to content

Commit

Permalink
Merge pull request #516 from pdm-project:feature/egg-info-extracting
Browse files Browse the repository at this point in the history
Extract name from egg-info when eligible
  • Loading branch information
frostming authored Jun 23, 2021
2 parents e20eaa4 + 631d74a commit b54fabb
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 47 deletions.
1 change: 1 addition & 0 deletions news/441.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Extract package name from egg-info in filename when eligible. Remove the patching code of resolvelib's inner class.
8 changes: 4 additions & 4 deletions pdm.lock

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

28 changes: 13 additions & 15 deletions pdm/models/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
import sysconfig
import tempfile
from contextlib import contextmanager
from os import PathLike
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, Generator, Iterator, List, Optional
from typing import TYPE_CHECKING, Any, Generator, Iterator

from distlib.scripts import ScriptMaker
from pip._vendor import packaging, pkg_resources
Expand All @@ -26,6 +25,7 @@
get_sys_config_paths,
)
from pdm.models.pip_shims import misc, patch_bin_prefix, req_uninstall
from pdm.models.requirements import _egg_info_re
from pdm.utils import (
allow_all_wheels,
cached_property,
Expand All @@ -42,15 +42,13 @@
from pdm.models.python import PythonInfo
from pdm.project import Project

_egg_info_re = re.compile(r"([a-z0-9_.]+)-([a-z0-9_.!+-]+)", re.IGNORECASE)


class WorkingSet(collections.abc.Mapping):
"""A dict-like class that holds all installed packages in the lib directory."""

def __init__(
self,
paths: Optional[List[str]] = None,
paths: list[str] | None = None,
python: str = pkg_resources.PY_MAJOR,
):
self.env = pkg_resources.Environment(paths, python=python)
Expand Down Expand Up @@ -96,7 +94,7 @@ def __init__(self, project: Project) -> None:
self.project.sources, self.project.core.ui.verbosity >= termui.DETAIL
)

def get_paths(self) -> Dict[str, str]:
def get_paths(self) -> dict[str, str]:
"""Get paths like ``sysconfig.get_paths()`` for installation."""
paths = sysconfig.get_paths()
scripts = "Scripts" if os.name == "nt" else "bin"
Expand Down Expand Up @@ -140,7 +138,7 @@ def activate(self) -> Iterator:
misc.site_packages = _old_sitepackages
pkg_resources.working_set = _old_ws

def is_local(self, path: PathLike) -> bool:
def is_local(self, path: str) -> bool:
"""PEP 582 version of ``is_local()`` function."""
return misc.normalize_path(path).startswith(
misc.normalize_path(self.packages_path.as_posix())
Expand Down Expand Up @@ -192,7 +190,7 @@ def _get_source_dir(self, ireq: pip_shims.InstallRequirement) -> str:
@contextmanager
def get_finder(
self,
sources: Optional[List[Source]] = None,
sources: list[Source] | None = None,
ignore_requires_python: bool = False,
) -> Generator[pip_shims.PackageFinder, None, None]:
"""Return the package finder of given index sources.
Expand Down Expand Up @@ -222,7 +220,7 @@ def get_finder(
def build(
self,
ireq: pip_shims.InstallRequirement,
hashes: Optional[Dict[str, str]] = None,
hashes: dict[str, str] | None = None,
allow_all: bool = True,
) -> str:
"""Build egg_info directory for editable candidates and a wheel for others.
Expand Down Expand Up @@ -316,7 +314,7 @@ def get_working_set(self) -> WorkingSet:
)

@cached_property
def marker_environment(self) -> Dict[str, Any]:
def marker_environment(self) -> dict[str, Any]:
"""Get environment for marker evaluation"""
return get_pep508_environment(self.interpreter.executable)

Expand Down Expand Up @@ -347,7 +345,7 @@ def update_shebangs(self, new_path: str) -> None:
re.sub(rb"#!.+?python.*?$", shebang, child.read_bytes(), flags=re.M)
)

def _download_pip_wheel(self, path: os.PathLike):
def _download_pip_wheel(self, path: str | Path):
dirname = Path(tempfile.mkdtemp(prefix="pip-download-"))
try:
subprocess.check_call(
Expand All @@ -370,7 +368,7 @@ def _download_pip_wheel(self, path: os.PathLike):
shutil.rmtree(dirname, ignore_errors=True)

@cached_property
def pip_command(self) -> List[str]:
def pip_command(self) -> list[str]:
"""Get a pip command for this environment, and download one if not available.
Return a list of args like ['python', '-m', 'pip']
"""
Expand Down Expand Up @@ -399,17 +397,17 @@ class GlobalEnvironment(Environment):

is_global = True

def get_paths(self) -> Dict[str, str]:
def get_paths(self) -> dict[str, str]:
paths = get_sys_config_paths(self.interpreter.executable)
paths["prefix"] = paths["data"]
paths["headers"] = paths["include"]
return paths

def is_local(self, path: PathLike) -> bool:
def is_local(self, path: str) -> bool:
return misc.normalize_path(path).startswith(
misc.normalize_path(self.get_paths()["prefix"])
)

@property
def packages_path(self) -> Optional[Path]:
def packages_path(self) -> Path | None:
return None
15 changes: 11 additions & 4 deletions pdm/models/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@
)

VCS_SCHEMA = ("git", "hg", "svn", "bzr")
VCS_REQ = re.compile(
_vcs_req_re = re.compile(
rf"(?P<url>(?P<vcs>{'|'.join(VCS_SCHEMA)})\+[^\s;]+)(?P<marker>[\t ]*;[^\n]+)?"
)
FILE_REQ = re.compile(
_file_req_re = re.compile(
r"(?:(?P<url>\S+://[^\s\[\];]+)|"
r"(?P<path>(?:[^\s;\[\]]|\\ )*"
r"|'(?:[^']|\\')*'"
r"|\"(?:[^\"]|\\\")*\"))"
r"(?P<extras>\[[^\[\]]+\])?(?P<marker>[\t ]*;[^\n]+)?"
)
_egg_info_re = re.compile(r"([a-z0-9_.]+)-([a-z0-9_.!+-]+)", re.IGNORECASE)
T = TypeVar("T", bound="Requirement")


Expand Down Expand Up @@ -326,6 +327,12 @@ def _parse_name_from_url(self) -> None:
filename = os.path.basename(url_without_fragments(self.url))
if filename.endswith(".whl"):
self.name, self.version = parse_name_version_from_wheel(filename)
else:
match = _egg_info_re.match(filename)
# Filename is like `<name>-<version>.tar.gz`, where name will be
# extracted and version will be left to be determined from the metadata.
if match:
self.name = match.group(1)

def _check_installable(self) -> None:
assert self.path
Expand Down Expand Up @@ -427,15 +434,15 @@ def filter_requirements_with_extras(

def parse_requirement(line: str, editable: bool = False) -> Requirement:

m = VCS_REQ.match(line)
m = _vcs_req_re.match(line)
r: Requirement
if m is not None:
r = VcsRequirement.create(**m.groupdict())
else:
try:
package_req = PackageRequirement(line) # type: ignore
except (RequirementParseError, InvalidRequirement) as e:
m = FILE_REQ.match(line)
m = _file_req_re.match(line)
if m is None:
raise RequirementError(str(e)) from None
r = FileRequirement.create(**m.groupdict())
Expand Down
34 changes: 10 additions & 24 deletions pdm/resolver/core.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,23 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Dict, List, Optional, Tuple

from resolvelib.resolvers import Resolution
from typing import TYPE_CHECKING

from pdm.models.requirements import strip_extras

if TYPE_CHECKING:
from resolvelib.resolvers import Criterion, Resolver
from resolvelib.resolvers import Resolver

from pdm.models.candidates import Candidate
from pdm.models.requirements import Requirement
from pdm.models.specifiers import PySpecSet

_old_merge_into_criterion = Resolution._merge_into_criterion


# Monkey patch `resolvelib.resolvers.Resolution._merge_into_criterion`.
def _merge_into_criterion(
self, requirement: Requirement, parent: Optional[Candidate]
) -> Tuple[str, Criterion]:
identifier, crit = _old_merge_into_criterion(self, requirement, parent)

if identifier.startswith(":empty:"):
# For local packages, name is only available after candidate is resolved
identifier = self._p.identify(requirement)
return identifier, crit


Resolution._merge_into_criterion = _merge_into_criterion
del _merge_into_criterion


def resolve(
resolver: Resolver,
requirements: List[Requirement],
requirements: list[Requirement],
requires_python: PySpecSet,
max_rounds: int = 10000,
) -> Tuple[Dict[str, Candidate], Dict[str, List[Requirement]], Dict[str, str]]:
) -> tuple[dict[str, Candidate], dict[str, list[Requirement]], dict[str, str]]:
"""Core function to perform the actual resolve process.
Return a tuple containing 3 items:
Expand All @@ -52,6 +32,12 @@ def resolve(
for key, candidate in list(result.mapping.items()):
if key is None:
continue
# For source distribution whose name can only be determined after it is built,
# the key in the resolution map should be updated.
if key.startswith(":empty:"):
new_key = provider.identify(candidate)
mapping[new_key] = mapping.pop(key)
key = new_key
# Root requires_python doesn't participate in the metaset resolving,
# now check it!
candidate_requires = provider.requires_python_collection[strip_extras(key)[0]]
Expand Down

0 comments on commit b54fabb

Please sign in to comment.