Skip to content

Commit

Permalink
Merge pull request #5451 from micahjsmith/micahjsmith/gh-5407
Browse files Browse the repository at this point in the history
Consolidate pipenv settings to use get_from_env
  • Loading branch information
oz123 authored Nov 4, 2022
2 parents 09e6009 + 93e97b7 commit 7c7cf81
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 82 deletions.
14 changes: 10 additions & 4 deletions docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://direnv.net>`_ project, in order to do so.
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions news/5451.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow pipenv settings to be explicitly disabled more easily by assigning to the environment variable a falsy value.
123 changes: 62 additions & 61 deletions pipenv/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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 ``<PREFIX>_NO_<arg>``, 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]]
"""
Expand All @@ -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):
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -174,29 +176,31 @@ 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.
Defaults to 900 (15 minutes), a very long arbitrary time.
"""

# 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.
Expand All @@ -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
Expand All @@ -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!")
Expand All @@ -243,68 +249,63 @@ 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.
"""
# 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,
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)


Expand Down
Loading

0 comments on commit 7c7cf81

Please sign in to comment.