未验证 提交 d2ca9a38 编写于 作者: F Frost Ming

Expand PROJECT_ROOT on build

上级 2301ccda
from typing import List, Optional, Tuple from typing import List
from pdm._types import Source from pdm._types import Source
from pdm.exceptions import PdmException from pdm.exceptions import PdmException
from pdm.models.pip_shims import MultiDomainBasicAuth from pdm.models.pip_shims import MultiDomainBasicAuth
from pdm.utils import expand_env_vars
class PdmBasicAuth(MultiDomainBasicAuth): class PdmBasicAuth(MultiDomainBasicAuth):
"""A custom auth class that differs from Pip's implementation in the """A custom auth class that differs from Pip's implementation in the
following ways: following ways:
1. It expands env variables in URL auth. - It shows an error message when credentials are not provided or correect.
2. It shows an error message when credentials are not provided or correect.
""" """
def _get_url_and_credentials(
self, original_url: str
) -> Tuple[str, Optional[str], Optional[str]]:
url, username, password = super()._get_url_and_credentials(original_url)
if username:
username = expand_env_vars(username)
if password:
password = expand_env_vars(password)
return url, username, password
def handle_401(self, resp, **kwargs): def handle_401(self, resp, **kwargs):
if resp.status_code == 401 and not self.prompting: if resp.status_code == 401 and not self.prompting:
raise PdmException( raise PdmException(
......
...@@ -13,7 +13,7 @@ from pdm.iostream import stream ...@@ -13,7 +13,7 @@ from pdm.iostream import stream
from pdm.models import pip_shims from pdm.models import pip_shims
from pdm.models.markers import Marker from pdm.models.markers import Marker
from pdm.models.requirements import Requirement, filter_requirements_with_extras from pdm.models.requirements import Requirement, filter_requirements_with_extras
from pdm.utils import cached_property from pdm.utils import cached_property, path_replace
if TYPE_CHECKING: if TYPE_CHECKING:
from distlib.metadata import Metadata from distlib.metadata import Metadata
...@@ -230,13 +230,16 @@ class Candidate: ...@@ -230,13 +230,16 @@ class Candidate:
"marker": str(self.marker).replace('"', "'") if self.marker else None, "marker": str(self.marker).replace('"', "'") if self.marker else None,
"editable": self.req.editable, "editable": self.req.editable,
} }
project_root = self.environment.project.root.as_posix().lstrip("/")
if self.req.is_vcs: if self.req.is_vcs:
result.update({self.req.vcs: self.req.repo, "revision": self.revision}) result.update({self.req.vcs: self.req.repo, "revision": self.revision})
elif not self.req.is_named: elif not self.req.is_named:
if self.req.path: if self.req.is_file_or_url and self.req.is_local_dir:
result.update(path=self.req.str_path) result.update(path=path_replace(project_root, ".", self.req.str_path))
else: else:
result.update(url=self.req.url) result.update(
url=path_replace(project_root, "${PROJECT_ROOT}", self.req.url)
)
return {k: v for k, v in result.items() if v} return {k: v for k, v in result.items() if v}
def format(self) -> str: def format(self) -> str:
......
...@@ -258,7 +258,9 @@ class Environment: ...@@ -258,7 +258,9 @@ class Environment:
""" """
if sources is None: if sources is None:
sources = self.project.sources sources = self.project.sources
sources = sources or [] for source in sources:
source["url"] = expand_env_vars_in_auth(source["url"])
python_version, _ = get_python_version(self.python_executable, digits=2) python_version, _ = get_python_version(self.python_executable, digits=2)
finder = get_finder( finder = get_finder(
sources, sources,
...@@ -321,10 +323,15 @@ class Environment: ...@@ -321,10 +323,15 @@ class Environment:
only_download = True only_download = True
if hashes: if hashes:
ireq.hash_options = convert_hashes(hashes) ireq.hash_options = convert_hashes(hashes)
ireq.link = pip_shims.Link(
expand_env_vars_in_auth(
ireq.link.url.replace(
"${PROJECT_ROOT}", self.project.root.as_posix().lstrip("/")
)
)
)
if not (ireq.editable and ireq.req.is_local_dir): if not (ireq.editable and ireq.req.is_local_dir):
downloader = pip_shims.Downloader(finder.session, "off") downloader = pip_shims.Downloader(finder.session, "off")
if ireq.link.is_vcs:
ireq.link = pip_shims.Link(expand_env_vars_in_auth(ireq.link.url))
downloaded = pip_shims.unpack_url( downloaded = pip_shims.unpack_url(
ireq.link, ireq.link,
ireq.source_dir, ireq.source_dir,
......
...@@ -243,7 +243,9 @@ class Requirement: ...@@ -243,7 +243,9 @@ class Requirement:
class FileRequirement(Requirement): class FileRequirement(Requirement):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.path = Path(self.path) if self.path else None self.path = (
Path(str(self.path).replace("${PROJECT_ROOT}", ".")) if self.path else None
)
self.version = None self.version = None
self._parse_url() self._parse_url()
if self.path and not self.path.exists(): if self.path and not self.path.exists():
...@@ -276,10 +278,19 @@ class FileRequirement(Requirement): ...@@ -276,10 +278,19 @@ class FileRequirement(Requirement):
def _parse_url(self) -> None: def _parse_url(self) -> None:
if not self.url: if not self.url:
if self.path: if self.path:
self.url = path_to_url(self.path.as_posix()) self.url = path_to_url(
self.path.as_posix().replace("${PROJECT_ROOT}", ".")
)
else: else:
try: try:
self.path = Path(url_to_path(self.url)) self.path = Path(
url_to_path(
self.url.replace(
"${PROJECT_ROOT}",
Path(".").absolute().as_posix().lstrip("/"),
)
)
)
except AssertionError: except AssertionError:
pass pass
self._parse_name_from_url() self._parse_name_from_url()
...@@ -361,7 +372,6 @@ class VcsRequirement(FileRequirement): ...@@ -361,7 +372,6 @@ class VcsRequirement(FileRequirement):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.repo = None self.repo = None
super().__init__(**kwargs) super().__init__(**kwargs)
self._parse_url()
@classmethod @classmethod
def parse(cls, line: str, parsed: Dict[str, str]) -> "VcsRequirement": def parse(cls, line: str, parsed: Dict[str, str]) -> "VcsRequirement":
......
...@@ -25,6 +25,7 @@ from pdm.project.metadata import MutableMetadata as Metadata ...@@ -25,6 +25,7 @@ from pdm.project.metadata import MutableMetadata as Metadata
from pdm.utils import ( from pdm.utils import (
atomic_open_for_write, atomic_open_for_write,
cached_property, cached_property,
cd,
find_project_root, find_project_root,
get_venv_python, get_venv_python,
setdefault, setdefault,
...@@ -170,11 +171,15 @@ class Project: ...@@ -170,11 +171,15 @@ class Project:
else: else:
deps = metadata.get("optional-dependencies", {}).get(section, []) deps = metadata.get("optional-dependencies", {}).get(section, [])
result = {} result = {}
for line in deps: with cd(self.root):
req = parse_requirement(line) for line in deps:
req.from_section = section or "default" if line.startswith("-e "):
# make editable packages behind normal ones to override correctly. req = parse_requirement(line[3:].strip(), True)
result[req.identify()] = req else:
req = parse_requirement(line)
req.from_section = section or "default"
# make editable packages behind normal ones to override correctly.
result[req.identify()] = req
return result return result
@property @property
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
Utility functions Utility functions
""" """
import atexit import atexit
import functools
import os import os
import re import re
import shutil import shutil
...@@ -378,3 +379,21 @@ def expand_env_vars_in_auth(url: str) -> str: ...@@ -378,3 +379,21 @@ def expand_env_vars_in_auth(url: str) -> str:
auth = expand_env_vars(auth, True) auth = expand_env_vars(auth, True)
netloc = "@".join([auth, rest]) netloc = "@".join([auth, rest])
return parse.urlunparse((scheme, netloc, path, params, query, fragment)) return parse.urlunparse((scheme, netloc, path, params, query, fragment))
@functools.lru_cache()
def path_replace(pattern: str, replace_with: str, dest: str) -> str:
"""Safely replace the pattern in a path with given string.
:param pattern: the pattern to match
:param replace_with: the string to replace with
:param dest: the path to replace
:return the replaced path
"""
sub_flags = re.IGNORECASE if os.name == "nt" else 0
return re.sub(
pattern.replace("\\", "/"),
replace_with,
dest.replace("\\", "/"),
flags=sub_flags,
)
...@@ -3,6 +3,7 @@ import pytest ...@@ -3,6 +3,7 @@ import pytest
from pdm.exceptions import ExtrasError from pdm.exceptions import ExtrasError
from pdm.models.candidates import Candidate from pdm.models.candidates import Candidate
from pdm.models.requirements import parse_requirement from pdm.models.requirements import parse_requirement
from pdm.project.core import Project
from tests import FIXTURES from tests import FIXTURES
...@@ -93,3 +94,31 @@ def test_parse_abnormal_specifiers(project): ...@@ -93,3 +94,31 @@ def test_parse_abnormal_specifiers(project):
) )
candidate = Candidate(req, project.environment) candidate = Candidate(req, project.environment)
assert candidate.get_dependencies_from_metadata() assert candidate.get_dependencies_from_metadata()
@pytest.mark.parametrize(
"req_str",
[
"demo @ file:///${PROJECT_ROOT}/tests/fixtures/artifacts"
"/demo-0.0.1-py2.py3-none-any.whl",
"demo @ file:///${PROJECT_ROOT}/tests/fixtures/artifacts/demo-0.0.1.tar.gz",
"demo @ file:///${PROJECT_ROOT}/tests/fixtures/projects/demo",
"-e ${PROJECT_ROOT}/tests/fixtures/projects/demo",
],
)
def test_expand_project_root_in_url(req_str):
project = Project(FIXTURES.parent.parent)
if req_str.startswith("-e "):
req = parse_requirement(req_str[3:], True)
else:
req = parse_requirement(req_str)
candidate = Candidate(req, project.environment)
assert candidate.get_dependencies_from_metadata() == [
"idna",
'chardet; os_name == "nt"',
]
lockfile_entry = candidate.as_lockfile_entry()
if "path" in lockfile_entry:
assert lockfile_entry["path"].startswith("./")
else:
assert "${PROJECT_ROOT}" in lockfile_entry["url"]
...@@ -55,8 +55,10 @@ def test_find_python_in_path(tmp_path): ...@@ -55,8 +55,10 @@ def test_find_python_in_path(tmp_path):
cli_utils.find_python_in_path(sys.executable) cli_utils.find_python_in_path(sys.executable)
== pathlib.Path(sys.executable).as_posix() == pathlib.Path(sys.executable).as_posix()
) )
assert cli_utils.find_python_in_path(sys.prefix).startswith( assert (
pathlib.Path(sys.executable).as_posix() cli_utils.find_python_in_path(sys.prefix)
.lower()
.startswith(pathlib.Path(sys.executable).as_posix().lower())
) )
with pytest.raises(PdmException): with pytest.raises(PdmException):
cli_utils.find_python_in_path(tmp_path) cli_utils.find_python_in_path(tmp_path)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册