diff --git a/pipenv/core.py b/pipenv/core.py index 78c0596e9b..02c2abd58d 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -81,11 +81,20 @@ 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() +# monkey patch for click.secho to respect environment variables requesting +# no color +def colorwise_secho(message, file=None, nl=True, err=False, color=True, **styles): + if os.getenv("NO_COLOR") or os.getenv("PIPENV_COLORBLIND"): + color = False + + if message is not None and not isinstance(message, (bytes, bytearray)): + message = click.style(message, **styles) + + return click.echo(message, file=file, nl=nl, err=err, color=color) + + +click.secho = colorwise_secho def do_clear(project): @@ -124,7 +133,7 @@ def load_dot_env(project, as_dict=False, quiet=False): ) if not os.path.isfile(dotenv_file) and project.s.PIPENV_DOTENV_LOCATION: - click.echo( + click.secho( "{}: file {}={} does not exist!!\n{}".format( click.style("Warning", fg="red", bold=True), click.style("PIPENV_DOTENV_LOCATION", bold=True), @@ -139,7 +148,7 @@ def load_dot_env(project, as_dict=False, quiet=False): return dotenv.dotenv_values(dotenv_file) elif os.path.isfile(dotenv_file): if not quiet: - click.echo( + click.secho( click.style( fix_utf8("Loading .env environment variables..."), bold=True ), @@ -157,7 +166,7 @@ def cleanup_virtualenv(project, bare=True): # Delete the virtualenv. shutil.rmtree(project.virtualenv_location) except OSError as e: - click.echo( + click.secho( "{} An error occurred while removing {}!".format( click.style("Error: ", fg="red", bold=True), click.style(project.virtualenv_location, fg="green"), @@ -232,7 +241,7 @@ def ensure_environment(): # Skip this on Windows... if os.name != "nt": if "LANG" not in os.environ: - click.echo( + click.secho( "{}: the environment variable {} is not set!" "\nWe recommend setting this in {} (or equivalent) for " "proper expected behavior.".format( @@ -264,7 +273,7 @@ def ensure_pipfile(project, validate=True, skip_requirements=False, system=False # If there's a requirements file, but no Pipfile... if project.requirements_exists and not skip_requirements: requirements_dir_path = os.path.dirname(project.requirements_location) - click.echo( + click.secho( "{0} found in {1} instead of {2}! Converting...".format( click.style("requirements.txt", bold=True), click.style(requirements_dir_path, fg="yellow", bold=True), @@ -282,7 +291,7 @@ def ensure_pipfile(project, validate=True, skip_requirements=False, system=False else: sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!")) # Warn the user of side-effects. - click.echo( + click.secho( "{0}: Your {1} now contains pinned versions, if your {2} did. \n" "We recommend updating your {1} to specify the {3} version, instead." "".format( @@ -293,10 +302,9 @@ def ensure_pipfile(project, validate=True, skip_requirements=False, system=False ) ) else: - click.echo( - click.style( - fix_utf8("Creating a Pipfile for this project..."), bold=True - ), + click.secho( + fix_utf8("Creating a Pipfile for this project..."), + bold=True, err=True, ) # Create the pipfile if it doesn't exist. @@ -308,9 +316,7 @@ def ensure_pipfile(project, validate=True, skip_requirements=False, system=False changed = project.ensure_proper_casing() # Write changes out to disk. if changed: - click.echo( - click.style("Fixing package names in Pipfile...", bold=True), err=True - ) + click.secho("Fixing package names in Pipfile...", bold=True, err=True) project.write_toml(p) @@ -345,7 +351,7 @@ def ensure_python(project, three=None, python=None): python = project.s.PIPENV_PYTHON def abort(msg=""): - click.echo( + click.secho( "{}\nYou can specify specific versions of Python with:\n{}".format( click.style(msg, fg="red"), click.style( @@ -367,11 +373,11 @@ def abort(msg=""): python = project.s.PIPENV_DEFAULT_PYTHON_VERSION path_to_python = find_a_system_python(python) if project.s.is_verbose(): - click.echo(f"Using python: {python}", err=True) - click.echo(f"Path to python: {path_to_python}", err=True) + click.secho(f"Using python: {python}", err=True) + click.secho(f"Path to python: {path_to_python}", err=True) if not path_to_python and python is not None: # We need to install Python. - click.echo( + click.secho( "{}: Python {} {}".format( click.style("Warning", fg="red", bold=True), click.style(python, fg="cyan"), @@ -417,7 +423,7 @@ def abort(msg=""): abort() else: # Tell the user we're installing Python. - click.echo( + click.secho( "{} {} {} {}{}".format( click.style("Installing", bold=True), click.style(f"CPython {version}", bold=True, fg="green"), @@ -433,7 +439,7 @@ def abort(msg=""): sp.fail( environments.PIPENV_SPINNER_FAIL_TEXT.format("Failed...") ) - click.echo(fix_utf8("Something went wrong..."), err=True) + click.secho(fix_utf8("Something went wrong..."), err=True) click.secho(e.err, fg="cyan", err=True) else: sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!")) @@ -451,7 +457,7 @@ def abort(msg=""): try: assert python_version(path_to_python) == version except AssertionError: - click.echo( + click.secho( "{}: The Python you just installed is not available on your {}, apparently." "".format( click.style("Warning", fg="red", bold=True), @@ -516,7 +522,7 @@ def abort(): or click.confirm("Use existing virtualenv?", default=True) ): abort() - click.echo( + click.secho( click.style(fix_utf8("Using existing virtualenv..."), bold=True), err=True ) # Remove the virtualenv. @@ -569,7 +575,7 @@ def ensure_project( if path_to_python and project.required_python_version not in ( python_version(path_to_python) or "" ): - click.echo( + click.secho( "{}: Your Pipfile requires {} {}, " "but you are using {} ({}).".format( click.style("Warning", fg="red", bold=True), @@ -582,7 +588,7 @@ def ensure_project( ), err=True, ) - click.echo( + click.secho( " {} and rebuilding the virtual environment " "may resolve the issue.".format( click.style("$ pipenv --rm", fg="green") @@ -590,7 +596,7 @@ def ensure_project( err=True, ) if not deploy: - click.echo( + click.secho( " {} will surely fail." "".format(click.style("$ pipenv check", fg="yellow")), err=True, @@ -624,7 +630,7 @@ def do_where(project, virtualenv=False, bare=True): """Executes the where functionality.""" if not virtualenv: if not project.pipfile_exists: - click.echo( + click.secho( "No Pipfile present at project home. Consider running " "{} first to automatically generate a Pipfile for you." "".format(click.style("`pipenv install`", fg="green")), @@ -635,19 +641,19 @@ def do_where(project, virtualenv=False, bare=True): # Shorten the virtual display of the path to the virtualenv. if not bare: location = shorten_path(location) - click.echo( + click.secho( "Pipfile found at {}.\n Considering this to be the project home." "".format(click.style(location, fg="green")), err=True, ) else: - click.echo(project.project_directory) + click.secho(project.project_directory) else: location = project.virtualenv_location if not bare: click.secho(f"Virtualenv location: {location}", fg="green", err=True) else: - click.echo(location) + click.secho(location) def _cleanup_procs(project, procs, failed_deps_queue, retry=True): @@ -669,7 +675,7 @@ def _cleanup_procs(project, procs, failed_deps_queue, retry=True): # additional passes at installation if "does not match installed location" in err: project.environment.expand_egg_links() - click.echo( + click.secho( "{}".format( click.style( "Failed initial installation: Failed to overwrite existing " @@ -696,7 +702,7 @@ def _cleanup_procs(project, procs, failed_deps_queue, retry=True): # Alert the user. dep = c.dep.copy() dep.use_pep517 = False - click.echo( + click.secho( "{} {}! Will try again.".format( click.style("An error occurred while installing", fg="red"), click.style(dep.as_line(), fg="green"), @@ -826,25 +832,19 @@ def do_install_dependencies( # Load the lockfile if it exists, or if dev_only is being used. if skip_lock or not project.lockfile_exists: if not bare: - click.echo( - click.style( - fix_utf8("Installing dependencies from Pipfile..."), bold=True - ) - ) + click.secho(fix_utf8("Installing dependencies from Pipfile..."), bold=True) # skip_lock should completely bypass the lockfile (broken in 4dac1676) lockfile = project.get_or_create_lockfile(from_pipfile=True) else: lockfile = project.get_or_create_lockfile() if not bare: - click.echo( - click.style( - fix_utf8( - "Installing dependencies from Pipfile.lock ({})...".format( - lockfile["_meta"].get("hash", {}).get("sha256")[-6:] - ) - ), - bold=True, - ) + click.secho( + fix_utf8( + "Installing dependencies from Pipfile.lock ({})...".format( + lockfile["_meta"].get("hash", {}).get("sha256")[-6:] + ) + ), + bold=True, ) dev = dev or dev_only deps_list = list(lockfile.get_requirements(dev=dev, only=dev_only)) @@ -854,8 +854,8 @@ def do_install_dependencies( ) index_args = " ".join(index_args).replace(" -", "\n-") deps = [req.as_line(sources=False, include_hashes=False) for req in deps_list] - click.echo(index_args) - click.echo("\n".join(sorted(deps))) + click.secho(index_args) + click.secho("\n".join(sorted(deps))) sys.exit(0) if concurrent: nprocs = project.s.PIPENV_MAX_SUBPROCESS @@ -885,7 +885,7 @@ def do_install_dependencies( # Iterate over the hopefully-poorly-packaged dependencies... if not failed_deps_queue.empty(): - click.echo( + click.secho( click.style( fix_utf8("Installing initially failed dependencies..."), bold=True ) @@ -910,7 +910,7 @@ def do_install_dependencies( while not failed_deps_queue.empty(): failed_dep = failed_deps_queue.get() failed_list.append(failed_dep) - click.echo( + click.secho( click.style( f"Failed to install some dependency or packages. " f"The following have failed installation and attempted retry: {failed_list}", @@ -939,12 +939,12 @@ def convert_three_to_python(three, python): def do_create_virtualenv(project, python=None, site_packages=None, pypi_mirror=None): """Creates a virtualenv.""" - click.echo( + click.secho( click.style(fix_utf8("Creating a virtualenv for this project..."), bold=True), err=True, ) - click.echo( + click.secho( "Pipfile: " + click.style(project.pipfile_location, fg="yellow", bold=True), err=True, ) @@ -954,7 +954,7 @@ def do_create_virtualenv(project, python=None, site_packages=None, pypi_mirror=N if not python: python = sys.executable using_string = "Using default python from" - click.echo( + click.secho( "{0} {1} {3} {2}".format( click.style(using_string, bold=True), click.style(python, fg="yellow", bold=True), @@ -975,7 +975,7 @@ def do_create_virtualenv(project, python=None, site_packages=None, pypi_mirror=N # Pass site-packages flag to virtualenv, if desired... if site_packages: - click.echo( + click.secho( click.style(fix_utf8("Making site-packages available..."), bold=True), err=True, ) @@ -1121,7 +1121,7 @@ def do_lock( if write: # Alert the user of progress. - click.echo( + click.secho( "{} {} {}".format( click.style("Locking"), click.style( @@ -1174,7 +1174,7 @@ def do_lock( ) if write: project.write_lockfile(lockfile) - click.echo( + click.secho( "{}".format( click.style( "Updated Pipfile.lock ({})!".format( @@ -1194,9 +1194,7 @@ def do_purge(project, bare=False, downloads=False, allow_global=False): if downloads: if not bare: - click.echo( - click.style(fix_utf8("Clearing out downloads directory..."), bold=True) - ) + click.secho(fix_utf8("Clearing out downloads directory..."), bold=True) shutil.rmtree(project.download_location) return @@ -1212,12 +1210,12 @@ def do_purge(project, bare=False, downloads=False, allow_global=False): # Skip purging if there is no packages which needs to be removed if not to_remove: if not bare: - click.echo("Found 0 installed package, skip purging.") + click.secho("Found 0 installed package, skip purging.") click.secho("Environment now purged and fresh!", fg="green") return installed if not bare: - click.echo(fix_utf8(f"Found {len(to_remove)} installed package(s), purging...")) + click.secho(fix_utf8(f"Found {len(to_remove)} installed package(s), purging...")) command = [ which_pip(project, allow_global=allow_global), @@ -1225,7 +1223,7 @@ def do_purge(project, bare=False, downloads=False, allow_global=False): "-y", ] + list(to_remove) if project.s.is_verbose(): - click.echo(f"$ {cmd_list_to_shell(command)}") + click.secho(f"$ {cmd_list_to_shell(command)}") c = subprocess_run(command) if c.returncode != 0: raise exceptions.UninstallError( @@ -1332,8 +1330,9 @@ def do_init( "See also: --deploy flag.", ) else: - click.echo( - click.style(fix_utf8("Pipfile.lock not found, creating..."), bold=True), + click.secho( + fix_utf8("Pipfile.lock not found, creating..."), + bold=True, err=True, ) do_lock( @@ -1356,9 +1355,17 @@ def do_init( pypi_mirror=pypi_mirror, ) + # warn the user that PIPENV_COLORBLIND is deprecated + if "PIPENV_COLORBLIND" in os.environ: + click.secho( + "PIPENV_COLORBLIND is deprecated, use NO_COLOR instead" + "Per https://no-color.org/", + err=True, + ) + # Hint the user what to do to activate the virtualenv. if not allow_global and not deploy and "PIPENV_ACTIVE" not in os.environ: - click.echo( + click.secho( "To activate this project's virtualenv, run {}.\n" "Alternatively, run a command " "inside the virtualenv with {}.".format( @@ -1454,7 +1461,7 @@ def write_requirement_to_file( prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir, delete=False ) if project.s.is_verbose(): - click.echo( + click.secho( f"Writing supplied requirement line to temporary file: {line!r}", err=True ) f.write(vistir.misc.to_bytes(line)) @@ -1537,10 +1544,7 @@ def pip_install( if project.s.is_verbose(): piplogger.setLevel(logging.WARN) if requirement: - click.echo( - click.style(f"Installing {requirement.name!r}", bold=True), - err=True, - ) + click.secho(f"Installing {requirement.name!r}", bold=True, err=True) pip_command = [ project._which("python", allow_global=allow_global), @@ -1565,7 +1569,7 @@ def pip_install( pip_command.extend(line) pip_command.extend(prepare_pip_source_args(sources)) if project.s.is_verbose(): - click.echo(f"$ {cmd_list_to_shell(pip_command)}", err=True) + click.secho(f"$ {cmd_list_to_shell(pip_command)}", err=True) cache_dir = Path(project.s.PIPENV_CACHE_DIR) default_exists_action = "w" if selective_upgrade: @@ -1580,7 +1584,7 @@ def pip_install( } if src_dir: if project.s.is_verbose(): - click.echo(f"Using source directory: {src_dir!r}", err=True) + click.secho(f"Using source directory: {src_dir!r}", err=True) pip_config.update({"PIP_SRC": src_dir}) c = subprocess_run(pip_command, block=block, env=pip_config) c.env = pip_config @@ -1679,7 +1683,7 @@ def system_which(command, path=None): env = {"PATH": path} if path else None c = subprocess_run(f"{_which} {command}", shell=True, env=env) if c.returncode == 127: - click.echo( + click.secho( "{}: the {} system utility is required for Pipenv to find Python installations properly." "\n Please install it.".format( click.style("Warning", fg="red", bold=True), @@ -1794,7 +1798,7 @@ def gen(out): def warn_in_virtualenv(project): # Only warn if pipenv isn't already active. if environments.is_in_virtualenv() and not project.s.is_quiet(): - click.echo( + click.secho( "{}: Pipenv found itself running within a virtual environment, " "so it will automatically use that environment, instead of " "creating its own for any project. You can set " @@ -1818,16 +1822,14 @@ def ensure_lockfile(project, keep_outdated=False, pypi_mirror=None): old_hash = project.get_lockfile_hash() new_hash = project.calculate_pipfile_hash() if new_hash != old_hash: - click.echo( - click.style( - fix_utf8( - "Pipfile.lock ({}) out of date, updating to ({})...".format( - old_hash[-6:], new_hash[-6:] - ) - ), - fg="yellow", - bold=True, + click.secho( + fix_utf8( + "Pipfile.lock ({}) out of date, updating to ({})...".format( + old_hash[-6:], new_hash[-6:] + ) ), + fg="yellow", + bold=True, err=True, ) do_lock(project, keep_outdated=keep_outdated, pypi_mirror=pypi_mirror) @@ -1837,7 +1839,7 @@ def ensure_lockfile(project, keep_outdated=False, pypi_mirror=None): def do_py(project, ctx=None, system=False): if not project.virtualenv_exists: - click.echo( + click.secho( "{}({}){}".format( click.style("No virtualenv has been created for this project ", fg="red"), click.style(project.project_directory, fg="yellow", bold=True), @@ -1848,9 +1850,9 @@ def do_py(project, ctx=None, system=False): ctx.abort() try: - click.echo(project._which("python", allow_global=system)) + click.secho(project._which("python", allow_global=system)) except AttributeError: - click.echo(click.style("No project found!", fg="red")) + click.secho("No project found!", fg="red") def do_outdated(project, pypi_mirror=None, pre=False, clear=False): @@ -1915,7 +1917,7 @@ def do_outdated(project, pypi_mirror=None, pre=False, clear=False): pipfile_version_text = f" ({version} set in Pipfile)" else: pipfile_version_text = " (Unpinned in Pipfile)" - click.echo( + click.secho( click.style( "Skipped Update of Package {!s}: {!s} installed,{!s}{!s}, " "{!s} available.".format( @@ -1926,10 +1928,10 @@ def do_outdated(project, pypi_mirror=None, pre=False, clear=False): err=True, ) if not outdated: - click.echo(click.style("All packages are up to date!", fg="green", bold=True)) + click.secho("All packages are up to date!", fg="green", bold=True) sys.exit(0) for package, new_version, old_version in outdated: - click.echo( + click.secho( "Package {!r} out-of-date: {!r} installed, {!r} available.".format( package, old_version, new_version ) @@ -2009,7 +2011,7 @@ def do_install( os.environ["PIPENV_USE_SYSTEM"] = "1" # Check if the file is remote or not if remote: - click.echo( + click.secho( click.style( fix_utf8("Remote requirements file provided! Downloading..."), bold=True ), @@ -2041,7 +2043,7 @@ def do_install( remote = True if requirementstxt: error, traceback = None, None - click.echo( + click.secho( click.style( fix_utf8("Requirements file provided! Importing into Pipfile..."), bold=True, @@ -2071,7 +2073,7 @@ def do_install( os.remove(temp_reqs) if error and traceback: click.secho(error, fg="red") - click.secho(click.style(str(traceback)), fg="yellow", err=True) + click.secho(str(traceback), fg="yellow", err=True) sys.exit(1) # Allow more than one package to be provided. @@ -2332,16 +2334,12 @@ def do_uninstall( # Uninstall [dev-packages], if --dev was provided. if all_dev: if "dev-packages" not in project.parsed_pipfile and not project_pkg_names["dev"]: - click.echo( - click.style( - "No {} to uninstall.".format( - click.style("[dev-packages]", fg="yellow") - ), - bold=True, - ) + click.secho( + "No {} to uninstall.".format(click.style("[dev-packages]", fg="yellow")), + bold=True, ) return - click.echo( + click.secho( click.style( fix_utf8( "Un-installing {}...".format( @@ -2358,13 +2356,13 @@ def do_uninstall( ignored_packages = bad_pkgs & set(list(package_map.keys())) for ignored_pkg in ignored_packages: if project.s.is_verbose(): - click.echo(f"Ignoring {ignored_pkg}.", err=True) + click.secho(f"Ignoring {ignored_pkg}.", err=True) package_names.discard(package_map[ignored_pkg]) used_packages = project_pkg_names["combined"] & installed_package_names failure = False if all: - click.echo( + click.secho( click.style( fix_utf8( "Un-installing all {} and {}...".format( @@ -2405,7 +2403,7 @@ def do_uninstall( in_packages = project.get_package_name_in_pipfile(package_name, dev=False) in_dev_packages = project.get_package_name_in_pipfile(package_name, dev=True) if normalized in lockfile_packages: - click.echo( + click.secho( "{} {} {} {}".format( click.style("Removing", fg="cyan"), click.style(package_name, fg="green"), @@ -2422,7 +2420,7 @@ def do_uninstall( if not (in_dev_packages or in_packages): if normalized in lockfile_packages: continue - click.echo( + click.secho( "No package {} to remove from Pipfile.".format( click.style(package_name, fg="green") ) @@ -2461,7 +2459,7 @@ def do_shell( from .shells import choose_shell shell = choose_shell(project) - click.echo(fix_utf8("Launching subshell in virtual environment..."), err=True) + click.secho(fix_utf8("Launching subshell in virtual environment..."), err=True) fork_args = ( project.virtualenv_location, @@ -2483,7 +2481,7 @@ def do_shell( try: shell.fork_compat(*fork_args) except (AttributeError, ImportError): - click.echo( + click.secho( fix_utf8( "Compatibility mode not supported. " "Trying to continue as well-configured shell..." @@ -2503,7 +2501,7 @@ def _inline_activate_virtualenv(project): exec(code, dict(__file__=activate_this)) # Catch all errors, just in case. except Exception: - click.echo( + click.secho( "{}: There was an unexpected error while activating your " "virtualenv. Continuing anyway...".format( click.style("Warning", fg="red", bold=True) @@ -2577,7 +2575,7 @@ def do_run_posix(project, script, command, env): command_path = system_which(script.command, path=path) if not command_path: if project.has_script(command): - click.echo( + click.secho( "{}: the command {} (from {}) could not be found within {}." "".format( click.style("Error", fg="red", bold=True), @@ -2588,7 +2586,7 @@ def do_run_posix(project, script, command, env): err=True, ) else: - click.echo( + click.secho( "{}: the command {} could not be found within {} or Pipfile's {}." "".format( click.style("Error", fg="red", bold=True), @@ -2650,9 +2648,9 @@ def do_run( script = project.build_script(command, args) cmd_string = cmd_list_to_shell([script.command] + script.args) if project.s.is_verbose(): - click.echo(click.style(f"$ {cmd_string}"), err=True) + click.secho(f"$ {cmd_string}", err=True) except ScriptEmptyError: - click.echo("Can't run script {0!r}-it's empty?", err=True) + click.secho("Can't run script {0!r}-it's empty?", err=True) run_args = [project, script] run_kwargs = {"env": env} # We're using `do_run_nt` on CI (even if we're running on a non-nt machine) @@ -2690,7 +2688,7 @@ def do_check( pypi_mirror=pypi_mirror, ) if not quiet and not project.s.is_quiet(): - click.echo( + click.secho( click.style(decode_for_output("Checking PEP 508 requirements..."), bold=True) ) pep508checker_path = pep508checker.__file__.rstrip("cdo") @@ -2713,7 +2711,7 @@ def do_check( try: results = simplejson.loads(c.stdout.strip()) except json.JSONDecodeError: - click.echo( + click.secho( "{}\n{}\n{}".format( click.style( decode_for_output("Failed parsing pep508 results: "), @@ -2735,7 +2733,7 @@ def do_check( assert results[marker] == specifier except AssertionError: failed = True - click.echo( + click.secho( "Specifier {} does not match {} ({})." "".format( click.style(marker, fg="green"), @@ -2749,9 +2747,9 @@ def do_check( sys.exit(1) else: if not quiet and not project.s.is_quiet(): - click.echo(click.style("Passed!", fg="green")) + click.secho("Passed!", fg="green") if not quiet and not project.s.is_quiet(): - click.echo( + click.secho( click.style( decode_for_output("Checking installed package safety..."), bold=True ) @@ -2761,7 +2759,7 @@ def do_check( ignore = [ignore] ignored = [["--ignore", cve] for cve in ignore] if not quiet and not project.s.is_quiet(): - click.echo( + click.secho( "Notice: Ignoring CVE(s) {}".format( click.style(", ".join(ignore), fg="yellow") ), @@ -2777,7 +2775,7 @@ def do_check( cmd = _cmd + [safety_path, "check", f"--{switch}"] if db: if not quiet and not project.s.is_quiet(): - click.echo(click.style(f"Using local database {db}")) + click.secho(f"Using local database {db}") cmd.append(f"--db={db}") elif key or project.s.PIPENV_PYUP_API_KEY: cmd = cmd + [f"--key={key or project.s.PIPENV_PYUP_API_KEY}"] @@ -2795,7 +2793,7 @@ def do_check( cmd_list_to_shell(c.args), c.stdout, c.stderr, c.returncode ) for (package, resolved, installed, description, vuln, *_) in results: - click.echo( + click.secho( "{}: {} {} resolved ({} installed)!".format( click.style(vuln, bold=True), click.style(package, fg="green"), @@ -2803,15 +2801,15 @@ def do_check( click.style(installed, fg="yellow", bold=True), ) ) - click.echo(f"{description}") - click.echo() + click.secho(f"{description}") + click.secho() if c.returncode == 0: - click.echo(click.style("All good!", fg="green")) + click.secho("All good!", fg="green") sys.exit(0) else: sys.exit(1) else: - click.echo(c.stdout) + click.secho(c.stdout) sys.exit(c.returncode) @@ -2824,7 +2822,7 @@ def do_graph(project, bare=False, json=False, json_tree=False, reverse=False): try: python_path = project._which("python") except AttributeError: - click.echo( + click.secho( "{}: {}".format( click.style("Warning", fg="red", bold=True), "Unable to display currently-installed dependency graph information here. " @@ -2841,7 +2839,7 @@ def do_graph(project, bare=False, json=False, json_tree=False, reverse=False): pipdeptree_path = Path(pipdeptree_path).as_posix() if reverse and json: - click.echo( + click.secho( "{}: {}".format( click.style("Warning", fg="red", bold=True), "Using both --reverse and --json together is not supported. " @@ -2851,7 +2849,7 @@ def do_graph(project, bare=False, json=False, json_tree=False, reverse=False): ) sys.exit(1) if reverse and json_tree: - click.echo( + click.secho( "{}: {}".format( click.style("Warning", fg="red", bold=True), "Using both --reverse and --json-tree together is not supported. " @@ -2861,7 +2859,7 @@ def do_graph(project, bare=False, json=False, json_tree=False, reverse=False): ) sys.exit(1) if json and json_tree: - click.echo( + click.secho( "{}: {}".format( click.style("Warning", fg="red", bold=True), "Using both --json and --json-tree together is not supported. " @@ -2878,7 +2876,7 @@ def do_graph(project, bare=False, json=False, json_tree=False, reverse=False): if reverse: flag = "--reverse" if not project.virtualenv_exists: - click.echo( + click.secho( "{}: No virtualenv has been created for this project yet! Consider " "running {} first to automatically generate one for you or see " "{} for further instructions.".format( @@ -2905,7 +2903,7 @@ def do_graph(project, bare=False, json=False, json_tree=False, reverse=False): for d in parsed: if d["package"]["key"] not in BAD_PACKAGES: data.append(d) - click.echo(simplejson.dumps(data, indent=4)) + click.secho(simplejson.dumps(data, indent=4)) sys.exit(0) elif json_tree: @@ -2926,7 +2924,7 @@ def traverse(obj): raise exceptions.JSONParseError(c.stdout, c.stderr) else: data = traverse(parsed) - click.echo(simplejson.dumps(data, indent=4)) + click.secho(simplejson.dumps(data, indent=4)) sys.exit(0) else: for line in c.stdout.strip().split("\n"): @@ -2937,14 +2935,14 @@ def traverse(obj): # Bold top-level packages. if not line.startswith(" "): - click.echo(click.style(line, bold=True)) + click.secho(line, bold=True) # Echo the rest. else: - click.echo(click.style(line, bold=False)) + click.secho(line, bold=False) else: - click.echo(c.stdout) + click.secho(c.stdout) if c.returncode != 0: - click.echo( + click.secho( "{} {}".format( click.style("ERROR: ", fg="red", bold=True), click.style(f"{c.stderr}", fg="white"), @@ -3005,7 +3003,7 @@ def do_sync( system=system, ) if not bare: - click.echo(click.style("All dependencies are now up-to-date!", fg="green")) + click.secho("All dependencies are now up-to-date!", fg="green") def do_clean( @@ -3031,7 +3029,7 @@ def do_clean( for bad_package in BAD_PACKAGES: if canonicalize_name(bad_package) in installed_package_names: if project.s.is_verbose(): - click.echo(f"Ignoring {bad_package}.", err=True) + click.secho(f"Ignoring {bad_package}.", err=True) installed_package_names.remove(canonicalize_name(bad_package)) # Intelligently detect if --dev should be used or not. locked_packages = { @@ -3044,7 +3042,7 @@ def do_clean( cmd = [which_pip(project, allow_global=system), "uninstall", "-y", "-qq"] for apparent_bad_package in installed_package_names: if dry_run and not bare: - click.echo(apparent_bad_package) + click.secho(apparent_bad_package) else: if not bare: click.secho( diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py index ac5de01012..d8533ec692 100644 --- a/tests/unit/test_core.py +++ b/tests/unit/test_core.py @@ -2,8 +2,9 @@ from tempfile import TemporaryDirectory import pytest +from mock import Mock -from pipenv.core import load_dot_env, warn_in_virtualenv +from pipenv.core import load_dot_env, warn_in_virtualenv, colorwise_secho from pipenv.utils.shell import temp_environ @@ -68,3 +69,23 @@ def test_load_dot_env_warns_if_file_doesnt_exist(monkeypatch, capsys, project): load_dot_env(project) output, err = capsys.readouterr() assert 'Warning' in err + + +@pytest.mark.core +def test_colorwise_secho_respects_environment_variables(): + # colorwise_secho is checking for $NO_COLOR or $PIPENV_COLORBLIND, + # it will uncolor TTY output if they exist and are truthy. + + with temp_environ(): + from pipenv.vendor import click + mock_echo = Mock() + click.echo = mock_echo + + # with color-wanting default env secho will be instructed to use color + colorwise_secho("a message", fg="red") + mock_echo.assert_called_with("a message",file=None, nl=True, err=False, color=True) + + # when request for no color comes secho will do that and flag color false + os.environ["NO_COLOR"] = "1" + colorwise_secho("a message", fg="red") + mock_echo.assert_called_with(click.style("a message", fg="red"),file=None, nl=True, err=False, color=False) \ No newline at end of file