config.py (poetry-1.1.15) | : | config.py (poetry-1.2.0) | ||
---|---|---|---|---|
from __future__ import annotations | ||||
import json | import json | |||
import re | import re | |||
from cleo import argument | from pathlib import Path | |||
from cleo import option | from typing import TYPE_CHECKING | |||
from typing import Any | ||||
from poetry.core.pyproject import PyProjectException | from typing import cast | |||
from poetry.core.toml.file import TOMLFile | ||||
from poetry.factory import Factory | from cleo.helpers import argument | |||
from cleo.helpers import option | ||||
from poetry.config.config import PackageFilterPolicy | ||||
from poetry.config.config import boolean_normalizer | ||||
from poetry.config.config import boolean_validator | ||||
from poetry.config.config import int_normalizer | ||||
from poetry.console.commands.command import Command | ||||
from poetry.locations import DEFAULT_CACHE_DIR | ||||
from .command import Command | if TYPE_CHECKING: | |||
from poetry.config.config_source import ConfigSource | ||||
class ConfigCommand(Command): | class ConfigCommand(Command): | |||
name = "config" | name = "config" | |||
description = "Manages configuration settings." | description = "Manages configuration settings." | |||
arguments = [ | arguments = [ | |||
argument("key", "Setting key.", optional=True), | argument("key", "Setting key.", optional=True), | |||
argument("value", "Setting value.", optional=True, multiple=True), | argument("value", "Setting value.", optional=True, multiple=True), | |||
] | ] | |||
options = [ | options = [ | |||
option("list", None, "List configuration settings."), | option("list", None, "List configuration settings."), | |||
option("unset", None, "Unset configuration setting."), | option("unset", None, "Unset configuration setting."), | |||
option("local", None, "Set/Get from the project's local configuration.") , | option("local", None, "Set/Get from the project's local configuration.") , | |||
] | ] | |||
help = """This command allows you to edit the poetry config settings and rep | help = """\ | |||
ositories. | This command allows you to edit the poetry config settings and repositories. | |||
To add a repository: | To add a repository: | |||
<comment>poetry config repositories.foo https://bar.com/simple/</comment> | <comment>poetry config repositories.foo https://bar.com/simple/</comment> | |||
To remove a repository (repo is a short alias for repositories): | To remove a repository (repo is a short alias for repositories): | |||
<comment>poetry config --unset repo.foo</comment>""" | <comment>poetry config --unset repo.foo</comment>""" | |||
LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"} | LIST_PROHIBITED_SETTINGS = {"http-basic", "pypi-token"} | |||
@property | @property | |||
def unique_config_values(self): | def unique_config_values(self) -> dict[str, tuple[Any, Any, Any]]: | |||
from poetry.config.config import boolean_normalizer | ||||
from poetry.config.config import boolean_validator | ||||
from poetry.locations import CACHE_DIR | ||||
from poetry.utils._compat import Path | ||||
unique_config_values = { | unique_config_values = { | |||
"cache-dir": ( | "cache-dir": ( | |||
str, | str, | |||
lambda val: str(Path(val)), | lambda val: str(Path(val)), | |||
str(Path(CACHE_DIR) / "virtualenvs"), | str(DEFAULT_CACHE_DIR / "virtualenvs"), | |||
), | ), | |||
"virtualenvs.create": (boolean_validator, boolean_normalizer, True), | "virtualenvs.create": (boolean_validator, boolean_normalizer, True), | |||
"virtualenvs.in-project": (boolean_validator, boolean_normalizer, Fa lse), | "virtualenvs.in-project": (boolean_validator, boolean_normalizer, Fa lse), | |||
"virtualenvs.options.always-copy": ( | ||||
boolean_validator, | ||||
boolean_normalizer, | ||||
False, | ||||
), | ||||
"virtualenvs.options.system-site-packages": ( | ||||
boolean_validator, | ||||
boolean_normalizer, | ||||
False, | ||||
), | ||||
"virtualenvs.options.no-pip": ( | ||||
boolean_validator, | ||||
boolean_normalizer, | ||||
False, | ||||
), | ||||
"virtualenvs.options.no-setuptools": ( | ||||
boolean_validator, | ||||
boolean_normalizer, | ||||
False, | ||||
), | ||||
"virtualenvs.path": ( | "virtualenvs.path": ( | |||
str, | str, | |||
lambda val: str(Path(val)), | lambda val: str(Path(val)), | |||
str(Path(CACHE_DIR) / "virtualenvs"), | str(DEFAULT_CACHE_DIR / "virtualenvs"), | |||
), | ||||
"virtualenvs.prefer-active-python": ( | ||||
boolean_validator, | ||||
boolean_normalizer, | ||||
False, | ||||
), | ), | |||
"experimental.new-installer": ( | "experimental.new-installer": ( | |||
boolean_validator, | boolean_validator, | |||
boolean_normalizer, | boolean_normalizer, | |||
True, | True, | |||
), | ), | |||
"installer.parallel": (boolean_validator, boolean_normalizer, True,) | "experimental.system-git-client": ( | |||
, | boolean_validator, | |||
boolean_normalizer, | ||||
False, | ||||
), | ||||
"installer.parallel": ( | ||||
boolean_validator, | ||||
boolean_normalizer, | ||||
True, | ||||
), | ||||
"installer.max-workers": ( | ||||
lambda val: int(val) > 0, | ||||
int_normalizer, | ||||
None, | ||||
), | ||||
"virtualenvs.prompt": ( | ||||
str, | ||||
lambda val: str(val), | ||||
"{project_name}-py{python_version}", | ||||
), | ||||
"installer.no-binary": ( | ||||
PackageFilterPolicy.validator, | ||||
PackageFilterPolicy.normalize, | ||||
None, | ||||
), | ||||
} | } | |||
return unique_config_values | return unique_config_values | |||
def handle(self): | def handle(self) -> int: | |||
from pathlib import Path | ||||
from poetry.core.pyproject.exceptions import PyProjectException | ||||
from poetry.core.toml.file import TOMLFile | ||||
from poetry.config.config import Config | ||||
from poetry.config.file_config_source import FileConfigSource | from poetry.config.file_config_source import FileConfigSource | |||
from poetry.locations import CONFIG_DIR | from poetry.locations import CONFIG_DIR | |||
from poetry.utils._compat import Path | ||||
from poetry.utils._compat import basestring | ||||
config = Factory.create_config(self.io) | config = Config.create() | |||
config_file = TOMLFile(Path(CONFIG_DIR) / "config.toml") | config_file = TOMLFile(CONFIG_DIR / "config.toml") | |||
try: | try: | |||
local_config_file = TOMLFile(self.poetry.file.parent / "poetry.toml" ) | local_config_file = TOMLFile(self.poetry.file.parent / "poetry.toml" ) | |||
if local_config_file.exists(): | if local_config_file.exists(): | |||
config.merge(local_config_file.read()) | config.merge(local_config_file.read()) | |||
except (RuntimeError, PyProjectException): | except (RuntimeError, PyProjectException): | |||
local_config_file = TOMLFile(Path.cwd() / "poetry.toml") | local_config_file = TOMLFile(Path.cwd() / "poetry.toml") | |||
if self.option("local"): | if self.option("local"): | |||
config.set_config_source(FileConfigSource(local_config_file)) | config.set_config_source(FileConfigSource(local_config_file)) | |||
skipping to change at line 109 | skipping to change at line 168 | |||
setting_key = self.argument("key") | setting_key = self.argument("key") | |||
if not setting_key: | if not setting_key: | |||
return 0 | return 0 | |||
if self.argument("value") and self.option("unset"): | if self.argument("value") and self.option("unset"): | |||
raise RuntimeError("You can not combine a setting value with --unset ") | raise RuntimeError("You can not combine a setting value with --unset ") | |||
# show the value if no value is provided | # show the value if no value is provided | |||
if not self.argument("value") and not self.option("unset"): | if not self.argument("value") and not self.option("unset"): | |||
m = re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key") ) | m = re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key") ) | |||
value: str | dict[str, Any] | ||||
if m: | if m: | |||
if not m.group(1): | if not m.group(1): | |||
value = {} | value = {} | |||
if config.get("repositories") is not None: | if config.get("repositories") is not None: | |||
value = config.get("repositories") | value = config.get("repositories") | |||
else: | else: | |||
repo = config.get("repositories.{}".format(m.group(1))) | repo = config.get(f"repositories.{m.group(1)}") | |||
if repo is None: | if repo is None: | |||
raise ValueError( | raise ValueError(f"There is no {m.group(1)} repository d | |||
"There is no {} repository defined".format(m.group(1 | efined") | |||
)) | ||||
) | ||||
value = repo | value = repo | |||
self.line(str(value)) | self.line(str(value)) | |||
else: | else: | |||
values = self.unique_config_values | if setting_key not in self.unique_config_values: | |||
if setting_key not in values: | raise ValueError(f"There is no {setting_key} setting.") | |||
raise ValueError("There is no {} setting.".format(setting_ke | ||||
y)) | ||||
value = config.get(setting_key) | value = config.get(setting_key) | |||
if not isinstance(value, basestring): | if not isinstance(value, str): | |||
value = json.dumps(value) | value = json.dumps(value) | |||
self.line(value) | self.line(value) | |||
return 0 | return 0 | |||
values = self.argument("value") | values: list[str] = self.argument("value") | |||
unique_config_values = self.unique_config_values | unique_config_values = self.unique_config_values | |||
if setting_key in unique_config_values: | if setting_key in unique_config_values: | |||
if self.option("unset"): | if self.option("unset"): | |||
return config.config_source.remove_property(setting_key) | config.config_source.remove_property(setting_key) | |||
return 0 | ||||
return self._handle_single_value( | return self._handle_single_value( | |||
config.config_source, | config.config_source, | |||
setting_key, | setting_key, | |||
unique_config_values[setting_key], | unique_config_values[setting_key], | |||
values, | values, | |||
) | ) | |||
# handle repositories | # handle repositories | |||
m = re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key")) | m = re.match(r"^repos?(?:itories)?(?:\.(.+))?", self.argument("key")) | |||
if m: | if m: | |||
if not m.group(1): | if not m.group(1): | |||
raise ValueError("You cannot remove the [repositories] section") | raise ValueError("You cannot remove the [repositories] section") | |||
if self.option("unset"): | if self.option("unset"): | |||
repo = config.get("repositories.{}".format(m.group(1))) | repo = config.get(f"repositories.{m.group(1)}") | |||
if repo is None: | if repo is None: | |||
raise ValueError( | raise ValueError(f"There is no {m.group(1)} repository defin | |||
"There is no {} repository defined".format(m.group(1)) | ed") | |||
) | ||||
config.config_source.remove_property( | config.config_source.remove_property(f"repositories.{m.group(1)} | |||
"repositories.{}".format(m.group(1)) | ") | |||
) | ||||
return 0 | return 0 | |||
if len(values) == 1: | if len(values) == 1: | |||
url = values[0] | url = values[0] | |||
config.config_source.add_property( | config.config_source.add_property(f"repositories.{m.group(1)}.ur | |||
"repositories.{}.url".format(m.group(1)), url | l", url) | |||
) | ||||
return 0 | return 0 | |||
raise ValueError( | raise ValueError( | |||
"You must pass the url. " | "You must pass the url. " | |||
"Example: poetry config repositories.foo https://bar.com" | "Example: poetry config repositories.foo https://bar.com" | |||
) | ) | |||
# handle auth | # handle auth | |||
m = re.match(r"^(http-basic|pypi-token)\.(.+)", self.argument("key")) | m = re.match(r"^(http-basic|pypi-token)\.(.+)", self.argument("key")) | |||
skipping to change at line 207 | skipping to change at line 259 | |||
return 0 | return 0 | |||
if m.group(1) == "http-basic": | if m.group(1) == "http-basic": | |||
if len(values) == 1: | if len(values) == 1: | |||
username = values[0] | username = values[0] | |||
# Only username, so we prompt for password | # Only username, so we prompt for password | |||
password = self.secret("Password:") | password = self.secret("Password:") | |||
elif len(values) != 2: | elif len(values) != 2: | |||
raise ValueError( | raise ValueError( | |||
"Expected one or two arguments " | "Expected one or two arguments " | |||
"(username, password), got {}".format(len(values)) | f"(username, password), got {len(values)}" | |||
) | ) | |||
else: | else: | |||
username = values[0] | username = values[0] | |||
password = values[1] | password = values[1] | |||
password_manager.set_http_password(m.group(2), username, passwor d) | password_manager.set_http_password(m.group(2), username, passwor d) | |||
elif m.group(1) == "pypi-token": | elif m.group(1) == "pypi-token": | |||
if len(values) != 1: | if len(values) != 1: | |||
raise ValueError( | raise ValueError( | |||
"Expected only one argument (token), got {}".format(len( values)) | f"Expected only one argument (token), got {len(values)}" | |||
) | ) | |||
token = values[0] | token = values[0] | |||
password_manager.set_pypi_token(m.group(2), token) | password_manager.set_pypi_token(m.group(2), token) | |||
return 0 | return 0 | |||
# handle certs | # handle certs | |||
m = re.match( | m = re.match(r"certificates\.([^.]+)\.(cert|client-cert)", self.argument | |||
r"(?:certificates)\.([^.]+)\.(cert|client-cert)", self.argument("key | ("key")) | |||
") | ||||
) | ||||
if m: | if m: | |||
repository = m.group(1) | ||||
key = m.group(2) | ||||
if self.option("unset"): | if self.option("unset"): | |||
config.auth_config_source.remove_property( | config.auth_config_source.remove_property( | |||
"certificates.{}.{}".format(m.group(1), m.group(2)) | f"certificates.{repository}.{key}" | |||
) | ) | |||
return 0 | return 0 | |||
if len(values) == 1: | if len(values) == 1: | |||
new_value: str | bool = values[0] | ||||
if key == "cert" and boolean_validator(values[0]): | ||||
new_value = boolean_normalizer(values[0]) | ||||
config.auth_config_source.add_property( | config.auth_config_source.add_property( | |||
"certificates.{}.{}".format(m.group(1), m.group(2)), values[ 0] | f"certificates.{repository}.{key}", new_value | |||
) | ) | |||
else: | else: | |||
raise ValueError("You must pass exactly 1 value") | raise ValueError("You must pass exactly 1 value") | |||
return 0 | return 0 | |||
raise ValueError("Setting {} does not exist".format(self.argument("key") )) | raise ValueError(f"Setting {self.argument('key')} does not exist") | |||
def _handle_single_value(self, source, key, callbacks, values): | def _handle_single_value( | |||
self, | ||||
source: ConfigSource, | ||||
key: str, | ||||
callbacks: tuple[Any, Any, Any], | ||||
values: list[Any], | ||||
) -> int: | ||||
validator, normalizer, _ = callbacks | validator, normalizer, _ = callbacks | |||
if len(values) > 1: | if len(values) > 1: | |||
raise RuntimeError("You can only pass one value.") | raise RuntimeError("You can only pass one value.") | |||
value = values[0] | value = values[0] | |||
if not validator(value): | if not validator(value): | |||
raise RuntimeError('"{}" is an invalid value for {}'.format(value, k ey)) | raise RuntimeError(f'"{value}" is an invalid value for {key}') | |||
source.add_property(key, normalizer(value)) | source.add_property(key, normalizer(value)) | |||
return 0 | return 0 | |||
def _list_configuration(self, config, raw, k=""): | def _list_configuration( | |||
from poetry.utils._compat import basestring | self, config: dict[str, Any], raw: dict[str, Any], k: str = "" | |||
) -> None: | ||||
orig_k = k | orig_k = k | |||
for key, value in sorted(config.items()): | for key, value in sorted(config.items()): | |||
if k + key in self.LIST_PROHIBITED_SETTINGS: | if k + key in self.LIST_PROHIBITED_SETTINGS: | |||
continue | continue | |||
raw_val = raw.get(key) | raw_val = raw.get(key) | |||
if isinstance(value, dict): | if isinstance(value, dict): | |||
k += "{}.".format(key) | k += f"{key}." | |||
raw_val = cast("dict[str, Any]", raw_val) | ||||
self._list_configuration(value, raw_val, k=k) | self._list_configuration(value, raw_val, k=k) | |||
k = orig_k | k = orig_k | |||
continue | continue | |||
elif isinstance(value, list): | elif isinstance(value, list): | |||
value = [ | value = ", ".join( | |||
json.dumps(val) if isinstance(val, list) else val for val in value | json.dumps(val) if isinstance(val, list) else val for val in value | |||
] | ) | |||
value = f"[{value}]" | ||||
value = "[{}]".format(", ".join(value)) | ||||
if k.startswith("repositories."): | if k.startswith("repositories."): | |||
message = "<c1>{}</c1> = <c2>{}</c2>".format( | message = f"<c1>{k + key}</c1> = <c2>{json.dumps(raw_val)}</c2>" | |||
k + key, json.dumps(raw_val) | elif isinstance(raw_val, str) and raw_val != value: | |||
) | message = ( | |||
elif isinstance(raw_val, basestring) and raw_val != value: | f"<c1>{k + key}</c1> = <c2>{json.dumps(raw_val)}</c2> # {va | |||
message = "<c1>{}</c1> = <c2>{}</c2> # {}".format( | lue}" | |||
k + key, json.dumps(raw_val), value | ||||
) | ) | |||
else: | else: | |||
message = "<c1>{}</c1> = <c2>{}</c2>".format(k + key, json.dumps (value)) | message = f"<c1>{k + key}</c1> = <c2>{json.dumps(value)}</c2>" | |||
self.line(message) | self.line(message) | |||
def _list_setting(self, contents, setting=None, k=None, default=None): | ||||
values = self._get_setting(contents, setting, k, default) | ||||
for value in values: | ||||
self.line( | ||||
"<comment>{}</comment> = <info>{}</info>".format(value[0], value | ||||
[1]) | ||||
) | ||||
def _get_setting(self, contents, setting=None, k=None, default=None): | ||||
orig_k = k | ||||
if setting and setting.split(".")[0] not in contents: | ||||
value = json.dumps(default) | ||||
return [((k or "") + setting, value)] | ||||
else: | ||||
values = [] | ||||
for key, value in contents.items(): | ||||
if setting and key != setting.split(".")[0]: | ||||
continue | ||||
if isinstance(value, dict) or key == "repositories" and k is Non | ||||
e: | ||||
if k is None: | ||||
k = "" | ||||
k += re.sub(r"^config\.", "", key + ".") | ||||
if setting and len(setting) > 1: | ||||
setting = ".".join(setting.split(".")[1:]) | ||||
values += self._get_setting( | ||||
value, k=k, setting=setting, default=default | ||||
) | ||||
k = orig_k | ||||
continue | ||||
if isinstance(value, list): | ||||
value = [ | ||||
json.dumps(val) if isinstance(val, list) else val | ||||
for val in value | ||||
] | ||||
value = "[{}]".format(", ".join(value)) | ||||
value = json.dumps(value) | ||||
values.append(((k or "") + key, value)) | ||||
return values | ||||
def _get_formatted_value(self, value): | ||||
if isinstance(value, list): | ||||
value = [json.dumps(val) if isinstance(val, list) else val for val i | ||||
n value] | ||||
value = "[{}]".format(", ".join(value)) | ||||
return json.dumps(value) | ||||
End of changes. 41 change blocks. | ||||
73 lines changed or deleted | 136 lines changed or added |