diff --git a/src/pdm/cli/actions.py b/src/pdm/cli/actions.py index b837494efd..5976922383 100644 --- a/src/pdm/cli/actions.py +++ b/src/pdm/cli/actions.py @@ -157,22 +157,18 @@ def check_lockfile(project: Project, raise_not_exist: bool = True) -> str | None if not project.lockfile.exists(): if raise_not_exist: raise ProjectError("Lockfile does not exist, nothing to install") - project.core.ui.echo("Lockfile does not exist", style="warning", err=True) + project.core.ui.warn("Lockfile does not exist") return "all" compat = project.lockfile.compatibility() if compat == Compatibility.NONE: - project.core.ui.echo("Lockfile is not compatible with PDM", style="warning", err=True) + project.core.ui.warn("Lockfile is not compatible with PDM") return "reuse" elif compat == Compatibility.BACKWARD: - project.core.ui.echo("Lockfile is generated on an older version of PDM", style="warning", err=True) + project.core.ui.warn("Lockfile is generated on an older version of PDM") elif compat == Compatibility.FORWARD: - project.core.ui.echo("Lockfile is generated on a newer version of PDM", style="warning", err=True) + project.core.ui.warn("Lockfile is generated on a newer version of PDM") elif not project.is_lockfile_hash_match(): - project.core.ui.echo( - "Lockfile hash doesn't match pyproject.toml, packages may be outdated", - style="warning", - err=True, - ) + project.core.ui.warn("Lockfile hash doesn't match pyproject.toml, packages may be outdated") return "reuse" return None @@ -253,15 +249,8 @@ def print_pep582_command(project: Project, shell: str = "AUTO") -> None: try: set_env_in_reg("PYTHONPATH", pep582_path) except PermissionError: - ui.echo( - "Permission denied, please run the terminal as administrator.", - style="error", - err=True, - ) - ui.echo( - "The environment variable has been saved, please restart the session to take effect.", - style="success", - ) + ui.error("Permission denied, please run the terminal as administrator.") + ui.info("The environment variable has been saved, please restart the session to take effect.") return lib_path = pep582_path.replace("'", "\\'") if shell == "AUTO": @@ -360,7 +349,7 @@ def check_update(project: Project) -> None: # pragma: no cover install_command = f"{sys.executable} -m {install_command}" message = [ - f"\nPDM [primary]{this_version}[/]", + f"PDM [primary]{this_version}[/]", f" is installed, while [primary]{latest_version}[/]", " is available.\n", f"Please run [req]`{install_command}`[/]", @@ -368,7 +357,7 @@ def check_update(project: Project) -> None: # pragma: no cover f"Run [req]`{disable_command}`[/]", " to disable the check.", ] - project.core.ui.echo("".join(message), err=True, style="info") + project.core.ui.info("".join(message)) # Moved functions diff --git a/src/pdm/cli/commands/add.py b/src/pdm/cli/commands/add.py index c00fbc383a..d5c8f17c46 100644 --- a/src/pdm/cli/commands/add.py +++ b/src/pdm/cli/commands/add.py @@ -118,7 +118,7 @@ def do_add( lock_groups = ["default"] if project.lockfile.empty() else project.lockfile.groups if lock_groups is not None and group not in lock_groups: if project.enable_write_lockfile: - project.core.ui.echo(f"Adding group [success]{group}[/] to lockfile", err=True, style="info") + project.core.ui.info(f"Adding group [success]{group}[/] to lockfile") lock_groups.append(group) if ( group == "default" @@ -129,11 +129,7 @@ def do_add( raise PdmUsageError("Cannot add editables to the default or optional dependency group") for r in [parse_requirement(line, True) for line in editables] + [parse_requirement(line) for line in packages]: if project.name and normalize_name(project.name) == r.key and not r.extras: - project.core.ui.echo( - f"Package [req]{project.name}[/] is the project itself.", - err=True, - style="warning", - ) + project.core.ui.warn(f"Package [req]{project.name}[/] is the project itself.") continue if r.is_file_or_url: r.relocate(project.backend) # type: ignore[attr-defined] diff --git a/src/pdm/cli/commands/config.py b/src/pdm/cli/commands/config.py index 1082b16870..3f4c012dbb 100644 --- a/src/pdm/cli/commands/config.py +++ b/src/pdm/cli/commands/config.py @@ -77,10 +77,8 @@ def _get_config(self, project: Project, options: argparse.Namespace) -> None: from findpython import ALL_PROVIDERS if options.key in project.project_config.deprecated: # pragma: no cover - project.core.ui.echo( - f"DEPRECATED: the config has been renamed to {project.project_config.deprecated[options.key]}", - style="warning", - err=True, + project.core.ui.warn( + f"[warning]DEPRECATED:[/] the config has been renamed to {project.project_config.deprecated[options.key]}", ) options.key = project.project_config.deprecated[options.key] try: @@ -96,10 +94,8 @@ def _get_config(self, project: Project, options: argparse.Namespace) -> None: def _set_config(self, project: Project, options: argparse.Namespace) -> None: config = project.project_config if options.local else project.global_config if options.key in config.deprecated: # pragma: no cover - project.core.ui.echo( - f"DEPRECATED: the config has been renamed to {config.deprecated[options.key]}", - style="warning", - err=True, + project.core.ui.warn( + f"[warning]DEPRECATED:[/] the config has been renamed to {config.deprecated[options.key]}", ) config[options.key] = options.value @@ -176,9 +172,7 @@ def _list_config(self, project: Project, options: argparse.Namespace) -> None: def _delete_config(self, project: Project, options: argparse.Namespace) -> None: config = project.project_config if options.local else project.global_config if options.key in config.deprecated: # pragma: no cover - project.core.ui.echo( - f"DEPRECATED: the config has been renamed to {config.deprecated[options.key]}", - style="warning", - err=True, + project.core.ui.warn( + f"[warning]DEPRECATED:[/] the config has been renamed to {config.deprecated[options.key]}", ) del config[options.key] diff --git a/src/pdm/cli/commands/export.py b/src/pdm/cli/commands/export.py index 15ab41be8b..ecf07b216f 100644 --- a/src/pdm/cli/commands/export.py +++ b/src/pdm/cli/commands/export.py @@ -60,11 +60,9 @@ def handle(self, project: Project, options: argparse.Namespace) -> None: else: if not project.lockfile.exists(): raise PdmUsageError("No lockfile found, please run `pdm lock` first.") - project.core.ui.echo( + project.core.ui.warn( "The exported requirements file is no longer cross-platform. " "Using it on other platforms may cause unexpected result.", - style="warning", - err=True, ) candidates = resolve_candidates_from_lockfile(project, requirements.values()) # Remove candidates with [extras] because the bare candidates are already diff --git a/src/pdm/cli/commands/fix/__init__.py b/src/pdm/cli/commands/fix/__init__.py index 8e4c7a2b99..43d34af0e1 100644 --- a/src/pdm/cli/commands/fix/__init__.py +++ b/src/pdm/cli/commands/fix/__init__.py @@ -32,7 +32,7 @@ def check_problems(project: Project, strict: bool = True) -> None: if not problems: return breaking = False - project.core.ui.echo("[warning]WARNING:[/] The following problems are found in your project:", err=True) + project.core.ui.warn("The following problems are found in your project:") for name, fixer in problems: project.core.ui.echo(f" [b]{name}[/]: {fixer.get_message()}", err=True) if fixer.breaking: diff --git a/src/pdm/cli/commands/init.py b/src/pdm/cli/commands/init.py index 44957f3bf4..9fb33501fa 100644 --- a/src/pdm/cli/commands/init.py +++ b/src/pdm/cli/commands/init.py @@ -71,11 +71,9 @@ def _init_cookiecutter(self, project: Project, options: argparse.Namespace) -> N if not options.template: raise PdmUsageError("template argument is required when --cookiecutter is passed") if options.project_path: - project.core.ui.echo( + project.core.ui.warn( "Cookiecutter generator does not respect --project option. " "It will always create a project dir under the current directory", - err=True, - style="warning", ) try: cookiecutter.main([options.template, *options.generator_args], standalone_mode=False) @@ -247,16 +245,13 @@ def set_python(self, project: Project, python: str | None, hooks: HookManager) - path = project._create_virtualenv() python_info = PythonInfo.from_path(get_venv_python(path)) except Exception as e: # pragma: no cover - project.core.ui.echo( - f"Error occurred when creating virtualenv: {e}\nPlease fix it and create later.", - style="error", - err=True, + project.core.ui.error( + f"Error occurred when creating virtualenv: {e}\nPlease fix it and create later." ) if python_info.get_venv() is None: - project.core.ui.echo( + project.core.ui.info( "You are using the PEP 582 mode, no virtualenv is created.\n" - "For more info, please visit https://peps.python.org/pep-0582/", - style="success", + "For more info, please visit https://peps.python.org/pep-0582/" ) project.python = python_info diff --git a/src/pdm/cli/commands/list.py b/src/pdm/cli/commands/list.py index a6ba7f60ec..4d435d6a85 100644 --- a/src/pdm/cli/commands/list.py +++ b/src/pdm/cli/commands/list.py @@ -281,11 +281,9 @@ def _group_of(name: str) -> set[str]: try: ui.echo(text_body, highlight=True) except UnicodeEncodeError: - ui.echo( + ui.error( "Markdown output contains non-ASCII characters. " "Setting env var PYTHONIOENCODING to 'utf8' may fix this.", - err=True, - style="error", ) ui.echo(text_body.encode().decode("ascii", errors="ignore"), highlight=True) ui.echo("**Problem decoding file as UTF-8. Some characters may be omit.**") diff --git a/src/pdm/cli/commands/publish/__init__.py b/src/pdm/cli/commands/publish/__init__.py index 673c1098a7..551e4a8ae1 100644 --- a/src/pdm/cli/commands/publish/__init__.py +++ b/src/pdm/cli/commands/publish/__init__.py @@ -175,11 +175,7 @@ def handle(self, project: Project, options: argparse.Namespace) -> None: logger.debug("Response from %s:\n%s %s", resp.url, resp.status_code, resp.reason) if options.skip_existing and self._skip_upload(resp): - project.core.ui.echo( - f"Skipping {package.base_filename} because it appears to already exist", - style="warning", - err=True, - ) + project.core.ui.warn(f"Skipping {package.base_filename} because it appears to already exist") continue self._check_response(resp) uploaded.append(package) diff --git a/src/pdm/cli/commands/remove.py b/src/pdm/cli/commands/remove.py index 55f7cecc93..463646589a 100644 --- a/src/pdm/cli/commands/remove.py +++ b/src/pdm/cli/commands/remove.py @@ -104,9 +104,7 @@ def do_remove( if not dry_run: project.pyproject.write() if lock_groups and group not in lock_groups: - project.core.ui.echo( - f"Group [success]{group}[/] isn't in lockfile, skipping lock.", style="warning", err=True - ) + project.core.ui.warn(f"Group [success]{group}[/] isn't in lockfile, skipping lock.") return do_lock(project, "reuse", dry_run=dry_run, hooks=hooks, groups=lock_groups) if sync: diff --git a/src/pdm/cli/commands/run.py b/src/pdm/cli/commands/run.py index e108982f6e..96d22ef1c8 100644 --- a/src/pdm/cli/commands/run.py +++ b/src/pdm/cli/commands/run.py @@ -387,11 +387,7 @@ def handle(self, project: Project, options: argparse.Namespace) -> None: if options.site_packages: runner.global_options.update({"site_packages": options.site_packages}) if not options.script: - project.core.ui.echo( - "No command is given, default to the Python REPL.", - style="warning", - err=True, - ) + project.core.ui.warn("No command is given, default to the Python REPL.") options.script = "python" hooks.try_emit("pre_run", script=options.script, args=options.args) exit_code = runner.run(options.script, options.args) diff --git a/src/pdm/cli/commands/show.py b/src/pdm/cli/commands/show.py index 85c4b45fbb..7f2b8ba3b6 100644 --- a/src/pdm/cli/commands/show.py +++ b/src/pdm/cli/commands/show.py @@ -45,11 +45,7 @@ def handle(self, project: Project, options: argparse.Namespace) -> None: with project.environment.get_finder() as finder: best_match = finder.find_best_match(package, allow_prereleases=True) if not best_match.applicable: - project.core.ui.echo( - f"No match found for the package {package!r}", - err=True, - style="warning", - ) + project.core.ui.warn(f"No match found for the package {package!r}") return latest = Candidate.from_installation_candidate(best_match.best, parse_requirement(package)) latest_stable = next(filter(filter_stable, best_match.applicable), None) diff --git a/src/pdm/cli/commands/use.py b/src/pdm/cli/commands/use.py index eee94a456b..45e233c173 100644 --- a/src/pdm/cli/commands/use.py +++ b/src/pdm/cli/commands/use.py @@ -61,17 +61,11 @@ def version_matcher(py_version: PythonInfo) -> bool: path = use_cache.get(python) cached_python = PythonInfo.from_path(path) if not cached_python.valid: - project.core.ui.echo( + project.core.ui.error( f"The last selection is corrupted. {path!r}", - style="error", - err=True, ) elif version_matcher(cached_python): - project.core.ui.echo( - "Using the last selection, add '-i' to ignore it.", - style="warning", - err=True, - ) + project.core.ui.info("Using the last selection, add '-i' to ignore it.") return cached_python found_interpreters = list(dict.fromkeys(project.find_interpreters(python))) diff --git a/src/pdm/cli/commands/venv/activate.py b/src/pdm/cli/commands/venv/activate.py index 3b888734ad..d7bbb639b4 100644 --- a/src/pdm/cli/commands/venv/activate.py +++ b/src/pdm/cli/commands/venv/activate.py @@ -26,19 +26,15 @@ def handle(self, project: Project, options: argparse.Namespace) -> None: # Use what is saved in .pdm-python interpreter = project._saved_python if not interpreter: - project.core.ui.echo( - "The project doesn't have a saved python.path. Run [success]pdm use[/] to pick one.", - style="warning", - err=True, + project.core.ui.warn( + "The project doesn't have a saved python.path. Run [success]pdm use[/] to pick one." ) raise SystemExit(1) venv_like = VirtualEnv.from_interpreter(Path(interpreter)) if venv_like is None: - project.core.ui.echo( + project.core.ui.warn( f"Can't activate a non-venv Python [success]{interpreter}[/], " "you can specify one with [success]pdm venv activate [/]", - style="warning", - err=True, ) raise SystemExit(1) venv = venv_like diff --git a/src/pdm/cli/commands/venv/purge.py b/src/pdm/cli/commands/venv/purge.py index 2d5b44884a..87d18224c3 100644 --- a/src/pdm/cli/commands/venv/purge.py +++ b/src/pdm/cli/commands/venv/purge.py @@ -34,7 +34,7 @@ def handle(self, project: Project, options: argparse.Namespace) -> None: return if not options.force: - project.core.ui.echo("The following Virtualenvs will be purged:", style="error") + project.core.ui.echo("The following Virtualenvs will be purged:", style="warning") for i, venv in enumerate(all_central_venvs): project.core.ui.echo(f"{i}. [success]{venv[0]}[/]") diff --git a/src/pdm/cli/utils.py b/src/pdm/cli/utils.py index 53b87d54ea..cafa036c5e 100644 --- a/src/pdm/cli/utils.py +++ b/src/pdm/cli/utils.py @@ -773,9 +773,7 @@ def use_venv(project: Project, name: str) -> None: from pdm.environments import PythonEnvironment venv = get_venv_with_name(project, cast(str, name)) - project.core.ui.echo( - f"In virtual environment: [success]{venv.root}[/]", err=True, style="info", verbosity=termui.Verbosity.DETAIL - ) + project.core.ui.info(f"In virtual environment: [success]{venv.root}[/]", verbosity=termui.Verbosity.DETAIL) project.environment = PythonEnvironment(project, python=str(venv.interpreter)) diff --git a/src/pdm/core.py b/src/pdm/core.py index 380278b546..1261318772 100644 --- a/src/pdm/core.py +++ b/src/pdm/core.py @@ -211,12 +211,7 @@ def main( err=True, ) if should_show_tb: - self.ui.echo( - "Add '-v' to see the detailed traceback", - style="warning", - err=True, - verbosity=termui.Verbosity.NORMAL, - ) + self.ui.warn("Add '-v' to see the detailed traceback", verbosity=termui.Verbosity.NORMAL) sys.exit(1) else: if project.config["check_update"] and not is_in_zipapp(): @@ -283,10 +278,8 @@ def my_plugin(core: pdm.core.Core) -> None: try: plugin.load()(self) except Exception as e: - self.ui.echo( + self.ui.error( f"Failed to load plugin {plugin.name}={plugin.value}: {e}", - style="error", - err=True, ) diff --git a/src/pdm/formats/flit.py b/src/pdm/formats/flit.py index 5f3c832267..8bbf759cd6 100644 --- a/src/pdm/formats/flit.py +++ b/src/pdm/formats/flit.py @@ -86,7 +86,7 @@ def warn_against_dynamic_version_or_docstring(self, source: Path, version: str, "They are probably imported from other files which is not supported by PDM." " You may need to supply their values in pyproject.toml manually." ) - self._ui.echo(message, err=True, style="warning") + self._ui.warn(message) @convert_from("metadata") def name(self, metadata: dict[str, Any]) -> str: diff --git a/src/pdm/models/auth.py b/src/pdm/models/auth.py index c646eba544..e46ff4d90f 100644 --- a/src/pdm/models/auth.py +++ b/src/pdm/models/auth.py @@ -55,12 +55,10 @@ def _prompt_for_password(self, netloc: str) -> tuple[str | None, str | None, boo def _should_save_password_to_keyring(self) -> bool: if get_keyring_provider() is None: - self.ui.echo( + self.ui.info( "The provided credentials will not be saved into the keyring.\n" "You can enable this by installing keyring:\n" - " [success]pdm self add keyring[/]", - err=True, - style="info", + " [success]pdm self add keyring[/]" ) return super()._should_save_password_to_keyring() diff --git a/src/pdm/models/repositories.py b/src/pdm/models/repositories.py index 70374e342e..a204ef57b4 100644 --- a/src/pdm/models/repositories.py +++ b/src/pdm/models/repositories.py @@ -474,12 +474,10 @@ def search(self, query: str) -> SearchResult: session = finder.session resp = session.get(search_url, params={"q": query}) if resp.status_code == 404: - self.environment.project.core.ui.echo( + self.environment.project.core.ui.warn( f"{pypi_simple!r} doesn't support '/search' endpoint, fallback " f"to {self.DEFAULT_INDEX_URL!r} now.\n" "This may take longer depending on your network condition.", - err=True, - style="warning", ) resp = session.get(f"{self.DEFAULT_INDEX_URL}/search", params={"q": query}) parser = SearchResultParser() diff --git a/src/pdm/project/config.py b/src/pdm/project/config.py index 49288c3b9d..196272c999 100644 --- a/src/pdm/project/config.py +++ b/src/pdm/project/config.py @@ -334,10 +334,7 @@ def __setitem__(self, key: str, value: Any) -> None: value = config.coerce(value) if key in self.env_map: - ui.echo( - f"WARNING: the config is shadowed by env var '{config.env_var}', the value set won't take effect.", - style="warning", - ) + ui.warn(f"the config is shadowed by env var '{config.env_var}', the value set won't take effect.") self._file_data[config_key] = value if config.replace: self._file_data.pop(config.replace, None) @@ -375,10 +372,7 @@ def __delitem__(self, key: str) -> None: env_var = config.env_var if env_var is not None and env_var in os.environ: - ui.echo( - f"WARNING: the config is shadowed by env var '{env_var}', set value won't take effect.", - style="warning", - ) + ui.warn(f"The config is shadowed by env var '{env_var}', set value won't take effect.") self._save_config() def get_repository_config(self, name_or_url: str, prefix: str) -> RepositoryConfig | None: diff --git a/src/pdm/project/core.py b/src/pdm/project/core.py index 9b789231ea..dc8db59db3 100644 --- a/src/pdm/project/core.py +++ b/src/pdm/project/core.py @@ -95,11 +95,7 @@ def __init__( root_path = global_project is_global = True if self.global_config["global_project.fallback_verbose"]: - self.core.ui.echo( - "Project is not found, fallback to the global project", - style="warning", - err=True, - ) + self.core.ui.info("Project is not found, fallback to the global project") self.root: Path = Path(root_path or "").absolute() self.is_global = is_global @@ -200,7 +196,7 @@ def match_version(python: PythonInfo) -> bool: def note(message: str) -> None: if not self.is_global: - self.core.ui.echo(message, style="info", err=True) + self.core.ui.info(message) config = self.config saved_path = self._saved_python @@ -306,11 +302,9 @@ def get_dependencies(self, group: str | None = None) -> dict[str, Requirement]: deps = metadata.get("dependencies", []) else: if group in optional_dependencies and group in dev_dependencies: - self.core.ui.echo( - f"The {group} group exists in both [optional-dependencies] " - "and [dev-dependencies], the former is taken.", - err=True, - style="warning", + self.core.ui.info( + f"The {group} group exists in both \\[optional-dependencies] " + "and \\[dev-dependencies], the former is taken." ) if group in optional_dependencies: deps = optional_dependencies[group] @@ -323,12 +317,10 @@ def get_dependencies(self, group: str | None = None) -> dict[str, Requirement]: for line in deps: if line.startswith("-e "): if in_metadata: - self.core.ui.echo( - f"WARNING: Skipping editable dependency [b]{line}[/] in the" + self.core.ui.warn( + f"Skipping editable dependency [b]{line}[/] in the" r" [success]\[project][/] table. Please move it to the " - r"[success]\[tool.pdm.dev-dependencies][/] table", - err=True, - style="warning", + r"[success]\[tool.pdm.dev-dependencies][/] table" ) continue req = parse_requirement(line[3:].strip(), True) @@ -447,11 +439,7 @@ def get_provider( except Exception: if for_install: raise - self.core.ui.echo( - "Unable to reuse the lock file as it is not compatible with PDM", - style="warning", - err=True, - ) + self.core.ui.warn("Unable to reuse the lock file as it is not compatible with PDM") if locked_repository is None: return BaseProvider( diff --git a/src/pdm/resolver/core.py b/src/pdm/resolver/core.py index 88c8b66202..700ff12887 100644 --- a/src/pdm/resolver/core.py +++ b/src/pdm/resolver/core.py @@ -36,11 +36,9 @@ def resolve( result = resolver.resolve(requirements, max_rounds) if repository.has_warnings: - repository.environment.project.core.ui.echo( + repository.environment.project.core.ui.info( "Use `-q/--quiet` to suppress these warnings, or ignore them per-package with " r"`ignore_package_warnings` config in \[tool.pdm] table.", - err=True, - style="info", verbosity=termui.Verbosity.NORMAL, ) diff --git a/src/pdm/termui.py b/src/pdm/termui.py index e82f794a1e..4c1c0ae365 100644 --- a/src/pdm/termui.py +++ b/src/pdm/termui.py @@ -275,3 +275,15 @@ def make_progress(self, *columns: str | ProgressColumn, **kwargs: Any) -> Progre disable=self.verbosity >= Verbosity.DETAIL, **kwargs, ) + + def info(self, message: str, verbosity: Verbosity = Verbosity.QUIET) -> None: + """Print a message to stdout.""" + self.echo(f"[info]INFO:[/] [dim]{message}[/]", err=True, verbosity=verbosity) + + def warn(self, message: str, verbosity: Verbosity = Verbosity.QUIET) -> None: + """Print a message to stdout.""" + self.echo(f"[warning]WARNING:[/] {message}", err=True, verbosity=verbosity) + + def error(self, message: str, verbosity: Verbosity = Verbosity.QUIET) -> None: + """Print a message to stdout.""" + self.echo(f"[error]WARNING:[/] {message}", err=True, verbosity=verbosity) diff --git a/tests/cli/test_config.py b/tests/cli/test_config.py index b9ebc5a2ee..140136d6f5 100644 --- a/tests/cli/test_config.py +++ b/tests/cli/test_config.py @@ -56,7 +56,7 @@ def test_config_env_var_shadowing(project, pdm, monkeypatch): assert result.output.strip() == "https://example.org/simple" result = pdm(["config", "pypi.url", "https://test.pypi.org/pypi"], obj=project) - assert "config is shadowed by env var 'PDM_PYPI_URL'" in result.output + assert "config is shadowed by env var 'PDM_PYPI_URL'" in result.stderr result = pdm(["config", "pypi.url"], obj=project) assert result.output.strip() == "https://example.org/simple"