Skip to content

Commit

Permalink
fix: proper display for extra Pypi indexes config (#1622)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Frost Ming <[email protected]>
  • Loading branch information
3 people committed Jan 28, 2023
1 parent f0992fc commit b75c72d
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 16 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ python -m pip install -e .
```

You are free to create a virtualenv with either `venv` module or `virtualenv` tool for the development. If you are doing
so, you may also need to set `pdm config use_venv true` after installation is done.
so, you may also need to set `pdm config python.use_venv true` after installation is done.

Now, all dependencies are installed into the Python environment you choose, which will be used for development after this point.

Expand Down
1 change: 1 addition & 0 deletions news/1622.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add proper display for the extra pypi sources in `pdm config`.
5 changes: 5 additions & 0 deletions src/pdm/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,9 @@ def __exit__(self, *args: Any) -> None:
...


class RichProtocol(Protocol):
def __rich__(self) -> str:
...


SearchResult = List[Package]
38 changes: 35 additions & 3 deletions src/pdm/cli/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
from pdm import termui
from pdm.cli.commands.base import BaseCommand
from pdm.project import Project
from pdm.project.config import Config
from pdm.project.config import (
DEFAULT_REPOSITORIES,
REPOSITORY,
Config,
RegistryConfig,
RepositoryConfig,
)


class Command(BaseCommand):
Expand Down Expand Up @@ -74,19 +80,45 @@ def _show_config(
if canonical_key in supersedes:
superseded = True
deprecated = f"[error](deprecating: {key})[/]"
elif key not in Config._config_map:
elif key not in Config._config_map and not (
key.startswith("pypi.") or key.startswith(REPOSITORY)
):
continue
extra_style = "dim" if superseded else None
if canonical_key not in Config._config_map:
if key.startswith("pypi."):
index = key.split(".")[1]
self.ui.echo(
f"[warning]# Configuration of non-default Pypi index `{index}`",
style=extra_style,
verbosity=termui.Verbosity.DETAIL,
)
self.ui.echo(RegistryConfig(**config[key], config_prefix=key))
elif key.startswith(REPOSITORY):
for item in config[key]:
self.ui.echo(
f"[warning]# Configuration of custom repository `{item}`",
style=extra_style,
verbosity=termui.Verbosity.DETAIL,
)
repository = dict(config[key][item])
if "url" not in repository and item in DEFAULT_REPOSITORIES:
repository["url"] = DEFAULT_REPOSITORIES[item].url
self.ui.echo(
RepositoryConfig(
**repository, config_prefix=f"{key}.{item}"
)
)
continue
config_item = Config._config_map[canonical_key]
self.ui.echo(
f"[warning]# {config_item.description}",
style=extra_style,
verbosity=termui.Verbosity.DETAIL,
)
value = "[i]<hidden>[/]" if key.endswith("password") else config[key]
self.ui.echo(
f"[primary]{canonical_key}[/]{deprecated} = {config[key]}",
f"[primary]{canonical_key}[/]{deprecated} = {value}",
style=extra_style,
)

Expand Down
56 changes: 47 additions & 9 deletions src/pdm/project/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,46 @@ class RepositoryConfig:
password: str | None = None
ca_certs: str | None = None

config_prefix: str | None = None

def __rich__(self) -> str:
lines = [f"[primary]url[/] = {self.url}"]
config_prefix = (
f"{self.config_prefix}." if self.config_prefix is not None else ""
)
lines = [f"[primary]{config_prefix}url[/] = {self.url}"]
if self.username:
lines.append(f"[primary]username[/] = {self.username}")
lines.append(f"[primary]{config_prefix}username[/] = {self.username}")
if self.password:
lines.append("[primary]password[/] = <hidden>")
lines.append(f"[primary]{config_prefix}password[/] = [i]<hidden>[/]")
if self.ca_certs:
lines.append(f"[primary]ca_certs[/] = {self.ca_certs}")
lines.append(f"[primary]{config_prefix}ca_certs[/] = {self.ca_certs}")
return "\n".join(lines)


@dataclasses.dataclass
class RegistryConfig:

url: str
username: str | None = None
password: str | None = None
verify_ssl: bool | None = None
type: str | None = None

config_prefix: str | None = None

def __rich__(self) -> str:
config_prefix = (
f"{self.config_prefix}." if self.config_prefix is not None else ""
)
lines = [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:
lines.append(f"[primary]{config_prefix}verify_ssl[/] = {self.verify_ssl}")
if self.type:
lines.append(f"[primary]{config_prefix}type[/] = {self.type}")
return "\n".join(lines)


Expand Down Expand Up @@ -338,7 +370,15 @@ def __getitem__(self, key: str) -> Any:
if index_key not in self._data:
raise KeyError(f"No PyPI index named {parts[1]}")
source = self._data[index_key]
return source[parts[2]] if len(parts) >= 3 else source
if len(parts) >= 3 and parts[2] == "password":
return "<hidden>"
return (
source[parts[2]]
if len(parts) >= 3
else RegistryConfig(**self._data[index_key])
)
elif key == "pypi.password":
return "<hidden>"

if key not in self._config_map and key not in self.deprecated:
raise NoConfigError(key)
Expand Down Expand Up @@ -449,12 +489,10 @@ def get_repository_config(self, name_or_url: str) -> RepositoryConfig | None:
"""Get a repository by name or url."""
if not self.is_global: # pragma: no cover
raise NoConfigError("repository")
repositories: Mapping[str, Mapping[str, str | None]] = self._data.get(
REPOSITORY, {}
)
repositories: Mapping[str, Source] = self._data.get(REPOSITORY, {})
repo: RepositoryConfig | None = None
if "://" in name_or_url:
config: Mapping[str, str | None] = next(
config: Source = next(
(v for v in repositories.values() if v.get("url") == name_or_url), {}
)
repo = next(
Expand Down
4 changes: 2 additions & 2 deletions src/pdm/termui.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from rich.table import Table
from rich.theme import Theme

from pdm._types import Spinner, SpinnerT
from pdm._types import RichProtocol, Spinner, SpinnerT

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
Expand Down Expand Up @@ -167,7 +167,7 @@ def set_theme(self, theme: Theme) -> None:

def echo(
self,
message: str = "",
message: str | RichProtocol = "",
err: bool = False,
verbosity: Verbosity = Verbosity.NORMAL,
**kwargs: Any,
Expand Down
17 changes: 16 additions & 1 deletion tests/cli/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def test_repository_overwrite_default(project):
assert repository.url == "https://example.pypi.org/legacy/"


def test_hide_password_in_output(project, invoke):
def test_hide_password_in_output_repository(project, invoke):
assert project.global_config["repository.pypi.password"] is None
project.global_config["repository.pypi.username"] = "testuser"
project.global_config["repository.pypi.password"] = "secret"
Expand All @@ -139,6 +139,21 @@ def test_hide_password_in_output(project, invoke):
assert "<hidden>" == result.output.strip()


def test_hide_password_in_output_pypi(project, invoke):
with pytest.raises(KeyError):
assert project.global_config["pypi.extra.password"] is None
project.global_config["pypi.extra.username"] = "testuser"
project.global_config["pypi.extra.password"] = "secret"
project.global_config["pypi.extra.url"] = "https://test/simple"
result = invoke(["config", "pypi.extra"], obj=project, strict=True)
assert "password = <hidden>" in result.output
result = invoke(["config", "pypi.extra.password"], obj=project, strict=True)
assert "<hidden>" == result.output.strip()
result = invoke(["config"], obj=project)
assert "pypi.extra.password" in result.output
assert "<hidden>" in result.output


def test_config_get_repository(project, invoke):
config = project.global_config["repository.pypi"]
assert config == project.global_config.get_repository_config("pypi")
Expand Down

0 comments on commit b75c72d

Please sign in to comment.