1# -*- coding: utf-8 -*- 2from __future__ import absolute_import 3 4import os 5import sys 6 7from click import ( 8 argument, echo, edit, group, option, pass_context, secho, version_option, Choice 9) 10 11from ..__version__ import __version__ 12from .._compat import fix_utf8 13from ..exceptions import PipenvOptionsError 14from ..patched import crayons 15from ..vendor import click_completion, delegator 16from .options import ( 17 CONTEXT_SETTINGS, PipenvGroup, code_option, common_options, deploy_option, 18 general_options, install_options, lock_options, pass_state, 19 pypi_mirror_option, python_option, site_packages_option, skip_lock_option, 20 sync_options, system_option, three_option, uninstall_options, 21 verbose_option 22) 23 24 25# Enable shell completion. 26click_completion.init() 27 28subcommand_context = CONTEXT_SETTINGS.copy() 29subcommand_context.update({ 30 "ignore_unknown_options": True, 31 "allow_extra_args": True 32}) 33subcommand_context_no_interspersion = subcommand_context.copy() 34subcommand_context_no_interspersion["allow_interspersed_args"] = False 35 36 37@group(cls=PipenvGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS) 38@option("--where", is_flag=True, default=False, help="Output project home information.") 39@option("--venv", is_flag=True, default=False, help="Output virtualenv information.") 40@option("--py", is_flag=True, default=False, help="Output Python interpreter information.") 41@option("--envs", is_flag=True, default=False, help="Output Environment Variable options.") 42@option("--rm", is_flag=True, default=False, help="Remove the virtualenv.") 43@option("--bare", is_flag=True, default=False, help="Minimal output.") 44@option( 45 "--completion", 46 is_flag=True, 47 default=False, 48 help="Output completion (to be executed by the shell).", 49) 50@option("--man", is_flag=True, default=False, help="Display manpage.") 51@option( 52 "--support", 53 is_flag=True, 54 help="Output diagnostic information for use in GitHub issues.", 55) 56@general_options 57@version_option(prog_name=crayons.normal("pipenv", bold=True), version=__version__) 58@pass_state 59@pass_context 60def cli( 61 ctx, 62 state, 63 where=False, 64 venv=False, 65 py=False, 66 envs=False, 67 rm=False, 68 bare=False, 69 completion=False, 70 man=False, 71 support=None, 72 help=False, 73 site_packages=None, 74 **kwargs 75): 76 # Handle this ASAP to make shell startup fast. 77 if completion: 78 from .. import shells 79 80 try: 81 shell = shells.detect_info()[0] 82 except shells.ShellDetectionFailure: 83 echo( 84 "Fail to detect shell. Please provide the {0} environment " 85 "variable.".format(crayons.normal("PIPENV_SHELL", bold=True)), 86 err=True, 87 ) 88 ctx.abort() 89 print(click_completion.get_code(shell=shell, prog_name="pipenv")) 90 return 0 91 92 from ..core import ( 93 system_which, 94 do_py, 95 warn_in_virtualenv, 96 do_where, 97 project, 98 cleanup_virtualenv, 99 ensure_project, 100 format_help, 101 do_clear, 102 ) 103 from ..utils import create_spinner 104 105 if man: 106 if system_which("man"): 107 path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pipenv.1") 108 os.execle(system_which("man"), "man", path, os.environ) 109 return 0 110 else: 111 secho("man does not appear to be available on your system.", fg="yellow", bold=True, err=True) 112 return 1 113 if envs: 114 echo("The following environment variables can be set, to do various things:\n") 115 from .. import environments 116 for key in environments.__dict__: 117 if key.startswith("PIPENV"): 118 echo(" - {0}".format(crayons.normal(key, bold=True))) 119 echo( 120 "\nYou can learn more at:\n {0}".format( 121 crayons.green( 122 "https://pipenv.pypa.io/en/latest/advanced/#configuration-with-environment-variables" 123 ) 124 ) 125 ) 126 return 0 127 warn_in_virtualenv() 128 if ctx.invoked_subcommand is None: 129 # --where was passed... 130 if where: 131 do_where(bare=True) 132 return 0 133 elif py: 134 do_py() 135 return 0 136 # --support was passed... 137 elif support: 138 from ..help import get_pipenv_diagnostics 139 140 get_pipenv_diagnostics() 141 return 0 142 # --clear was passed... 143 elif state.clear: 144 do_clear() 145 return 0 146 # --venv was passed... 147 elif venv: 148 # There is no virtualenv yet. 149 if not project.virtualenv_exists: 150 echo( 151 "{}({}){}".format( 152 crayons.red("No virtualenv has been created for this project"), 153 crayons.normal(project.project_directory, bold=True), 154 crayons.red(" yet!") 155 ), 156 err=True, 157 ) 158 ctx.abort() 159 else: 160 echo(project.virtualenv_location) 161 return 0 162 # --rm was passed... 163 elif rm: 164 # Abort if --system (or running in a virtualenv). 165 from ..environments import PIPENV_USE_SYSTEM 166 if PIPENV_USE_SYSTEM: 167 echo( 168 crayons.red( 169 "You are attempting to remove a virtualenv that " 170 "Pipenv did not create. Aborting." 171 ) 172 ) 173 ctx.abort() 174 if project.virtualenv_exists: 175 loc = project.virtualenv_location 176 echo( 177 crayons.normal( 178 u"{0} ({1})...".format( 179 crayons.normal("Removing virtualenv", bold=True), 180 crayons.green(loc), 181 ) 182 ) 183 ) 184 with create_spinner(text="Running..."): 185 # Remove the virtualenv. 186 cleanup_virtualenv(bare=True) 187 return 0 188 else: 189 echo( 190 crayons.red( 191 "No virtualenv has been created for this project yet!", 192 bold=True, 193 ), 194 err=True, 195 ) 196 ctx.abort() 197 # --two / --three was passed... 198 if (state.python or state.three is not None) or state.site_packages: 199 ensure_project( 200 three=state.three, 201 python=state.python, 202 warn=True, 203 site_packages=state.site_packages, 204 pypi_mirror=state.pypi_mirror, 205 clear=state.clear, 206 ) 207 # Check this again before exiting for empty ``pipenv`` command. 208 elif ctx.invoked_subcommand is None: 209 # Display help to user, if no commands were passed. 210 echo(format_help(ctx.get_help())) 211 212 213@cli.command( 214 short_help="Installs provided packages and adds them to Pipfile, or (if no packages are given), installs all packages from Pipfile.", 215 context_settings=subcommand_context, 216) 217@system_option 218@code_option 219@deploy_option 220@site_packages_option 221@skip_lock_option 222@install_options 223@pass_state 224@pass_context 225def install( 226 ctx, 227 state, 228 **kwargs 229): 230 """Installs provided packages and adds them to Pipfile, or (if no packages are given), installs all packages from Pipfile.""" 231 from ..core import do_install 232 233 retcode = do_install( 234 dev=state.installstate.dev, 235 three=state.three, 236 python=state.python, 237 pypi_mirror=state.pypi_mirror, 238 system=state.system, 239 lock=not state.installstate.skip_lock, 240 ignore_pipfile=state.installstate.ignore_pipfile, 241 skip_lock=state.installstate.skip_lock, 242 requirementstxt=state.installstate.requirementstxt, 243 sequential=state.installstate.sequential, 244 pre=state.installstate.pre, 245 code=state.installstate.code, 246 deploy=state.installstate.deploy, 247 keep_outdated=state.installstate.keep_outdated, 248 selective_upgrade=state.installstate.selective_upgrade, 249 index_url=state.index, 250 extra_index_url=state.extra_index_urls, 251 packages=state.installstate.packages, 252 editable_packages=state.installstate.editables, 253 site_packages=state.site_packages 254 ) 255 if retcode: 256 ctx.abort() 257 258 259@cli.command( 260 short_help="Uninstalls a provided package and removes it from Pipfile.", 261 context_settings=subcommand_context 262) 263@option( 264 "--all-dev", 265 is_flag=True, 266 default=False, 267 help="Uninstall all package from [dev-packages].", 268) 269@option( 270 "--all", 271 is_flag=True, 272 default=False, 273 help="Purge all package(s) from virtualenv. Does not edit Pipfile.", 274) 275@uninstall_options 276@pass_state 277@pass_context 278def uninstall( 279 ctx, 280 state, 281 all_dev=False, 282 all=False, 283 **kwargs 284): 285 """Uninstalls a provided package and removes it from Pipfile.""" 286 from ..core import do_uninstall 287 retcode = do_uninstall( 288 packages=state.installstate.packages, 289 editable_packages=state.installstate.editables, 290 three=state.three, 291 python=state.python, 292 system=state.system, 293 lock=not state.installstate.skip_lock, 294 all_dev=all_dev, 295 all=all, 296 keep_outdated=state.installstate.keep_outdated, 297 pypi_mirror=state.pypi_mirror, 298 ctx=ctx 299 ) 300 if retcode: 301 sys.exit(retcode) 302 303 304LOCK_HEADER = """\ 305# 306# These requirements were autogenerated by pipenv 307# To regenerate from the project's Pipfile, run: 308# 309# pipenv lock {options} 310# 311""" 312 313 314LOCK_DEV_NOTE = """\ 315# Note: in pipenv 2020.x, "--dev" changed to emit both default and development 316# requirements. To emit only development requirements, pass "--dev-only". 317""" 318 319 320@cli.command(short_help="Generates Pipfile.lock.", context_settings=CONTEXT_SETTINGS) 321@lock_options 322@pass_state 323@pass_context 324def lock( 325 ctx, 326 state, 327 **kwargs 328): 329 """Generates Pipfile.lock.""" 330 from ..core import ensure_project, do_init, do_lock 331 # Ensure that virtualenv is available. 332 # Note that we don't pass clear on to ensure_project as it is also 333 # handled in do_lock 334 ensure_project( 335 three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, 336 warn=(not state.quiet), site_packages=state.site_packages, 337 ) 338 emit_requirements = state.lockoptions.emit_requirements 339 dev = state.installstate.dev 340 dev_only = state.lockoptions.dev_only 341 pre = state.installstate.pre 342 if emit_requirements: 343 # Emit requirements file header (unless turned off with --no-header) 344 if state.lockoptions.emit_requirements_header: 345 header_options = ["--requirements"] 346 if dev_only: 347 header_options.append("--dev-only") 348 elif dev: 349 header_options.append("--dev") 350 echo(LOCK_HEADER.format(options=" ".join(header_options))) 351 # TODO: Emit pip-compile style header 352 if dev and not dev_only: 353 echo(LOCK_DEV_NOTE) 354 # Setting "emit_requirements=True" means do_init() just emits the 355 # install requirements file to stdout, it doesn't install anything 356 do_init( 357 dev=dev, 358 dev_only=dev_only, 359 emit_requirements=emit_requirements, 360 pypi_mirror=state.pypi_mirror, 361 pre=pre, 362 ) 363 elif state.lockoptions.dev_only: 364 raise PipenvOptionsError( 365 "--dev-only", 366 "--dev-only is only permitted in combination with --requirements. " 367 "Aborting." 368 ) 369 do_lock( 370 ctx=ctx, 371 clear=state.clear, 372 pre=pre, 373 keep_outdated=state.installstate.keep_outdated, 374 pypi_mirror=state.pypi_mirror, 375 write=not state.quiet, 376 ) 377 378 379@cli.command( 380 short_help="Spawns a shell within the virtualenv.", 381 context_settings=subcommand_context, 382) 383@option( 384 "--fancy", 385 is_flag=True, 386 default=False, 387 help="Run in shell in fancy mode. Make sure the shell have no path manipulating" 388 " scripts. Run $pipenv shell for issues with compatibility mode.", 389) 390@option( 391 "--anyway", 392 is_flag=True, 393 default=False, 394 help="Always spawn a sub-shell, even if one is already spawned.", 395) 396@argument("shell_args", nargs=-1) 397@pypi_mirror_option 398@three_option 399@python_option 400@pass_state 401def shell( 402 state, 403 fancy=False, 404 shell_args=None, 405 anyway=False, 406): 407 """Spawns a shell within the virtualenv.""" 408 from ..core import load_dot_env, do_shell 409 410 # Prevent user from activating nested environments. 411 if "PIPENV_ACTIVE" in os.environ: 412 # If PIPENV_ACTIVE is set, VIRTUAL_ENV should always be set too. 413 venv_name = os.environ.get("VIRTUAL_ENV", "UNKNOWN_VIRTUAL_ENVIRONMENT") 414 if not anyway: 415 echo( 416 "{0} {1} {2}\nNo action taken to avoid nested environments.".format( 417 crayons.normal("Shell for"), 418 crayons.green(venv_name, bold=True), 419 crayons.normal("already activated.", bold=True), 420 ), 421 err=True, 422 ) 423 sys.exit(1) 424 # Load .env file. 425 load_dot_env() 426 # Use fancy mode for Windows. 427 if os.name == "nt": 428 fancy = True 429 do_shell( 430 three=state.three, 431 python=state.python, 432 fancy=fancy, 433 shell_args=shell_args, 434 pypi_mirror=state.pypi_mirror, 435 ) 436 437 438@cli.command( 439 short_help="Spawns a command installed into the virtualenv.", 440 context_settings=subcommand_context_no_interspersion, 441) 442@common_options 443@argument("command") 444@argument("args", nargs=-1) 445@pass_state 446def run(state, command, args): 447 """Spawns a command installed into the virtualenv.""" 448 from ..core import do_run 449 do_run( 450 command=command, args=args, three=state.three, python=state.python, pypi_mirror=state.pypi_mirror 451 ) 452 453 454@cli.command( 455 short_help="Checks for PyUp Safety security vulnerabilities and against" 456 " PEP 508 markers provided in Pipfile.", 457 context_settings=subcommand_context 458) 459@option( 460 "--unused", 461 nargs=1, 462 default=False, 463 help="Given a code path, show potentially unused dependencies.", 464) 465@option( 466 "--db", 467 nargs=1, 468 default=lambda: os.environ.get('PIPENV_SAFETY_DB', False), 469 help="Path to a local PyUp Safety vulnerabilities database." 470 " Default: ENV PIPENV_SAFETY_DB or None.", 471) 472@option( 473 "--ignore", 474 "-i", 475 multiple=True, 476 help="Ignore specified vulnerability during PyUp Safety checks.", 477) 478@option( 479 "--output", 480 type=Choice(["default", "json", "full-report", "bare"]), 481 default="default", 482 help="Translates to --json, --full-report or --bare from PyUp Safety check", 483) 484@option( 485 "--key", 486 help="Safety API key from PyUp.io for scanning dependencies against a live" 487 " vulnerabilities database. Leave blank for scanning against a" 488 " database that only updates once a month.", 489) 490@option( 491 "--quiet", 492 is_flag=True, 493 help="Quiet standard output, except vulnerability report." 494) 495@common_options 496@system_option 497@argument("args", nargs=-1) 498@pass_state 499def check( 500 state, 501 unused=False, 502 db=False, 503 style=False, 504 ignore=None, 505 output="default", 506 key=None, 507 quiet=False, 508 args=None, 509 **kwargs 510): 511 """Checks for PyUp Safety security vulnerabilities and against PEP 508 markers provided in Pipfile.""" 512 from ..core import do_check 513 514 do_check( 515 three=state.three, 516 python=state.python, 517 system=state.system, 518 unused=unused, 519 db=db, 520 ignore=ignore, 521 output=output, 522 key=key, 523 quiet=quiet, 524 args=args, 525 pypi_mirror=state.pypi_mirror, 526 ) 527 528 529@cli.command(short_help="Runs lock, then sync.", context_settings=CONTEXT_SETTINGS) 530@option("--bare", is_flag=True, default=False, help="Minimal output.") 531@option( 532 "--outdated", is_flag=True, default=False, help=u"List out-of-date dependencies." 533) 534@option("--dry-run", is_flag=True, default=None, help=u"List out-of-date dependencies.") 535@install_options 536@pass_state 537@pass_context 538def update( 539 ctx, 540 state, 541 bare=False, 542 dry_run=None, 543 outdated=False, 544 **kwargs 545): 546 """Runs lock, then sync.""" 547 from ..core import ( 548 ensure_project, 549 do_outdated, 550 do_lock, 551 do_sync, 552 project, 553 ) 554 ensure_project( 555 three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, 556 warn=(not state.quiet), site_packages=state.site_packages, clear=state.clear 557 ) 558 if not outdated: 559 outdated = bool(dry_run) 560 if outdated: 561 do_outdated(clear=state.clear, pre=state.installstate.pre, pypi_mirror=state.pypi_mirror) 562 packages = [p for p in state.installstate.packages if p] 563 editable = [p for p in state.installstate.editables if p] 564 if not packages: 565 echo( 566 "{0} {1} {2} {3}{4}".format( 567 crayons.normal("Running", bold=True), 568 crayons.yellow("$ pipenv lock", bold=True), 569 crayons.normal("then", bold=True), 570 crayons.yellow("$ pipenv sync", bold=True), 571 crayons.normal(".", bold=True), 572 ) 573 ) 574 else: 575 for package in packages + editable: 576 if package not in project.all_packages: 577 echo( 578 "{0}: {1} was not found in your Pipfile! Aborting." 579 "".format( 580 crayons.red("Warning", bold=True), 581 crayons.green(package, bold=True), 582 ), 583 err=True, 584 ) 585 ctx.abort() 586 do_lock( 587 ctx=ctx, 588 clear=state.clear, 589 pre=state.installstate.pre, 590 keep_outdated=state.installstate.keep_outdated, 591 pypi_mirror=state.pypi_mirror, 592 write=not state.quiet, 593 ) 594 do_sync( 595 ctx=ctx, 596 dev=state.installstate.dev, 597 three=state.three, 598 python=state.python, 599 bare=bare, 600 dont_upgrade=not state.installstate.keep_outdated, 601 user=False, 602 clear=state.clear, 603 unused=False, 604 sequential=state.installstate.sequential, 605 pypi_mirror=state.pypi_mirror, 606 ) 607 608 609@cli.command( 610 short_help=u"Displays currently-installed dependency graph information.", 611 context_settings=CONTEXT_SETTINGS 612) 613@option("--bare", is_flag=True, default=False, help="Minimal output.") 614@option("--json", is_flag=True, default=False, help="Output JSON.") 615@option("--json-tree", is_flag=True, default=False, help="Output JSON in nested tree.") 616@option("--reverse", is_flag=True, default=False, help="Reversed dependency graph.") 617def graph(bare=False, json=False, json_tree=False, reverse=False): 618 """Displays currently-installed dependency graph information.""" 619 from ..core import do_graph 620 621 do_graph(bare=bare, json=json, json_tree=json_tree, reverse=reverse) 622 623 624@cli.command( 625 short_help="View a given module in your editor.", name="open", 626 context_settings=CONTEXT_SETTINGS 627) 628@common_options 629@argument("module", nargs=1) 630@pass_state 631def run_open(state, module, *args, **kwargs): 632 """View a given module in your editor. 633 634 This uses the EDITOR environment variable. You can temporarily override it, 635 for example: 636 637 EDITOR=atom pipenv open requests 638 """ 639 from ..core import which, ensure_project, inline_activate_virtual_environment 640 641 # Ensure that virtualenv is available. 642 ensure_project( 643 three=state.three, python=state.python, 644 validate=False, pypi_mirror=state.pypi_mirror, 645 ) 646 c = delegator.run( 647 '{0} -c "import {1}; print({1}.__file__);"'.format(which("python"), module) 648 ) 649 try: 650 assert c.return_code == 0 651 except AssertionError: 652 echo(crayons.red("Module not found!")) 653 sys.exit(1) 654 if "__init__.py" in c.out: 655 p = os.path.dirname(c.out.strip().rstrip("cdo")) 656 else: 657 p = c.out.strip().rstrip("cdo") 658 echo(crayons.normal("Opening {0!r} in your EDITOR.".format(p), bold=True)) 659 inline_activate_virtual_environment() 660 edit(filename=p) 661 return 0 662 663 664@cli.command( 665 short_help="Installs all packages specified in Pipfile.lock.", 666 context_settings=CONTEXT_SETTINGS 667) 668@system_option 669@option("--bare", is_flag=True, default=False, help="Minimal output.") 670@sync_options 671@pass_state 672@pass_context 673def sync( 674 ctx, 675 state, 676 bare=False, 677 user=False, 678 unused=False, 679 **kwargs 680): 681 """Installs all packages specified in Pipfile.lock.""" 682 from ..core import do_sync 683 684 retcode = do_sync( 685 ctx=ctx, 686 dev=state.installstate.dev, 687 three=state.three, 688 python=state.python, 689 bare=bare, 690 dont_upgrade=(not state.installstate.keep_outdated), 691 user=user, 692 clear=state.clear, 693 unused=unused, 694 sequential=state.installstate.sequential, 695 pypi_mirror=state.pypi_mirror, 696 system=state.system 697 ) 698 if retcode: 699 ctx.abort() 700 701 702@cli.command( 703 short_help="Uninstalls all packages not specified in Pipfile.lock.", 704 context_settings=CONTEXT_SETTINGS 705) 706@option("--bare", is_flag=True, default=False, help="Minimal output.") 707@option("--dry-run", is_flag=True, default=False, help="Just output unneeded packages.") 708@verbose_option 709@three_option 710@python_option 711@pass_state 712@pass_context 713def clean(ctx, state, dry_run=False, bare=False, user=False): 714 """Uninstalls all packages not specified in Pipfile.lock.""" 715 from ..core import do_clean 716 do_clean(ctx=ctx, three=state.three, python=state.python, dry_run=dry_run, 717 system=state.system) 718 719 720@cli.command( 721 short_help="Lists scripts in current environment config.", 722 context_settings=subcommand_context_no_interspersion, 723) 724@common_options 725def scripts(): 726 """Lists scripts in current environment config.""" 727 from ..core import project 728 729 if not project.pipfile_exists: 730 echo("No Pipfile present at project home.", err=True) 731 sys.exit(1) 732 scripts = project.parsed_pipfile.get('scripts', {}) 733 first_column_width = max(len(word) for word in ["Command"] + list(scripts)) 734 second_column_width = max(len(word) for word in ["Script"] + list(scripts.values())) 735 lines = ["{0:<{width}} Script".format("Command", width=first_column_width)] 736 lines.append("{} {}".format("-" * first_column_width, "-" * second_column_width)) 737 lines.extend( 738 "{0:<{width}} {1}".format(name, script, width=first_column_width) 739 for name, script in scripts.items() 740 ) 741 echo("\n".join(fix_utf8(line) for line in lines)) 742 743 744if __name__ == "__main__": 745 cli() 746