diff --git a/news/5158.bugfix.rst b/news/5158.bugfix.rst new file mode 100644 index 00000000..cdee931d --- /dev/null +++ b/news/5158.bugfix.rst @@ -0,0 +1 @@ +Support ANSI ``NO_COLOR`` environment variable and deprecate ``PIPENV_COLORBLIND`` variable, which will be removed after this release. diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index e544d820..5f40bb1f 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -93,6 +93,13 @@ def cli( warn_in_virtualenv, ) + if "PIPENV_COLORBLIND" in os.environ: + echo( + "PIPENV_COLORBLIND is deprecated, use NO_COLOR instead" + "Per https://no-color.org/", + err=True, + ) + if man: if system_which("man"): path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pipenv.1") diff --git a/pipenv/core.py b/pipenv/core.py index 78c0596e..349a76fe 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -17,6 +17,7 @@ import vistir from pipenv import environments, exceptions, pep508checker, progress from pipenv._compat import decode_for_output, fix_utf8 +from pipenv.utils.constants import MYPY_RUNNING from pipenv.utils.dependencies import ( convert_deps_to_pip, get_canonical_names, @@ -39,7 +40,7 @@ from pipenv.utils.shell import ( from pipenv.utils.spinner import create_spinner from pipenv.vendor import click -if environments.is_type_checking(): +if MYPY_RUNNING: from typing import Dict, List, Optional, Union from pipenv.project import Project @@ -81,12 +82,6 @@ else: INSTALL_LABEL2 = " " STARTING_LABEL = " " -# Disable colors, for the color blind and others who do not prefer colors. -# if environments.PIPENV_COLORBLIND: - -# problem here, click.style and click.secho are doing other things besides just color -# crayons.disable() - def do_clear(project): click.secho(fix_utf8("Clearing caches..."), bold=True) diff --git a/pipenv/environment.py b/pipenv/environment.py index 87b3c7e1..50e0c9fe 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -14,8 +14,8 @@ from sysconfig import get_paths, get_python_version, get_scheme_names import pkg_resources import pipenv -from pipenv.environments import is_type_checking from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name +from pipenv.utils.constants import is_type_checking from pipenv.utils.indexes import prepare_pip_source_args from pipenv.utils.processes import subprocess_run from pipenv.utils.shell import make_posix, normalize_path diff --git a/pipenv/environments.py b/pipenv/environments.py index b263d241..41835bfe 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -8,38 +8,21 @@ from appdirs import user_cache_dir from vistir.path import normalize_drive from pipenv._compat import fix_utf8 +from pipenv.utils.constants import FALSE_VALUES, TRUE_VALUES +from pipenv.utils.shell import env_to_bool from pipenv.vendor.vistir.misc import _isatty # HACK: avoid resolver.py uses the wrong byte code files. # I hope I can remove this one day. os.environ["PYTHONDONTWRITEBYTECODE"] = "1" -_false_values = ("0", "false", "no", "off") -_true_values = ("1", "true", "yes", "on") - - -def env_to_bool(val): - """ - Convert **val** to boolean, returning True if truthy or False if falsey - - :param Any val: The value to convert - :return: False if Falsey, True if truthy - :rtype: bool - """ - if isinstance(val, bool): - return val - if val.lower() in _false_values: - return False - if val.lower() in _true_values: - return True - raise ValueError(f"Value is not a valid boolean-like: {val}") def _is_env_truthy(name): """An environment variable is truthy if it exists and isn't one of (0, false, no, off)""" if name not in os.environ: return False - return os.environ.get(name).lower() not in _false_values + return os.environ.get(name).lower() not in FALSE_VALUES def get_from_env(arg, prefix="PIPENV", check_for_negation=True): @@ -99,12 +82,14 @@ os.environ.pop("__PYVENV_LAUNCHER__", None) # Internal, to tell whether the command line session is interactive. SESSION_IS_INTERACTIVE = _isatty(sys.stdout) PIPENV_IS_CI = env_to_bool(os.environ.get("CI") or os.environ.get("TF_BUILD") or False) -PIPENV_COLORBLIND = bool(os.environ.get("PIPENV_COLORBLIND")) -"""If set, disable terminal colors. +NO_COLOR = False +if os.getenv("NO_COLOR") or os.getenv("PIPENV_COLORBLIND"): + NO_COLOR = True + from pipenv.utils.shell import style_no_color + from pipenv.vendor import click -Some people don't like colors in their terminals, for some reason. Default is -to show colors. -""" + click.original_style = click.style + click.style = style_no_color PIPENV_HIDE_EMOJIS = ( os.environ.get("PIPENV_HIDE_EMOJIS") is None @@ -294,9 +279,9 @@ class Setting: self.PIPENV_VENV_IN_PROJECT = os.environ.get("PIPENV_VENV_IN_PROJECT") if self.PIPENV_VENV_IN_PROJECT is not None: - if self.PIPENV_VENV_IN_PROJECT.lower() in _true_values: + if self.PIPENV_VENV_IN_PROJECT.lower() in TRUE_VALUES: self.PIPENV_VENV_IN_PROJECT = True - elif self.PIPENV_VENV_IN_PROJECT.lower() in _false_values: + elif self.PIPENV_VENV_IN_PROJECT.lower() in FALSE_VALUES: self.PIPENV_VENV_IN_PROJECT = False else: self.PIPENV_VENV_IN_PROJECT = None @@ -422,14 +407,5 @@ def is_in_virtualenv(): return virtual_env and not (pipenv_active or ignore_virtualenvs) -def is_type_checking(): - try: - from typing import TYPE_CHECKING - except ImportError: - return False - return TYPE_CHECKING - - -MYPY_RUNNING = is_type_checking() PIPENV_SPINNER_FAIL_TEXT = fix_utf8("✘ {0}") if not PIPENV_HIDE_EMOJIS else "{0}" PIPENV_SPINNER_OK_TEXT = fix_utf8("✔ {0}") if not PIPENV_HIDE_EMOJIS else "{0}" diff --git a/pipenv/progress.py b/pipenv/progress.py index 6d0c7f21..7fbcdf8e 100644 --- a/pipenv/progress.py +++ b/pipenv/progress.py @@ -11,21 +11,21 @@ import os import sys import time -from pipenv.environments import PIPENV_COLORBLIND, PIPENV_HIDE_EMOJIS +from pipenv.environments import NO_COLOR, PIPENV_HIDE_EMOJIS from pipenv.vendor import click STREAM = sys.stderr MILL_TEMPLATE = "%s %s %i/%i\r" DOTS_CHAR = "." if PIPENV_HIDE_EMOJIS: - if PIPENV_COLORBLIND: + if NO_COLOR: BAR_FILLED_CHAR = "=" BAR_EMPTY_CHAR = "-" else: BAR_FILLED_CHAR = str(click.style("=", bold=True, fg="green")) BAR_EMPTY_CHAR = str(click.style("-", fg="black")) else: - if PIPENV_COLORBLIND: + if NO_COLOR: BAR_FILLED_CHAR = "▉" BAR_EMPTY_CHAR = " " else: diff --git a/pipenv/project.py b/pipenv/project.py index 7095ace7..951ca018 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -22,12 +22,8 @@ import vistir from pipenv.cmdparse import Script from pipenv.core import system_which from pipenv.environment import Environment -from pipenv.environments import ( - Setting, - is_in_virtualenv, - is_type_checking, - normalize_pipfile_path, -) +from pipenv.environments import Setting, is_in_virtualenv, normalize_pipfile_path +from pipenv.utils.constants import is_type_checking from pipenv.utils.dependencies import ( get_canonical_names, is_editable, diff --git a/pipenv/utils/constants.py b/pipenv/utils/constants.py index 766a44f7..f0ad8ac4 100644 --- a/pipenv/utils/constants.py +++ b/pipenv/utils/constants.py @@ -1,3 +1,16 @@ # List of version control systems we support. VCS_LIST = ("git", "svn", "hg", "bzr") SCHEME_LIST = ("http://", "https://", "ftp://", "ftps://", "file://") +FALSE_VALUES = ("0", "false", "no", "off") +TRUE_VALUES = ("1", "true", "yes", "on") + + +def is_type_checking(): + try: + from typing import TYPE_CHECKING + except ImportError: + return False + return TYPE_CHECKING + + +MYPY_RUNNING = is_type_checking() diff --git a/pipenv/utils/indexes.py b/pipenv/utils/indexes.py index 8bf61108..61dcb180 100644 --- a/pipenv/utils/indexes.py +++ b/pipenv/utils/indexes.py @@ -5,12 +5,12 @@ from collections.abc import Mapping from urllib3.util import parse_url -from pipenv import environments from pipenv.exceptions import PipenvUsageError +from pipenv.utils.constants import MYPY_RUNNING from .internet import create_mirror_source, is_pypi_url -if environments.MYPY_RUNNING: +if MYPY_RUNNING: from typing import List, Optional, Union # noqa from pipenv.project import Project, TSource # noqa diff --git a/pipenv/utils/processes.py b/pipenv/utils/processes.py index 44b460ff..fb96e30a 100644 --- a/pipenv/utils/processes.py +++ b/pipenv/utils/processes.py @@ -1,11 +1,11 @@ import os import subprocess -from pipenv import environments from pipenv.exceptions import PipenvCmdError +from pipenv.utils.constants import MYPY_RUNNING from pipenv.vendor import click -if environments.MYPY_RUNNING: +if MYPY_RUNNING: from typing import Tuple # noqa diff --git a/pipenv/utils/resolver.py b/pipenv/utils/resolver.py index c60f0b66..d6d07cad 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -10,6 +10,7 @@ from functools import lru_cache from pipenv import environments from pipenv.exceptions import RequirementError, ResolutionFailure +from pipenv.utils.constants import MYPY_RUNNING from pipenv.vendor import click from pipenv.vendor.requirementslib import Pipfile, Requirement from pipenv.vendor.requirementslib.models.utils import DIRECT_URL_RE @@ -31,11 +32,10 @@ from .locking import format_requirement_for_lockfile, prepare_lockfile from .shell import make_posix, subprocess_run, temp_environ from .spinner import create_spinner -if environments.MYPY_RUNNING: +if MYPY_RUNNING: from typing import Any, Dict, List, Optional, Set, Tuple, Union # noqa from pipenv.project import Project # noqa - from pipenv.vendor.requirementslib import Pipfile, Requirement # noqa from pipenv.vendor.requirementslib.models.requirements import Line # noqa diff --git a/pipenv/utils/shell.py b/pipenv/utils/shell.py index 6e44bdbe..9af584b0 100644 --- a/pipenv/utils/shell.py +++ b/pipenv/utils/shell.py @@ -11,12 +11,13 @@ from contextlib import contextmanager from functools import lru_cache from pathlib import Path -from pipenv import environments +from pipenv.utils.constants import MYPY_RUNNING +from pipenv.vendor import click -from .constants import SCHEME_LIST +from .constants import FALSE_VALUES, SCHEME_LIST, TRUE_VALUES from .processes import subprocess_run -if environments.MYPY_RUNNING: +if MYPY_RUNNING: from typing import Text # noqa @@ -424,3 +425,27 @@ def handle_remove_readonly(func, path, exc): return raise exc + + +def style_no_color(text, fg=None, bg=None, **kwargs) -> str: + """Wrap click style to ignore colors.""" + if hasattr(click, "original_style"): + return click.original_style(text, **kwargs) + return click.style(text, **kwargs) + + +def env_to_bool(val): + """ + Convert **val** to boolean, returning True if truthy or False if falsey + + :param Any val: The value to convert + :return: False if Falsey, True if truthy + :rtype: bool + """ + if isinstance(val, bool): + return val + if val.lower() in FALSE_VALUES: + return False + if val.lower() in TRUE_VALUES: + return True + raise ValueError(f"Value is not a valid boolean-like: {val}") diff --git a/tests/integration/test_dot_venv.py b/tests/integration/test_dot_venv.py index 053a5dda..af429353 100644 --- a/tests/integration/test_dot_venv.py +++ b/tests/integration/test_dot_venv.py @@ -7,12 +7,12 @@ from tempfile import TemporaryDirectory import pytest -from pipenv.environments import _true_values, _false_values +from pipenv.utils.constants import FALSE_VALUES, TRUE_VALUES from pipenv.utils.shell import normalize_drive, temp_environ @pytest.mark.dotvenv -@pytest.mark.parametrize("true_value", _true_values) +@pytest.mark.parametrize("true_value", TRUE_VALUES) def test_venv_in_project(true_value, PipenvInstance): with temp_environ(): os.environ['PIPENV_VENV_IN_PROJECT'] = true_value @@ -23,7 +23,7 @@ def test_venv_in_project(true_value, PipenvInstance): @pytest.mark.dotvenv -@pytest.mark.parametrize("false_value", _false_values) +@pytest.mark.parametrize("false_value", FALSE_VALUES) def test_venv_in_project_disabled_ignores_venv(false_value, PipenvInstance): venv_name = "my_project" with temp_environ(): @@ -50,7 +50,7 @@ def test_venv_in_project_disabled_ignores_venv(false_value, PipenvInstance): @pytest.mark.dotvenv -@pytest.mark.parametrize("true_value", _true_values) +@pytest.mark.parametrize("true_value", TRUE_VALUES) def test_venv_at_project_root(true_value, PipenvInstance): with temp_environ(): with PipenvInstance(chdir=True) as p: