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

feat(core) merge index parameters #1683

Merged
merged 3 commits into from
Feb 7, 2023
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
3 changes: 3 additions & 0 deletions docs/docs/usage/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ pdm config pypi.extra.username "foo"
pdm config pypi.extra.password "password4foo"
```

The index parameters can be put in different levels of configuration files. That is, you can only put the `url` in `pyproject.toml`,
and store the credentials in `~/.config/pdm/config.toml` under the `[pypi.<name>]` section of which the name must match.

!!! NOTE
Configured indexes will be tried **after** the sources in `pyproject.toml`, if you want to completely ignore the
locally configured indexes, including the main index, set the config value `pypi.ignore_stored_index`
Expand Down
1 change: 1 addition & 0 deletions news/1667.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Merge the index parameters from different configuration files.
55 changes: 46 additions & 9 deletions src/pdm/_types.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,54 @@
from __future__ import annotations

import dataclasses
from typing import Any, Dict, List, NamedTuple, Tuple, TypeVar, Union

from pdm.compat import Literal, Protocol, TypedDict
from pdm.compat import Protocol


@dataclasses.dataclass
class RepositoryConfig:
url: str | None = None
username: str | None = None
password: str | None = None
verify_ssl: bool | None = None
type: str | None = None
ca_certs: str | None = None

config_prefix: str | None = None
name: str | None = None

def passive_update(
self, other: RepositoryConfig | None = None, **kwargs: Any
) -> None:
"""An update method that prefers the existing value over the new one."""
if other is not None:
for k in other.__dataclass_fields__:
v = getattr(other, k)
if getattr(self, k) is None and v is not None:
setattr(self, k, v)
for k, v in kwargs.items():
if getattr(self, k) is None and v is not None:
setattr(self, k, v)


class Source(TypedDict, total=False):
url: str
verify_ssl: bool
name: str
type: Literal["index", "find_links"]
username: str
password: str
def __rich__(self) -> str:
config_prefix = (
f"{self.config_prefix}." if self.config_prefix is not None else ""
)
lines: list[str] = []
if self.url:
lines.append(f"[primary]{config_prefix}url[/] = {self.url}")
if self.username:
lines.append(f"[primary]{config_prefix}username[/] = {self.username}")
if self.password:
lines.append(f"[primary]{config_prefix}password[/] = [i]<hidden>[/]")
if self.verify_ssl is not None:
lines.append(f"[primary]{config_prefix}verify_ssl[/] = {self.verify_ssl}")
if self.type:
lines.append(f"[primary]{config_prefix}type[/] = {self.type}")
if self.ca_certs:
lines.append(f"[primary]{config_prefix}ca_certs[/] = {self.ca_certs}")
return "\n".join(lines)


RequirementDict = Union[str, Dict[str, Union[str, bool]]]
Expand Down
13 changes: 4 additions & 9 deletions src/pdm/cli/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@
from typing import Any, Mapping

from pdm import termui
from pdm._types import RepositoryConfig
from pdm.cli.commands.base import BaseCommand
from pdm.project import Project
from pdm.project.config import (
DEFAULT_REPOSITORIES,
REPOSITORY,
Config,
RegistryConfig,
RepositoryConfig,
)
from pdm.project.config import DEFAULT_REPOSITORIES, REPOSITORY, Config


class Command(BaseCommand):
Expand Down Expand Up @@ -93,7 +88,7 @@ def _show_config(
style=extra_style,
verbosity=termui.Verbosity.DETAIL,
)
self.ui.echo(RegistryConfig(**config[key], config_prefix=key))
self.ui.echo(RepositoryConfig(**config[key], config_prefix=key))
elif key.startswith(REPOSITORY):
for item in config[key]:
self.ui.echo(
Expand All @@ -103,7 +98,7 @@ def _show_config(
)
repository = dict(config[key][item])
if "url" not in repository and item in DEFAULT_REPOSITORIES:
repository["url"] = DEFAULT_REPOSITORIES[item].url
repository["url"] = DEFAULT_REPOSITORIES[item]
self.ui.echo(
RepositoryConfig(
**repository, config_prefix=f"{key}.{item}"
Expand Down
1 change: 1 addition & 0 deletions src/pdm/cli/commands/publish/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def get_repository(project: Project, options: argparse.Namespace) -> Repository:
config = project.global_config.get_repository_config(repository)
if config is None:
raise PdmUsageError(f"Missing repository config of {repository}")
assert config.url is not None
if username is not None:
config.username = username
if password is not None:
Expand Down
4 changes: 2 additions & 2 deletions src/pdm/cli/commands/publish/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ def _convert_to_list_of_tuples(data: dict[str, Any]) -> list[tuple[str, Any]]:
return result

def get_release_urls(self, packages: list[PackageFile]) -> Iterable[str]:
if self.url.startswith(DEFAULT_REPOSITORIES["pypi"].url.rstrip("/")):
if self.url.startswith(DEFAULT_REPOSITORIES["pypi"].rstrip("/")):
base = "https://pypi.org/"
elif self.url.startswith(DEFAULT_REPOSITORIES["testpypi"].url.rstrip("/")):
elif self.url.startswith(DEFAULT_REPOSITORIES["testpypi"].rstrip("/")):
base = "https://test.pypi.org/"
else:
return set()
Expand Down
4 changes: 2 additions & 2 deletions src/pdm/formats/poetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
if TYPE_CHECKING:
from pdm.project.core import Project
from argparse import Namespace
from pdm._types import RequirementDict, Source
from pdm._types import RequirementDict

from pdm.utils import cd

Expand Down Expand Up @@ -193,7 +193,7 @@ def build(self, value: str | dict) -> None:
raise Unset()

@convert_from("source")
def sources(self, value: list[Source]) -> None:
def sources(self, value: list[dict[str, Any]]) -> None:
self.settings["source"] = [
{
"name": item.get("name", ""),
Expand Down
11 changes: 4 additions & 7 deletions src/pdm/formats/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@
import urllib.parse
from argparse import Namespace
from os import PathLike
from typing import TYPE_CHECKING, Any, Mapping, cast
from typing import Any, Mapping

from pdm.formats.base import make_array
from pdm.models.candidates import Candidate
from pdm.models.requirements import Requirement, parse_requirement
from pdm.project import Project
from pdm.utils import expand_env_vars_in_auth

if TYPE_CHECKING:
from pdm._types import Source


class RequirementParser:
"""Reference:
Expand Down Expand Up @@ -111,7 +108,7 @@ def _is_url_trusted(url: str, trusted_hosts: list[str]) -> bool:

def convert_url_to_source(
url: str, name: str | None, trusted_hosts: list[str], type: str = "index"
) -> Source:
) -> dict[str, Any]:
if not name:
name = hashlib.sha1(url.encode("utf-8")).hexdigest()[:6]
source = {
Expand All @@ -121,7 +118,7 @@ def convert_url_to_source(
}
if type != "index":
source["type"] = type
return cast("Source", source)
return source


def convert(
Expand Down Expand Up @@ -150,7 +147,7 @@ def convert(
data["optional-dependencies"] = {options.group: deps}
else:
data["dependencies"] = deps
sources: list[Source] = []
sources: list[dict[str, Any]] = []
if parser.index_url and not parser.no_index:
sources.append(
convert_url_to_source(parser.index_url, "pypi", parser.trusted_hosts)
Expand Down
13 changes: 7 additions & 6 deletions src/pdm/models/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from unearth.auth import MaybeAuth, MultiDomainBasicAuth
from unearth.utils import split_auth_from_netloc

from pdm._types import Source
from pdm._types import RepositoryConfig
from pdm.exceptions import PdmException
from pdm.termui import UI

Expand All @@ -24,7 +24,7 @@ class PdmBasicAuth(MultiDomainBasicAuth):
- It shows an error message when credentials are not provided or correct.
"""

def __init__(self, sources: list[Source], prompting: bool = True) -> None:
def __init__(self, sources: list[RepositoryConfig], prompting: bool = True) -> None:
super().__init__(prompting=True)
self._real_prompting = prompting
self.sources = sources
Expand All @@ -33,12 +33,13 @@ def _get_auth_from_index_url(self, netloc: str) -> tuple[MaybeAuth, str | None]:
if not self.sources:
return None, None
for source in self.sources:
parsed = urllib.parse.urlparse(source["url"])
assert source.url
parsed = urllib.parse.urlparse(source.url)
auth, index_netloc = split_auth_from_netloc(parsed.netloc)
if index_netloc == netloc:
if "username" in source:
auth = (source["username"], source.get("password"))
return auth, source["url"]
if source.username:
auth = (source.username, source.password)
return auth, source.url
return None, None

def _prompt_for_password(self, netloc: str) -> tuple[str | None, str | None, bool]:
Expand Down
4 changes: 2 additions & 2 deletions src/pdm/models/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
)

if TYPE_CHECKING:
from pdm._types import Source
from pdm._types import RepositoryConfig
from pdm.project import Project


Expand Down Expand Up @@ -145,7 +145,7 @@ def _build_session(
@contextmanager
def get_finder(
self,
sources: list[Source] | None = None,
sources: list[RepositoryConfig] | None = None,
ignore_compatibility: bool = False,
) -> Generator[unearth.PackageFinder, None, None]:
"""Return the package finder of given index sources.
Expand Down
13 changes: 7 additions & 6 deletions src/pdm/models/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from pdm.utils import cd, normalize_name, url_without_fragments

if TYPE_CHECKING:
from pdm._types import CandidateInfo, SearchResult, Source
from pdm._types import CandidateInfo, RepositoryConfig, SearchResult
from pdm.models.environment import Environment

ALLOW_ALL_PYTHON = PySpecSet()
Expand All @@ -46,7 +46,7 @@ class BaseRepository:

def __init__(
self,
sources: list[Source],
sources: list[RepositoryConfig],
environment: Environment,
ignore_compatibility: bool = True,
) -> None:
Expand All @@ -62,7 +62,7 @@ def __init__(
self._candidate_info_cache = environment.project.make_candidate_info_cache()
self._hash_cache = environment.project.make_hash_cache()

def get_filtered_sources(self, req: Requirement) -> list[Source]:
def get_filtered_sources(self, req: Requirement) -> list[RepositoryConfig]:
"""Get matching sources based on the index attribute."""
return self.sources

Expand Down Expand Up @@ -295,7 +295,8 @@ def _get_dependencies_from_json(self, candidate: Candidate) -> CandidateInfo:
proc_url[:-7] # Strip "/simple".
for proc_url in (
raw_url.rstrip("/")
for raw_url in (source.get("url", "") for source in sources)
for raw_url in (source.url for source in sources)
if raw_url
)
if proc_url.endswith("/simple")
]
Expand Down Expand Up @@ -347,7 +348,7 @@ def _find_candidates(self, requirement: Requirement) -> Iterable[Candidate]:
return cans

def search(self, query: str) -> SearchResult:
pypi_simple = self.sources[0]["url"].rstrip("/")
pypi_simple = self.sources[0].url.rstrip("/") # type: ignore[union-attr]

if pypi_simple.endswith("/simple"):
search_url = pypi_simple[:-6] + "search"
Expand Down Expand Up @@ -378,7 +379,7 @@ class LockedRepository(BaseRepository):
def __init__(
self,
lockfile: Mapping[str, Any],
sources: list[Source],
sources: list[RepositoryConfig],
environment: Environment,
) -> None:
super().__init__(sources, environment, ignore_compatibility=False)
Expand Down
Loading