diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d242986005..23f01f73e8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,25 @@ +2022.10.25 (2022-10-25) +======================= +Pipenv 2022.10.25 (2022-10-25) +============================== + + +Features & Improvements +----------------------- + +- Add support to export requirements file for a specified set of categories. `#5431 `_ + +Vendored Libraries +------------------ + +- Remove appdirs.py in favor of platformdirs. `#5420 `_ + +Removals and Deprecations +------------------------- + +- Remove usage of vistir.cmdparse in favor of pipenv.cmdparse `#5419 `_ + + 2022.10.12 (2022-10-12) ======================= Pipenv 2022.10.12 (2022-10-12) diff --git a/docs/advanced.rst b/docs/advanced.rst index a6cf001c69..28af135241 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -628,11 +628,17 @@ You can display the names and commands of your shortcuts by running ``pipenv scr ☤ Configuration With Environment Variables ------------------------------------------ -Pipenv comes with a handful of options that can be enabled via shell environment -variables. To activate them, simply create the variable in your shell and pipenv -will detect it. +Pipenv comes with a handful of options that can be set via shell environment +variables. -.. automodule:: pipenv.environments +To enable boolean options, create the variable in your shell and assign to it a +truthy value (i.e. ``"1"``):: + + $ PIPENV_IGNORE_VIRTUALENVS=1 + +To explicitly disable a boolean option, assign to it a falsey value (i.e. ``"0"``). + +.. autoclass:: pipenv.environments.Setting :members: If you'd like to set these environment variables on a per-project basis, I recommend utilizing the fantastic `direnv `_ project, in order to do so. diff --git a/docs/dev/contributing.rst b/docs/dev/contributing.rst index b84c658ce1..640aa94bbb 100644 --- a/docs/dev/contributing.rst +++ b/docs/dev/contributing.rst @@ -110,7 +110,7 @@ Pipenv now uses pre-commit hooks similar to Pip in order to apply linting and code formatting automatically! The build now also checks that these linting rules have been applied to the code before running the tests. The build will fail when linting changes are detected so be sure to sync dev requirements -and install the pre-commit hooks locally: +and install the pre-commit hooks locally:: $ ``pipenv install --dev`` # This will configure running the pre-commit checks at start of each commit diff --git a/news/5419.removal.rst b/news/5419.removal.rst deleted file mode 100644 index 97a67c5a78..0000000000 --- a/news/5419.removal.rst +++ /dev/null @@ -1 +0,0 @@ -Remove usage of vistir.cmdparse in favor of pipenv.cmdparse diff --git a/news/5420.vendor.rst b/news/5420.vendor.rst deleted file mode 100644 index 71670b09bd..0000000000 --- a/news/5420.vendor.rst +++ /dev/null @@ -1 +0,0 @@ -Remove appdirs.py in favor of platformdirs. diff --git a/news/5431.feature.rst b/news/5431.feature.rst deleted file mode 100644 index 627693cd9f..0000000000 --- a/news/5431.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Add support to export requirements file for a specified set of categories. diff --git a/news/5451.feature.rst b/news/5451.feature.rst new file mode 100644 index 0000000000..30786d74e9 --- /dev/null +++ b/news/5451.feature.rst @@ -0,0 +1 @@ +Allow pipenv settings to be explicitly disabled more easily by assigning to the environment variable a falsy value. diff --git a/pipenv/__version__.py b/pipenv/__version__.py index 2987c86063..039f0176fc 100644 --- a/pipenv/__version__.py +++ b/pipenv/__version__.py @@ -2,4 +2,4 @@ # // ) ) / / // ) ) //___) ) // ) ) || / / # //___/ / / / //___/ / // // / / || / / # // / / // ((____ // / / ||/ / -__version__ = "2022.10.13.dev0" +__version__ = "2022.10.26.dev0" diff --git a/pipenv/environments.py b/pipenv/environments.py index 9338c2eb21..f0c4667ede 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -8,8 +8,7 @@ from pipenv._compat import fix_utf8 from pipenv.patched.pip._vendor.platformdirs import user_cache_dir -from pipenv.utils.constants import FALSE_VALUES, TRUE_VALUES -from pipenv.utils.shell import env_to_bool +from pipenv.utils.shell import env_to_bool, is_env_truthy from pipenv.vendor.vistir.misc import _isatty # HACK: avoid resolver.py uses the wrong byte code files. @@ -18,14 +17,7 @@ os.environ["PYTHONDONTWRITEBYTECODE"] = "1" -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 - - -def get_from_env(arg, prefix="PIPENV", check_for_negation=True): +def get_from_env(arg, prefix="PIPENV", check_for_negation=True, default=None): """ Check the environment for a variable, returning its truthy or stringified value @@ -36,6 +28,8 @@ def get_from_env(arg, prefix="PIPENV", check_for_negation=True): :param str prefix: The prefix to attach to the variable, defaults to "PIPENV" :param bool check_for_negation: Whether to check for ``_NO_``, defaults to True + :param Optional[Union[str, bool]] default: The value to return if the environment variable does + not exist, defaults to None :return: The value from the environment if available :rtype: Optional[Union[str, bool]] """ @@ -56,7 +50,7 @@ def get_from_env(arg, prefix="PIPENV", check_for_negation=True): return not env_to_bool(value) except ValueError: return value - return None + return default def normalize_pipfile_path(p): @@ -83,7 +77,7 @@ def normalize_pipfile_path(p): SESSION_IS_INTERACTIVE = _isatty(sys.stdout) # TF_BUILD indicates to Azure pipelines it is a build step -PIPENV_IS_CI = _is_env_truthy("CI") or _is_env_truthy("TF_BUILD") +PIPENV_IS_CI = is_env_truthy("CI") or is_env_truthy("TF_BUILD") NO_COLOR = False @@ -98,7 +92,7 @@ def normalize_pipfile_path(p): PIPENV_HIDE_EMOJIS = ( os.environ.get("PIPENV_HIDE_EMOJIS") is None and (os.name == "nt" or PIPENV_IS_CI) - or _is_env_truthy("PIPENV_HIDE_EMOJIS") + or is_env_truthy("PIPENV_HIDE_EMOJIS") ) """Disable emojis in output. @@ -118,13 +112,13 @@ def __init__(self) -> None: #: Location for Pipenv to store it's package cache. #: Default is to use appdir's user cache directory. - self.PIPENV_CACHE_DIR = os.environ.get( - "PIPENV_CACHE_DIR", user_cache_dir("pipenv") + self.PIPENV_CACHE_DIR = get_from_env( + "CACHE_DIR", check_for_negation=False, default=user_cache_dir("pipenv") ) # Tells Pipenv which Python to default to, when none is provided. - self.PIPENV_DEFAULT_PYTHON_VERSION = os.environ.get( - "PIPENV_DEFAULT_PYTHON_VERSION" + self.PIPENV_DEFAULT_PYTHON_VERSION = get_from_env( + "DEFAULT_PYTHON_VERSION", check_for_negation=False ) """Use this Python version when creating new virtual environments by default. @@ -134,38 +128,46 @@ def __init__(self) -> None: this configuration. """ - self.PIPENV_DONT_LOAD_ENV = bool(os.environ.get("PIPENV_DONT_LOAD_ENV")) + self.PIPENV_DONT_LOAD_ENV = bool( + get_from_env("DONT_LOAD_ENV", check_for_negation=False) + ) """If set, Pipenv does not load the ``.env`` file. Default is to load ``.env`` for ``run`` and ``shell`` commands. """ - self.PIPENV_DONT_USE_PYENV = bool(os.environ.get("PIPENV_DONT_USE_PYENV")) + self.PIPENV_DONT_USE_PYENV = bool( + get_from_env("DONT_USE_PYENV", check_for_negation=False) + ) """If set, Pipenv does not attempt to install Python with pyenv. Default is to install Python automatically via pyenv when needed, if possible. """ - self.PIPENV_DONT_USE_ASDF = bool(os.environ.get("PIPENV_DONT_USE_ASDF")) + self.PIPENV_DONT_USE_ASDF = bool( + get_from_env("DONT_USE_ASDF", check_for_negation=False) + ) """If set, Pipenv does not attempt to install Python with asdf. Default is to install Python automatically via asdf when needed, if possible. """ - self.PIPENV_DOTENV_LOCATION = os.environ.get("PIPENV_DOTENV_LOCATION") + self.PIPENV_DOTENV_LOCATION = get_from_env( + "DOTENV_LOCATION", check_for_negation=False + ) """If set, Pipenv loads the ``.env`` file at the specified location. Default is to load ``.env`` from the project root, if found. """ - self.PIPENV_EMULATOR = os.environ.get("PIPENV_EMULATOR", "") + self.PIPENV_EMULATOR = get_from_env("EMULATOR", default="") """If set, the terminal emulator's name for ``pipenv shell`` to use. Default is to detect emulators automatically. This should be set if your emulator, e.g. Cmder, cannot be detected correctly. """ - self.PIPENV_IGNORE_VIRTUALENVS = bool(os.environ.get("PIPENV_IGNORE_VIRTUALENVS")) + self.PIPENV_IGNORE_VIRTUALENVS = bool(get_from_env("IGNORE_VIRTUALENVS")) """If set, Pipenv will always assign a virtual environment for this project. By default, Pipenv tries to detect whether it is run inside a virtual @@ -174,7 +176,7 @@ def __init__(self) -> None: """ self.PIPENV_INSTALL_TIMEOUT = int( - os.environ.get("PIPENV_INSTALL_TIMEOUT", 60 * 15) + get_from_env("INSTALL_TIMEOUT", default=60 * 15) ) """Max number of seconds to wait for package installation. @@ -182,21 +184,23 @@ def __init__(self) -> None: """ # NOTE: +1 because of a temporary bug in Pipenv. - self.PIPENV_MAX_DEPTH = int(os.environ.get("PIPENV_MAX_DEPTH", "3")) + 1 + self.PIPENV_MAX_DEPTH = int(get_from_env("PIPENV_MAX_DEPTH", default=3)) + 1 """Maximum number of directories to recursively search for a Pipfile. Default is 3. See also ``PIPENV_NO_INHERIT``. """ - self.PIPENV_MAX_RETRIES = int( - os.environ.get("PIPENV_MAX_RETRIES", "1" if PIPENV_IS_CI else "0") + self.PIPENV_MAX_RETRIES = ( + int(get_from_env("MAX_RETRIES", default=1)) if PIPENV_IS_CI else 0 ) """Specify how many retries Pipenv should attempt for network requests. Default is 0. Automatically set to 1 on CI environments for robust testing. """ - self.PIPENV_NO_INHERIT = "PIPENV_NO_INHERIT" in os.environ + self.PIPENV_NO_INHERIT = bool( + get_from_env("NO_INHERIT", check_for_negation=False) + ) """Tell Pipenv not to inherit parent directories. This is useful for deployment to avoid using the wrong current directory. @@ -205,7 +209,7 @@ def __init__(self) -> None: if self.PIPENV_NO_INHERIT: self.PIPENV_MAX_DEPTH = 2 - self.PIPENV_NOSPIN = bool(os.environ.get("PIPENV_NOSPIN")) + self.PIPENV_NOSPIN = bool(get_from_env("NOSPIN", check_for_negation=False)) """If set, disable terminal spinner. This can make the logs cleaner. Automatically set on Windows, and in CI @@ -215,14 +219,16 @@ def __init__(self) -> None: self.PIPENV_NOSPIN = True pipenv_spinner = "dots" if not os.name == "nt" else "bouncingBar" - self.PIPENV_SPINNER = os.environ.get("PIPENV_SPINNER", pipenv_spinner) + self.PIPENV_SPINNER = get_from_env( + "SPINNER", check_for_negation=False, default=pipenv_spinner + ) """Sets the default spinner type. Spinners are identical to the ``node.js`` spinners and can be found at https://github.com/sindresorhus/cli-spinners """ - pipenv_pipfile = os.environ.get("PIPENV_PIPFILE") + pipenv_pipfile = get_from_env("PIPFILE", check_for_negation=False) if pipenv_pipfile: if not os.path.isfile(pipenv_pipfile): raise RuntimeError("Given PIPENV_PIPFILE is not found!") @@ -243,20 +249,20 @@ def __init__(self) -> None: See also ``PIPENV_MAX_DEPTH``. """ - self.PIPENV_PYPI_MIRROR = os.environ.get("PIPENV_PYPI_MIRROR") + self.PIPENV_PYPI_MIRROR = get_from_env("PYPI_MIRROR", check_for_negation=False) """If set, tells pipenv to override PyPI index urls with a mirror. Default is to not mirror PyPI, i.e. use the real one, pypi.org. The ``--pypi-mirror`` command line flag overwrites this. """ - self.PIPENV_QUIET = bool(os.environ.get("PIPENV_QUIET")) + self.PIPENV_QUIET = bool(get_from_env("QUIET", check_for_negation=False)) """If set, makes Pipenv quieter. Default is unset, for normal verbosity. ``PIPENV_VERBOSE`` overrides this. """ - self.PIPENV_SHELL_EXPLICIT = os.environ.get("PIPENV_SHELL") + self.PIPENV_SHELL_EXPLICIT = get_from_env("SHELL", check_for_negation=False) """An absolute path to the preferred shell for ``pipenv shell``. Default is to detect automatically what shell is currently in use. @@ -264,47 +270,42 @@ def __init__(self) -> None: # Hack because PIPENV_SHELL is actually something else. Internally this # variable is called PIPENV_SHELL_EXPLICIT instead. - self.PIPENV_SHELL_FANCY = bool(os.environ.get("PIPENV_SHELL_FANCY")) + self.PIPENV_SHELL_FANCY = bool(get_from_env("SHELL_FANCY")) """If set, always use fancy mode when invoking ``pipenv shell``. Default is to use the compatibility shell if possible. """ - self.PIPENV_TIMEOUT = int(os.environ.get("PIPENV_TIMEOUT", 120)) + self.PIPENV_TIMEOUT = int( + get_from_env("TIMEOUT", check_for_negation=False, default=120) + ) """Max number of seconds Pipenv will wait for virtualenv creation to complete. Default is 120 seconds, an arbitrary number that seems to work. """ - 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: - self.PIPENV_VENV_IN_PROJECT = True - elif self.PIPENV_VENV_IN_PROJECT.lower() in FALSE_VALUES: - self.PIPENV_VENV_IN_PROJECT = False - else: - self.PIPENV_VENV_IN_PROJECT = None + self.PIPENV_VENV_IN_PROJECT = get_from_env("VENV_IN_PROJECT") """ When set True, will create or use the ``.venv`` in your project directory. When Set False, will ignore the .venv in your project directory even if it exists. - Default is None will use the .venv of project directory should it exist, otherwise + If unset (default), will use the .venv of project directory should it exist, otherwise will create new virtual environments in a global location. """ - self.PIPENV_VERBOSE = bool(os.environ.get("PIPENV_VERBOSE")) + self.PIPENV_VERBOSE = bool(get_from_env("VERBOSE", check_for_negation=False)) """If set, makes Pipenv more wordy. Default is unset, for normal verbosity. This takes precedence over ``PIPENV_QUIET``. """ - self.PIPENV_YES = bool(os.environ.get("PIPENV_YES")) + self.PIPENV_YES = bool(get_from_env("YES")) """If set, Pipenv automatically assumes "yes" at all prompts. Default is to prompt the user for an answer if the current command line session if interactive. """ - self.PIPENV_SKIP_LOCK = bool(os.environ.get("PIPENV_SKIP_LOCK", False)) + self.PIPENV_SKIP_LOCK = bool(get_from_env("SKIP_LOCK")) """If set, Pipenv won't lock dependencies automatically. This might be desirable if a project has large number of dependencies, @@ -317,33 +318,34 @@ def __init__(self) -> None: NOTE: This only affects the ``install`` and ``uninstall`` commands. """ - self.PIP_EXISTS_ACTION = os.environ.get("PIP_EXISTS_ACTION", "w") + self.PIP_EXISTS_ACTION = get_from_env( + "EXISTS_ACTION", prefix="PIP", check_for_negation=False, default="w" + ) """Specifies the value for pip's --exists-action option Defaults to ``(w)ipe`` """ - self.PIPENV_RESOLVE_VCS = os.environ.get( - "PIPENV_RESOLVE_VCS" - ) is None or _is_env_truthy("PIPENV_RESOLVE_VCS") - + self.PIPENV_RESOLVE_VCS = bool(get_from_env("RESOLVE_VCS", default=True)) """Tells Pipenv whether to resolve all VCS dependencies in full. As of Pipenv 2018.11.26, only editable VCS dependencies were resolved in full. To retain this behavior and avoid handling any conflicts that arise from the new - approach, you may set this to '0', 'off', or 'false'. + approach, you may disable this. """ - self.PIPENV_CUSTOM_VENV_NAME = os.getenv("PIPENV_CUSTOM_VENV_NAME", None) + self.PIPENV_CUSTOM_VENV_NAME = get_from_env( + "CUSTOM_VENV_NAME", check_for_negation=False + ) """Tells Pipenv whether to name the venv something other than the default dir name.""" - self.PIPENV_PYUP_API_KEY = os.environ.get("PIPENV_PYUP_API_KEY", None) + self.PIPENV_PYUP_API_KEY = get_from_env("PYUP_API_KEY", check_for_negation=False) # Internal, support running in a different Python from sys.executable. - self.PIPENV_PYTHON = os.environ.get("PIPENV_PYTHON") + self.PIPENV_PYTHON = get_from_env("PYTHON", check_for_negation=False) # Internal, overwrite all index funcitonality. - self.PIPENV_TEST_INDEX = os.environ.get("PIPENV_TEST_INDEX") + self.PIPENV_TEST_INDEX = get_from_env("TEST_INDEX", check_for_negation=False) # Internal, tells Pipenv about the surrounding environment. self.PIPENV_USE_SYSTEM = False @@ -364,9 +366,8 @@ def __init__(self) -> None: # Internal, consolidated verbosity representation as an integer. The default # level is 0, increased for wordiness and decreased for terseness. - verbosity = os.environ.get("PIPENV_VERBOSITY", "") try: - self.PIPENV_VERBOSITY = int(verbosity) + self.PIPENV_VERBOSITY = int(get_from_env("VERBOSITY")) except (ValueError, TypeError): if self.PIPENV_VERBOSE: self.PIPENV_VERBOSITY = 1 @@ -405,7 +406,7 @@ def is_in_virtualenv(): pipenv_active = os.environ.get("PIPENV_ACTIVE", False) virtual_env = bool(os.environ.get("VIRTUAL_ENV")) - ignore_virtualenvs = bool(os.environ.get("PIPENV_IGNORE_VIRTUALENVS", False)) + ignore_virtualenvs = bool(get_from_env("IGNORE_VIRTUALENVS")) return virtual_env and not (pipenv_active or ignore_virtualenvs) diff --git a/pipenv/pipenv.1 b/pipenv/pipenv.1 index 740f610f74..55bbe2f5a9 100644 --- a/pipenv/pipenv.1 +++ b/pipenv/pipenv.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PIPENV" "1" "Oct 24, 2022" "2022.10.13.dev0" "pipenv" +.TH "PIPENV" "1" "Oct 25, 2022" "2022.10.25" "pipenv" .sp Pipenv uses a set of commands to manage your Project\(aqs dependencies and custom scripts. It replaces the use of \fBMakefile\fP, direct calls to \fBpip\fP and \fBpython \-m venv\fP or \fBvirtualenv\fP\&. diff --git a/pipenv/utils/shell.py b/pipenv/utils/shell.py index a8765e2987..2739021ce8 100644 --- a/pipenv/utils/shell.py +++ b/pipenv/utils/shell.py @@ -395,18 +395,30 @@ 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 + :return: False if falsey, True if truthy :rtype: bool + :raises: + ValueError: if val is not a valid boolean-like """ if isinstance(val, bool): return val - if val.lower() in FALSE_VALUES: - return False - if val.lower() in TRUE_VALUES: - return True + + try: + if val.lower() in FALSE_VALUES: + return False + if val.lower() in TRUE_VALUES: + return True + except AttributeError: + pass + 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)""" + return env_to_bool(os.getenv(name, False)) + + def project_python(project, system=False): if not system: python = project._which("python") diff --git a/tests/unit/test_environments.py b/tests/unit/test_environments.py index c818840d1f..b5bfbef4b5 100644 --- a/tests/unit/test_environments.py +++ b/tests/unit/test_environments.py @@ -31,19 +31,17 @@ def test_get_from_env(arg, prefix, use_negation): main_expected_value = False # use negation means if the normal variable isnt set we will check # for the negated version - negative_expected_value = ( - True if is_negative else None - ) + negative_expected_value = True if is_negative else None if is_positive: assert ( environments.get_from_env( - var_to_set, prefix, check_for_negation=use_negation + var_to_set, prefix=prefix, check_for_negation=use_negation ) is main_expected_value ) assert ( environments.get_from_env( - opposite_var, prefix, check_for_negation=use_negation + opposite_var, prefix=prefix, check_for_negation=use_negation ) is negative_expected_value ) @@ -54,7 +52,7 @@ def test_get_from_env(arg, prefix, use_negation): # get NO_BLAH -- expecting this to be True assert ( environments.get_from_env( - var_to_set, prefix, check_for_negation=use_negation + var_to_set, prefix=prefix, check_for_negation=use_negation ) is negative_expected_value ) @@ -62,7 +60,44 @@ def test_get_from_env(arg, prefix, use_negation): # but otherwise should be None assert ( environments.get_from_env( - opposite_var, prefix, check_for_negation=use_negation + opposite_var, prefix=prefix, check_for_negation=use_negation ) is main_expected_value ) + + +@pytest.mark.environments +@pytest.mark.parametrize( + "check_for_negation, default", + list(itertools.product((True, False), (None, "default", 1))), +) +def test_get_from_env_default(check_for_negation, default): + """When the desired env var does""" + arg = "ENABLE_SOMETHING" + prefix = "FAKEPREFIX" + envvar = f"{prefix}_{arg}" + negated_envvar = f"{prefix}_NO_{arg}" + with temp_environ(): + os.environ.pop(envvar, None) + os.environ.pop(negated_envvar, None) + assert ( + environments.get_from_env( + arg, prefix=prefix, check_for_negation=check_for_negation, default=default + ) + == default + ) + + +def test_pipenv_venv_in_project_set_true(monkeypatch): + monkeypatch.setenv("PIPENV_VENV_IN_PROJECT", "1") + assert environments.Setting().PIPENV_VENV_IN_PROJECT is True + + +def test_pipenv_venv_in_project_set_false(monkeypatch): + monkeypatch.setenv("PIPENV_VENV_IN_PROJECT", "0") + assert environments.Setting().PIPENV_VENV_IN_PROJECT is False + + +def test_pipenv_venv_in_project_unset(monkeypatch): + monkeypatch.delenv("PIPENV_VENV_IN_PROJECT", raising=False) + assert environments.Setting().PIPENV_VENV_IN_PROJECT is None diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 703bfff69a..2972b087fc 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -3,7 +3,6 @@ import pytest -import pipenv.utils.shell from pipenv.utils import dependencies from pipenv.utils import indexes from pipenv.utils import internet @@ -338,7 +337,7 @@ def test_new_line_end_of_toml_file(this): ) @pytest.mark.skipif(os.name != "nt", reason="Windows file paths tested") def test_win_normalize_drive(self, input_path, expected): - assert pipenv.utils.shell.normalize_drive(input_path) == expected + assert shell.normalize_drive(input_path) == expected @pytest.mark.utils @pytest.mark.parametrize( @@ -352,7 +351,7 @@ def test_win_normalize_drive(self, input_path, expected): ) @pytest.mark.skipif(os.name == "nt", reason="*nix file paths tested") def test_nix_normalize_drive(self, input_path, expected): - assert pipenv.utils.shell.normalize_drive(input_path) == expected + assert shell.normalize_drive(input_path) == expected @pytest.mark.utils @pytest.mark.parametrize( @@ -490,3 +489,37 @@ def mock_shutil_which(command, path=None): python = shell.project_python(project, system=True) assert python == "/usr/local/bin/python3" + + @pytest.mark.utils + @pytest.mark.parametrize( + "val, expected", + ( + (True, True), + (False, False), + ("true", True), + ("1", True), + ("off", False), + ("0", False), + ) + ) + def test_env_to_bool(self, val, expected): + actual = shell.env_to_bool(val) + assert actual == expected + + @pytest.mark.utils + def test_is_env_truthy_exists_true(self, monkeypatch): + name = "ZZZ" + monkeypatch.setenv(name, "1") + assert shell.is_env_truthy(name) is True + + @pytest.mark.utils + def test_is_env_truthy_exists_false(self, monkeypatch): + name = "ZZZ" + monkeypatch.setenv(name, "0") + assert shell.is_env_truthy(name) is False + + @pytest.mark.utils + def test_is_env_truthy_does_not_exisxt(self, monkeypatch): + name = "ZZZ" + monkeypatch.delenv(name, raising=False) + assert shell.is_env_truthy(name) is False