From 9a3b3ce70621af6f9adaa9eeac9cf83fa149319c Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Tue, 29 Mar 2022 23:01:50 -0400 Subject: [PATCH] Issue 4993 Add standard pre commit hooks and apply linting. (#4994) * Add .pre-commit-config.yaml to the project and exclude tests (for now). This does not include the MyPy linting that pip does but does include everything else. --- .github/workflows/ci.yaml | 9 +- .isort.cfg | 2 + .pre-commit-config.yaml | 64 ++++ CHANGELOG.rst | 32 +- Pipfile | 3 + Pipfile.lock | 472 +++++++++++++++---------- docs/advanced.rst | 14 +- docs/basics.rst | 2 +- docs/conf.py | 84 ++--- docs/dev/contributing.rst | 19 +- docs/index.rst | 2 +- get-pipenv.py | 45 +-- news/4992.process.rst | 1 + news/4993.process.rst | 2 + pipenv/__init__.py | 5 +- pipenv/__main__.py | 1 - pipenv/_compat.py | 13 +- pipenv/cli/__init__.py | 2 +- pipenv/cli/command.py | 213 +++++++----- pipenv/cli/options.py | 336 +++++++++++++----- pipenv/cmdparse.py | 10 +- pipenv/core.py | 650 ++++++++++++++++++++++------------- pipenv/environment.py | 334 +++++++++++------- pipenv/environments.py | 39 ++- pipenv/exceptions.py | 164 +++++---- pipenv/help.py | 6 +- pipenv/installers.py | 80 +++-- pipenv/pipenv.1 | 10 +- pipenv/process.py | 20 +- pipenv/progress.py | 1 - pipenv/project.py | 154 +++++---- pipenv/resolver.py | 232 +++++++++---- pipenv/shells.py | 26 +- pipenv/utils/__init__.py | 1 + pipenv/utils/dependencies.py | 40 ++- pipenv/utils/indexes.py | 42 ++- pipenv/utils/internet.py | 7 +- pipenv/utils/locking.py | 21 +- pipenv/utils/processes.py | 34 +- pipenv/utils/resolver.py | 301 ++++++++++------ pipenv/utils/shell.py | 56 +-- pipenv/utils/spinner.py | 4 +- pipenv/utils/toml.py | 9 +- pyproject.toml | 5 + setup.py | 6 +- tasks/__init__.py | 1 - tasks/release.py | 47 +-- tasks/vendoring/__init__.py | 59 ++-- 48 files changed, 2330 insertions(+), 1350 deletions(-) create mode 100644 .isort.cfg create mode 100644 .pre-commit-config.yaml create mode 100644 news/4992.process.rst create mode 100644 news/4993.process.rst diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 97acea0a..aa3e688f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -72,6 +72,14 @@ jobs: git submodule update --init --recursive python -m pip install -e . --upgrade pipenv install --deploy --dev --python=${{ steps.python-path.outputs.path }} + - name: Lint check of the code + env: + PIPENV_DEFAULT_PYTHON_VERSION: ${{ matrix.python-version }} + PYTHONWARNINGS: ignore:DEPRECATION + PYTHONIOENCODING: "utf-8" + GIT_ASK_YESNO: "false" + run: | + pipenv run pre-commit run --all-files --verbose - name: Run tests env: PIPENV_DEFAULT_PYTHON_VERSION: ${{ matrix.python-version }} @@ -109,4 +117,3 @@ jobs: - run: | python -m pip install --upgrade wheel invoke parver bs4 vistir towncrier python -m invoke vendoring.update - diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 00000000..f238bf7e --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +profile = black diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..43e22bf3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,64 @@ +exclude: '^(pipenv/patched/|pipenv/vendor/|tests/)' + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: check-builtin-literals + - id: check-added-large-files + - id: check-case-conflict + - id: check-ast + - id: check-toml + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + exclude: WHEEL + - id: forbid-new-submodules + - id: trailing-whitespace + exclude: .patch + +- repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + +- repo: https://gitlab.com/pycqa/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + additional_dependencies: [ + 'flake8-bugbear==20.1.4', + 'flake8-logging-format==0.6.0', + 'flake8-implicit-str-concat==0.2.0', + ] + exclude: tests/data + +- repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + files: \.py$ + +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.7.0 + hooks: + - id: python-no-log-warn + - id: python-no-eval + - id: rst-backticks + files: .*\.rst$ + types: [file] + +- repo: local + hooks: + - id: news-fragment-filenames + name: NEWS fragment + language: fail + entry: NEWS fragment files must be named *.(feature|behavior|bugfix|vendor|doc|trivial|removal|process).rst + exclude: ^news/(towncrier_template.rst|.*\.(feature|behavior|bugfix|vendor|doc|trivial|removal|process).rst) + files: ^news/ + +- repo: https://github.com/mgedmin/check-manifest + rev: '0.46' + hooks: + - id: check-manifest + stages: [manual] diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 21903377..4edf8264 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,7 +5,7 @@ Bug Fixes --------- -- Environment variables were not being loaded when the `--quiet` flag was set `#5010 `_ +- Environment variables were not being loaded when the ``--quiet`` flag was set `#5010 `_ - It would appear that ``requirementslib`` was not fully specifying the subdirectory to ``build_pep517`` and and when a new version of ``setuptools`` was released, the test ``test_lock_nested_vcs_direct_url`` broke indicating the Pipfile.lock no longer contained the extra dependencies that should have been resolved. @@ -25,7 +25,7 @@ Features & Improvements ----------------------- - It is now possible to silence the ``Loading .env environment variables`` message on ``pipenv run`` - with the ``--quiet`` flag or the `PIPENV_QUIET` environment variable. `#4027 `_ + with the ``--quiet`` flag or the ``PIPENV_QUIET`` environment variable. `#4027 `_ Bug Fixes --------- @@ -43,7 +43,7 @@ Bug Fixes Features & Improvements ----------------------- -- Use environment variable `PIPENV_SKIP_LOCK` to control the behaviour of lock skipping. `#4797 `_ +- Use environment variable ``PIPENV_SKIP_LOCK`` to control the behaviour of lock skipping. `#4797 `_ - New CLI command ``verify``, checks the Pipfile.lock is up-to-date `#4893 `_ Behavior Changes @@ -56,15 +56,15 @@ Bug Fixes - Python versions on Windows can now be installed automatically through pyenv-win `#4525 `_ - Patched our vendored Pip to fix: Pipenv Lock (Or Install) Does Not Respect Index Specified For A Package. `#4637 `_ -- If `PIP_TARGET` is set to environment variables, Refer specified directory for calculate delta, instead default directory `#4775 `_ +- If ``PIP_TARGET`` is set to environment variables, Refer specified directory for calculate delta, instead default directory `#4775 `_ - Remove remaining mention of python2 and --two flag from codebase. `#4938 `_ -- Use `CI` environment value, over mere existence of name `#4944 `_ +- Use ``CI`` environment value, over mere existence of name `#4944 `_ - Environment variables from dot env files are now properly expanded when included in scripts. `#4975 `_ Vendored Libraries ------------------ -- Updated vendor version of `pythonfinder` from `1.2.9` to `1.2.10` which fixes a bug with WSL +- Updated vendor version of ``pythonfinder`` from ``1.2.9`` to ``1.2.10`` which fixes a bug with WSL (Windows Subsystem for Linux) when a path can not be read and Permission Denied error is encountered. `#4976 `_ Removals and Deprecations @@ -207,7 +207,7 @@ Vendored Libraries - ``tomli 1.1.0`` - ``wheel 0.36.2`` `#4747 `_ - Drop the dependencies for Python 2.7 compatibility purpose. `#4751 `_ -- Switch the dependency resolver from ``pip-tools`` to `pip`. +- Switch the dependency resolver from ``pip-tools`` to ``pip``. Update vendor libraries: - Update ``requirementslib`` from ``1.5.16`` to ``1.6.1`` @@ -416,7 +416,7 @@ Features & Improvements - Allow overriding PIPENV_INSTALL_TIMEOUT environment variable (in seconds). `#3652 `_ - Allow overriding PIP_EXISTS_ACTION evironment variable (value is passed to pip install). Possible values here: https://pip.pypa.io/en/stable/reference/pip/#exists-action-option - Useful when you need to `PIP_EXISTS_ACTION=i` (ignore existing packages) - great for CI environments, where you need really fast setup. `#3738 `_ + Useful when you need to ``PIP_EXISTS_ACTION=i`` (ignore existing packages) - great for CI environments, where you need really fast setup. `#3738 `_ - Pipenv will no longer forcibly override ``PIP_NO_DEPS`` on all vcs and file dependencies as resolution happens on these in a pre-lock step. `#3763 `_ - Improved verbose logging output during ``pipenv lock`` will now stream output to the console while maintaining a spinner. `#3810 `_ - Added support for automatic python installs via ``asdf`` and associated ``PIPENV_DONT_USE_ASDF`` environment variable. `#4018 `_ @@ -434,7 +434,7 @@ Behavior Changes Bug Fixes --------- -- Raise `PipenvUsageError` when [[source]] does not contain url field. `#2373 `_ +- Raise ``PipenvUsageError`` when [[source]] does not contain url field. `#2373 `_ - Fixed a bug which caused editable package resolution to sometimes fail with an unhelpful setuptools-related error message. `#2722 `_ - Fixed an issue which caused errors due to reliance on the system utilities ``which`` and ``where`` which may not always exist on some systems. - Fixed a bug which caused periodic failures in python discovery when executables named ``python`` were not present on the target ``$PATH``. `#2783 `_ @@ -697,13 +697,13 @@ Bug Fixes - Fixed an issue in ``delegator.py`` related to subprocess calls when using ``PopenSpawn`` to stream output, which sometimes threw unexpected ``EOF`` errors. `#3102 `_, `#3114 `_, `#3117 `_ -- Fix the path casing issue that makes `pipenv clean` fail on Windows `#3104 `_ +- Fix the path casing issue that makes ``pipenv clean`` fail on Windows `#3104 `_ - Pipenv will avoid leaving build artifacts in the current working directory. `#3106 `_ - Fixed issues with broken subprocess calls leaking resource handles and causing random and sporadic failures. `#3109 `_ - Fixed an issue which caused ``pipenv clean`` to sometimes clean packages from the base ``site-packages`` folder or fail entirely. `#3113 `_ - Updated ``pythonfinder`` to correct an issue with unnesting of nested paths when searching for python versions. `#3121 `_ - Added additional logic for ignoring and replacing non-ascii characters when formatting console output on non-UTF-8 systems. `#3131 `_ -- Fix virtual environment discovery when ``PIPENV_VENV_IN_PROJECT`` is set, but the in-project `.venv` is a file. `#3134 `_ +- Fix virtual environment discovery when ``PIPENV_VENV_IN_PROJECT`` is set, but the in-project ``.venv`` is a file. `#3134 `_ - Hashes for remote and local non-PyPI artifacts will now be included in ``Pipfile.lock`` during resolution. `#3145 `_ - Fix project path hashing logic in purpose to prevent collisions of virtual environments. `#3151 `_ - Fix package installation when the virtual environment path contains parentheses. `#3158 `_ @@ -787,7 +787,7 @@ Vendored Libraries Features & Improvements ----------------------- -- Added environment variables `PIPENV_VERBOSE` and `PIPENV_QUIET` to control +- Added environment variables ``PIPENV_VERBOSE`` and ``PIPENV_QUIET`` to control output verbosity without needing to pass options. `#2527 `_ - Updated test-PyPI add-on to better support json-API access (forward compatibility). @@ -820,7 +820,7 @@ Behavior Changes - Add ``COMSPEC`` to fallback option (along with ``SHELL`` and ``PYENV_SHELL``) if shell detection fails, improving robustness on Windows. `#2651 `_ -- Fallback to shell mode if `run` fails with Windows error 193 to handle non-executable commands. This should improve usability on Windows, where some users run non-executable files without specifying a command, relying on Windows file association to choose the current command. `#2718 `_ +- Fallback to shell mode if ``run`` fails with Windows error 193 to handle non-executable commands. This should improve usability on Windows, where some users run non-executable files without specifying a command, relying on Windows file association to choose the current command. `#2718 `_ Bug Fixes @@ -918,7 +918,7 @@ Bug Fixes `#2867 `_, `#2880 `_ -- Fixed a bug where `pipenv` crashes when the `WORKON_HOME` directory does not exist. `#2877 `_ +- Fixed a bug where ``pipenv`` crashes when the ``WORKON_HOME`` directory does not exist. `#2877 `_ - Fixed pip is not loaded from pipenv's patched one but the system one `#2912 `_ @@ -985,7 +985,7 @@ Improved Documentation - Added simple example to README.md for installing from git. `#2685 `_ -- Stopped recommending `--system` for Docker contexts. `#2762 `_ +- Stopped recommending ``--system`` for Docker contexts. `#2762 `_ - Fixed the example url for doing "pipenv install -e some-repository-url#egg=something", it was missing the "egg=" in the fragment @@ -993,7 +993,7 @@ Improved Documentation - Fixed link to the "be cordial" essay in the contribution documentation. `#2793 `_ -- Clarify `pipenv install` documentation `#2844 `_ +- Clarify ``pipenv install`` documentation `#2844 `_ - Replace reference to uservoice with PEEP-000 `#2909 `_ diff --git a/Pipfile b/Pipfile index a4888164..4e46706b 100644 --- a/Pipfile +++ b/Pipfile @@ -5,7 +5,10 @@ click = "*" pytest_pypi = {path = "./tests/pytest-pypi", editable = true} stdeb = {version="*", markers="sys_platform == 'linux'"} dataclasses = {version="*", markers="python_version < '3.7'"} +importlib-resources = {version = "*", markers = "python_version < '3.7'"} sphinxcontrib-spelling = "<4.3.0" +pre-commit = "*" +atomicwrites = {version = "*", markers="sys_platform == 'win32'"} [packages] diff --git a/Pipfile.lock b/Pipfile.lock index 9bd75187..b9545405 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b6632ccfba082244f188747d88665264be87621552d2c1bbebaf36174bc24e8a" + "sha256": "0d25587c8b692005c51421eb35b08c878ca1d6e14e175d3c9ed74fd4d637476d" }, "pipfile-spec": 6, "requires": {}, @@ -39,11 +39,11 @@ }, "attrs": { "hashes": [ - "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", - "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.2.0" + "version": "==21.4.0" }, "babel": { "hashes": [ @@ -53,14 +53,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.9.1" }, - "backports.entry-points-selectable": { - "hashes": [ - "sha256:7fceed9532a7aa2bd888654a7314f864a3c16a4e710b34a58cfc0f08114c663b", - "sha256:914b21a479fde881635f7af5adc7f6e38d6b274be32269070c53b698c60d5386" - ], - "markers": "python_version >= '2.7'", - "version": "==1.1.1" - }, "beautifulsoup4": { "hashes": [ "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf", @@ -71,11 +63,11 @@ }, "black": { "hashes": [ - "sha256:0b1f66cbfadcd332ceeaeecf6373d9991d451868d2e2219ad0ac1213fb701117", - "sha256:83f3852301c8dcb229e9c444dd79f573c8d31c7c2dad9bbaaa94c808630e32aa" + "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3", + "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f" ], "markers": "python_full_version >= '3.6.2'", - "version": "==21.11b0" + "version": "==21.12b0" }, "bleach": { "hashes": [ @@ -98,13 +90,76 @@ ], "version": "==2021.10.8" }, + "cffi": { + "hashes": [ + "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", + "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", + "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", + "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", + "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", + "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", + "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", + "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", + "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", + "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", + "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", + "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", + "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", + "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", + "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", + "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", + "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", + "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", + "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", + "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", + "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", + "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", + "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", + "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", + "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", + "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", + "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", + "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", + "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", + "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", + "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", + "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", + "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", + "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", + "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", + "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", + "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", + "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", + "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", + "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", + "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", + "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", + "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", + "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", + "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", + "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", + "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", + "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", + "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", + "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" + ], + "version": "==1.15.0" + }, + "cfgv": { + "hashes": [ + "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426", + "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736" + ], + "markers": "python_full_version >= '3.6.1'", + "version": "==3.3.1" + }, "charset-normalizer": { "hashes": [ - "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0", - "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" ], "markers": "python_version >= '3.0'", - "version": "==2.0.7" + "version": "==2.0.12" }, "click": { "hashes": [ @@ -125,9 +180,35 @@ "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" ], - "markers": "sys_platform == 'win32'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==0.4.4" }, + "cryptography": { + "hashes": [ + "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b", + "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51", + "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7", + "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d", + "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6", + "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29", + "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9", + "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf", + "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815", + "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf", + "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85", + "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77", + "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86", + "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb", + "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e", + "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0", + "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3", + "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84", + "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2", + "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6" + ], + "markers": "python_version >= '3.6'", + "version": "==36.0.2" + }, "dataclasses": { "hashes": [ "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf", @@ -139,10 +220,10 @@ }, "distlib": { "hashes": [ - "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31", - "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05" + "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b", + "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579" ], - "version": "==0.3.3" + "version": "==0.3.4" }, "docutils": { "hashes": [ @@ -162,11 +243,11 @@ }, "filelock": { "hashes": [ - "sha256:2e139a228bcf56dd8b2274a65174d005c4a6b68540ee0bdbb92c76f43f29f7e8", - "sha256:93d512b32a23baf4cac44ffd72ccf70732aeff7b8050fcaf6d3ec406d954baf4" + "sha256:0f12f552b42b5bf60dba233710bf71337d35494fc8bdd4fd6d9f6d082ad45e06", + "sha256:a4bc51381e01502a30e9f06dd4fa19a1712eab852b6fb0f84fd7cce0793d8ca3" ], "markers": "python_version >= '3.6'", - "version": "==3.4.0" + "version": "==3.4.1" }, "flake8": { "hashes": [ @@ -186,11 +267,19 @@ }, "flask": { "hashes": [ - "sha256:7b2fb8e934ddd50731893bdcdb00fc8c0315916f9fcd50d22c7cc1a95ab634e2", - "sha256:cb90f62f1d8e4dc4621f52106613488b5ba826b2e1e10a33eac92f723093ab6a" + "sha256:59da8a3170004800a2837844bfa84d49b022550616070f7cb1a659682b2e7c9f", + "sha256:e1120c228ca2f553b470df4a5fa927ab66258467526069981b3eb0a91902687d" ], "markers": "python_version >= '3.6'", - "version": "==2.0.2" + "version": "==2.0.3" + }, + "identify": { + "hashes": [ + "sha256:6b4b5031f69c48bf93a646b90de9b381c6b5f560df4cbe0ed3cf7650ae741e4d", + "sha256:aa68609c7454dbcaae60a01ff6b8df1de9b39fe6e50b1f6107ec81dcda624aa6" + ], + "markers": "python_full_version >= '3.6.1'", + "version": "==2.4.4" }, "idna": { "hashes": [ @@ -210,19 +299,20 @@ }, "importlib-metadata": { "hashes": [ - "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100", - "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb" + "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e", + "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668" ], "markers": "python_version < '3.8'", - "version": "==4.8.2" + "version": "==4.8.3" }, "importlib-resources": { "hashes": [ - "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45", - "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b" + "sha256:203d70dda34cfbfbb42324a8d4211196e7d3e858de21a5eb68c6d1cdd99e4e98", + "sha256:ae35ed1cfe8c0d6c1a53ecd168167f01fa93b893d51a62cdf23aea044c67211b" ], + "index": "pypi", "markers": "python_version < '3.7'", - "version": "==5.4.0" + "version": "==5.2.3" }, "incremental": { "hashes": [ @@ -240,11 +330,10 @@ }, "invoke": { "hashes": [ - "sha256:374d1e2ecf78981da94bfaf95366216aaec27c2d6a7b7d5818d92da55aa258d3", - "sha256:769e90caeb1bd07d484821732f931f1ad8916a38e3f3e618644687fc09cb6317", - "sha256:e6c9917a1e3e73e7ea91fdf82d5f151ccfe85bf30cc65cdb892444c02dbb5f74" + "sha256:a5159fc63dba6ca2a87a1e33d282b99cea69711b03c64a35bb4e1c53c6c4afa0", + "sha256:e332e49de40463f2016315f51df42313855772be86435686156bc18f45b5cc6c" ], - "version": "==1.6.0" + "version": "==1.7.0" }, "itsdangerous": { "hashes": [ @@ -254,6 +343,14 @@ "markers": "python_version >= '3.6'", "version": "==2.0.1" }, + "jeepney": { + "hashes": [ + "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac", + "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f" + ], + "markers": "sys_platform == 'linux'", + "version": "==0.7.1" + }, "jinja2": { "hashes": [ "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8", @@ -264,11 +361,11 @@ }, "keyring": { "hashes": [ - "sha256:6334aee6073db2fb1f30892697b1730105b5e9a77ce7e61fca6b435225493efe", - "sha256:bd2145a237ed70c8ce72978b497619ddfcae640b6dcf494402d5143e37755c6e" + "sha256:17e49fb0d6883c2b4445359434dba95aad84aabb29bbff044ad0ed7100232eca", + "sha256:89cbd74d4683ed164c8082fb38619341097741323b3786905c6dac04d6915a55" ], "markers": "python_version >= '3.6'", - "version": "==23.2.1" + "version": "==23.4.1" }, "markupsafe": { "hashes": [ @@ -367,13 +464,20 @@ ], "version": "==0.4.3" }, + "nodeenv": { + "hashes": [ + "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b", + "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7" + ], + "version": "==1.6.0" + }, "packaging": { "hashes": [ - "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966", - "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0" + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" ], "markers": "python_version >= '3.6'", - "version": "==21.2" + "version": "==21.3" }, "parver": { "hashes": [ @@ -408,10 +512,10 @@ }, "pkginfo": { "hashes": [ - "sha256:37ecd857b47e5f55949c41ed061eb51a0bee97a87c969219d144c0e023982779", - "sha256:e7432f81d08adec7297633191bbf0bd47faf13cd8724c3a13250e51d542635bd" + "sha256:542e0d0b6750e2e21c20179803e40ab50598d8066d51097a0e382cba9eb02bff", + "sha256:c24c487c6a7f72c66e816ab1796b96ac6c3d14d49338293d2141664330b55ffc" ], - "version": "==1.7.1" + "version": "==1.8.2" }, "platformdirs": { "hashes": [ @@ -429,6 +533,14 @@ "markers": "python_version >= '3.6'", "version": "==1.0.0" }, + "pre-commit": { + "hashes": [ + "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616", + "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a" + ], + "index": "pypi", + "version": "==2.17.0" + }, "py": { "hashes": [ "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", @@ -445,6 +557,13 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.7.0" }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "version": "==2.21" + }, "pyenchant": { "hashes": [ "sha256:1cf830c6614362a78aab78d50eaf7c6c93831369c52e1bb64ffae1df0341e637", @@ -465,35 +584,35 @@ }, "pygments": { "hashes": [ - "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380", - "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6" + "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65", + "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a" ], "markers": "python_version >= '3.5'", - "version": "==2.10.0" + "version": "==2.11.2" }, "pyparsing": { "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", + "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.4.7" + "markers": "python_version >= '3.6'", + "version": "==3.0.7" }, "pytest": { "hashes": [ - "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", - "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" + "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db", + "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171" ], "markers": "python_version >= '3.6'", - "version": "==6.2.5" + "version": "==7.0.1" }, "pytest-forked": { "hashes": [ - "sha256:6aa9ac7e00ad1a539c41bec6d21011332de671e938c7637378ec9710204e37ca", - "sha256:dc4147784048e70ef5d437951728825a131b81714b398d5d52f17c7c144d8815" + "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e", + "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.3.0" + "markers": "python_version >= '3.6'", + "version": "==1.4.0" }, "pytest-pypi": { "editable": true, @@ -501,19 +620,19 @@ }, "pytest-timeout": { "hashes": [ - "sha256:329bdea323d3e5bea4737070dd85a0d1021dbecb2da5342dc25284fdb929dff0", - "sha256:a5ec4eceddb8ea726911848593d668594107e797621e97f93a1d1dbc6fbb9080" + "sha256:c07ca07404c612f8abbe22294b23c368e2e5104b521c1790195561f37e1ac3d9", + "sha256:f6f50101443ce70ad325ceb4473c4255e9d74e3c7cd0ef827309dfa4c0d975c6" ], "markers": "python_version >= '3.6'", - "version": "==2.0.1" + "version": "==2.1.0" }, "pytest-xdist": { "hashes": [ - "sha256:7b61ebb46997a0820a263553179d6d1e25a8c50d8a8620cd1aa1e20e3be99168", - "sha256:89b330316f7fc475f999c81b577c2b926c9569f3d397ae432c0c2e2496d61ff9" + "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf", + "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65" ], "markers": "python_version >= '3.6'", - "version": "==2.4.0" + "version": "==2.5.0" }, "pytz": { "hashes": [ @@ -522,82 +641,60 @@ ], "version": "==2021.3" }, - "pywin32-ctypes": { + "pyyaml": { "hashes": [ - "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", - "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98" + "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", + "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", + "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", + "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", + "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", + "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", + "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", + "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", + "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", + "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", + "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", + "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", + "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", + "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", + "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", + "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", + "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", + "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", + "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", + "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", + "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", + "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", + "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", + "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", + "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", + "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", + "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" ], - "markers": "sys_platform == 'win32'", - "version": "==0.2.0" + "markers": "python_version >= '3.6'", + "version": "==6.0" }, "readme-renderer": { "hashes": [ - "sha256:3286806450d9961d6e3b5f8a59f77e61503799aca5155c8d8d40359b4e1e1adc", - "sha256:8299700d7a910c304072a7601eafada6712a5b011a20139417e1b1e9f04645d8" + "sha256:262510fe6aae81ed4e94d8b169077f325614c0b1a45916a80442c6576264a9c2", + "sha256:dfb4d17f21706d145f7473e0b61ca245ba58e810cf9b2209a48239677f82e5b0" ], - "version": "==30.0" - }, - "regex": { - "hashes": [ - "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f", - "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc", - "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4", - "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4", - "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8", - "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f", - "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a", - "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef", - "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f", - "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc", - "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50", - "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d", - "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d", - "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733", - "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36", - "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345", - "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0", - "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12", - "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646", - "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667", - "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244", - "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29", - "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec", - "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf", - "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4", - "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449", - "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a", - "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d", - "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb", - "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e", - "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83", - "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e", - "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a", - "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94", - "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc", - "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e", - "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965", - "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0", - "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36", - "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec", - "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23", - "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7", - "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe", - "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6", - "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b", - "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb", - "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b", - "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30", - "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e" - ], - "version": "==2021.11.10" + "markers": "python_version >= '3.6'", + "version": "==34.0" }, "requests": { "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==2.26.0" + "version": "==2.27.1" }, "requests-toolbelt": { "hashes": [ @@ -611,15 +708,24 @@ "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835", "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97" ], + "markers": "python_version >= '3.7'", "version": "==1.5.0" }, + "secretstorage": { + "hashes": [ + "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f", + "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195" + ], + "markers": "sys_platform == 'linux'", + "version": "==3.3.1" + }, "setuptools": { "hashes": [ - "sha256:94ee891f4759150cded601a6beb6b08400413aefd0267b692f3f8c6e0bb238e7", - "sha256:fb537610c2dfe77b5896e3ee53dd53fbdd9adc48076c8f28cee3a30fb59a5038" + "sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373", + "sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e" ], "markers": "python_version >= '3.6'", - "version": "==59.1.1" + "version": "==59.6.0" }, "six": { "hashes": [ @@ -720,6 +826,7 @@ "hashes": [ "sha256:08c22c9c03b28a140fe3ec5064b53a5288279f22e596ca06b0be698d50c93cf2" ], + "index": "pypi", "markers": "sys_platform == 'linux'", "version": "==0.10.0" }, @@ -733,83 +840,88 @@ }, "tomli": { "hashes": [ - "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee", - "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade" + "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f", + "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c" ], "markers": "python_version >= '3.6'", - "version": "==1.2.2" + "version": "==1.2.3" }, "towncrier": { "hashes": [ - "sha256:930454ab86da25aae01bf0f37927e565d9366b12b948d5f533c7c7641dba7b16", - "sha256:bfc86ad9dd28c53dfefbb6d7d33875a8f459c5491e18857be4bcff096b3192c3" + "sha256:9cb6f45c16e1a1eec9d0e7651165e7be60cd0ab81d13a5c96ca97a498ae87f48", + "sha256:fc5a88a2a54988e3a8ed2b60d553599da8330f65722cc607c839614ed87e0f92" ], - "version": "==21.9.0rc1" + "version": "==21.9.0" }, "tqdm": { "hashes": [ - "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c", - "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d" + "sha256:1d9835ede8e394bb8c9dcbffbca02d717217113adc679236873eeaac5bc0b3cd", + "sha256:e643e071046f17139dea55b880dc9b33822ce21613b4a4f5ea57f202833dbc29" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==4.62.3" + "version": "==4.63.0" }, "twine": { "hashes": [ - "sha256:4caad5ef4722e127b3749052fcbffaaf71719b19d4fd4973b29c469957adeba2", - "sha256:916070f8ecbd1985ebed5dbb02b9bda9a092882a96d7069d542d4fc0bb5c673c" + "sha256:8efa52658e0ae770686a13b675569328f1fba9837e5de1867bfe5f46a9aefe19", + "sha256:d0550fca9dc19f3d5e8eadfce0c227294df0a2a951251a4385797c8a6198b7c8" ], "markers": "python_version >= '3.6'", - "version": "==3.6.0" + "version": "==3.8.0" }, "typed-ast": { "hashes": [ - "sha256:14fed8820114a389a2b7e91624db5f85f3f6682fda09fe0268a59aabd28fe5f5", - "sha256:155b74b078be842d2eb630dd30a280025eca0a5383c7d45853c27afee65f278f", - "sha256:224afecb8b39739f5c9562794a7c98325cb9d972712e1a98b6989a4720219541", - "sha256:361b9e5d27bd8e3ccb6ea6ad6c4f3c0be322a1a0f8177db6d56264fa0ae40410", - "sha256:37ba2ab65a0028b1a4f2b61a8fe77f12d242731977d274a03d68ebb751271508", - "sha256:49af5b8f6f03ed1eb89ee06c1d7c2e7c8e743d720c3746a5857609a1abc94c94", - "sha256:51040bf45aacefa44fa67fb9ebcd1f2bec73182b99a532c2394eea7dabd18e24", - "sha256:52ca2b2b524d770bed7a393371a38e91943f9160a190141e0df911586066ecda", - "sha256:618912cbc7e17b4aeba86ffe071698c6e2d292acbd6d1d5ec1ee724b8c4ae450", - "sha256:65c81abbabda7d760df7304d843cc9dbe7ef5d485504ca59a46ae2d1731d2428", - "sha256:7b310a207ee9fde3f46ba327989e6cba4195bc0c8c70a158456e7b10233e6bed", - "sha256:7e6731044f748340ef68dcadb5172a4b1f40847a2983fe3983b2a66445fbc8e6", - "sha256:806e0c7346b9b4af8c62d9a29053f484599921a4448c37fbbcbbf15c25138570", - "sha256:a67fd5914603e2165e075f1b12f5a8356bfb9557e8bfb74511108cfbab0f51ed", - "sha256:e4374a76e61399a173137e7984a1d7e356038cf844f24fd8aea46c8029a2f712", - "sha256:e8a9b9c87801cecaad3b4c2b8876387115d1a14caa602c1618cedbb0cb2a14e6", - "sha256:ea517c2bb11c5e4ba7a83a91482a2837041181d57d3ed0749a6c382a2b6b7086", - "sha256:ec184dfb5d3d11e82841dbb973e7092b75f306b625fad7b2e665b64c5d60ab3f", - "sha256:ff4ad88271aa7a55f19b6a161ed44e088c393846d954729549e3cde8257747bb" + "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e", + "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344", + "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266", + "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a", + "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd", + "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d", + "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837", + "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098", + "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e", + "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27", + "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b", + "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596", + "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76", + "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30", + "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4", + "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78", + "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca", + "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985", + "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb", + "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88", + "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7", + "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5", + "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e", + "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7" ], "markers": "python_version < '3.8' and implementation_name == 'cpython'", - "version": "==1.5.0" + "version": "==1.5.2" }, "typing-extensions": { "hashes": [ - "sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed", - "sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9" + "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", + "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" ], "markers": "python_version >= '3.6'", - "version": "==4.0.0" + "version": "==4.1.1" }, "urllib3": { "hashes": [ - "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", - "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" + "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", + "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.7" + "version": "==1.26.9" }, "virtualenv": { "hashes": [ - "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814", - "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218" + "sha256:c3e01300fb8495bc00ed70741f5271fc95fed067eb7106297be73d30879af60c", + "sha256:ce8901d3bbf3b90393498187f2d56797a8a452fb2d0d7efc6fd837554d6f679c" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==20.10.0" + "version": "==20.13.4" }, "virtualenv-clone": { "hashes": [ @@ -828,18 +940,18 @@ }, "werkzeug": { "hashes": [ - "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f", - "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a" + "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8", + "sha256:b863f8ff057c522164b6067c9e28b041161b4be5ba4d0daceeaa50a163822d3c" ], "markers": "python_version >= '3.6'", - "version": "==2.0.2" + "version": "==2.0.3" }, "zipp": { "hashes": [ "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832", "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc" ], - "markers": "python_version >= '3.6'", + "markers": "python_version < '3.10'", "version": "==3.6.0" } } diff --git a/docs/advanced.rst b/docs/advanced.rst index 3d863c6f..fe52751e 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -274,7 +274,7 @@ Example:: .. note:: - Each month, `PyUp.io` updates the ``safety`` database of + Each month, `PyUp.io`_ updates the ``safety`` database of insecure Python packages and `makes it available to the community for free `__. Pipenv makes an API call to retrieve those results and use them @@ -380,7 +380,7 @@ If a ``.env`` file is present in your project, ``$ pipenv shell`` and ``$ pipenv >>> os.environ['HELLO'] 'WORLD' -Shell like variable expansion is available in ``.env`` files using `${VARNAME}` syntax.:: +Shell like variable expansion is available in ``.env`` files using ``${VARNAME}`` syntax.:: $ cat .env CONFIG_PATH=${HOME}/.config/foo @@ -591,15 +591,15 @@ Magic shell completions are now enabled! It's reasonably common for platform specific Python bindings for operating system interfaces to only be available through the system package manager, and hence unavailable for installation into virtual -environments with `pip`. In these cases, the virtual environment can -be created with access to the system `site-packages` directory:: +environments with ``pip``. In these cases, the virtual environment can +be created with access to the system ``site-packages`` directory:: $ pipenv --three --site-packages -To ensure that all `pip`-installable components actually are installed +To ensure that all ``pip``-installable components actually are installed into the virtual environment and system packages are only used for interfaces that don't participate in Python-level dependency resolution -at all, use the `PIP_IGNORE_INSTALLED` setting:: +at all, use the ``PIP_IGNORE_INSTALLED`` setting:: $ PIP_IGNORE_INSTALLED=1 pipenv install --dev @@ -618,7 +618,7 @@ Libraries are ultimately meant to be used in some **application**. Applications To summarize: - For libraries, define **abstract dependencies** via ``install_requires`` in ``setup.py``. The decision of which version exactly to be installed and where to obtain that dependency is not yours to make! -- For applications, define **dependencies and where to get them** in the `Pipfile` and use this file to update the set of **concrete dependencies** in ``Pipfile.lock``. This file defines a specific idempotent environment that is known to work for your project. The ``Pipfile.lock`` is your source of truth. The ``Pipfile`` is a convenience for you to create that lock-file, in that it allows you to still remain somewhat vague about the exact version of a dependency to be used. Pipenv is there to help you define a working conflict-free set of specific dependency-versions, which would otherwise be a very tedious task. +- For applications, define **dependencies and where to get them** in the ``Pipfile`` and use this file to update the set of **concrete dependencies** in ``Pipfile.lock``. This file defines a specific idempotent environment that is known to work for your project. The ``Pipfile.lock`` is your source of truth. The ``Pipfile`` is a convenience for you to create that lock-file, in that it allows you to still remain somewhat vague about the exact version of a dependency to be used. Pipenv is there to help you define a working conflict-free set of specific dependency-versions, which would otherwise be a very tedious task. - Of course, ``Pipfile`` and Pipenv are still useful for library developers, as they can be used to define a development or test environment. - And, of course, there are projects for which the distinction between library and application isn't that clear. In that case, use ``install_requires`` alongside Pipenv and ``Pipfile``. diff --git a/docs/basics.rst b/docs/basics.rst index 8ab24875..eeccef00 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -132,7 +132,7 @@ Example Pipfile.lock - Generally, keep both ``Pipfile`` and ``Pipfile.lock`` in version control. - Do not keep ``Pipfile.lock`` in version control if multiple versions of Python are being targeted. -- Specify your target Python version in your `Pipfile`'s ``[requires]`` section. Ideally, you should only have one target Python version, as this is a deployment tool. ``python_version`` should be in the format ``X.Y`` (or ``X``) and ``python_full_version`` should be in ``X.Y.Z`` format. +- Specify your target Python version in your ``Pipfile``'s ``[requires]`` section. Ideally, you should only have one target Python version, as this is a deployment tool. ``python_version`` should be in the format ``X.Y`` (or ``X``) and ``python_full_version`` should be in ``X.Y.Z`` format. - ``pipenv install`` is fully compatible with ``pip install`` syntax, for which the full documentation can be found `here `_. - Note that the ``Pipfile`` uses the `TOML Spec `_. diff --git a/docs/conf.py b/docs/conf.py index e5aa5953..a21ef32e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,38 +34,38 @@ with open(os.path.join(here, "..", "pipenv", "__version__.py")) as f: # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', - 'sphinx_click.ext', + "sphinx.ext.autodoc", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", + "sphinx_click.ext", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'pipenv' +project = "pipenv" copyright = '2020. A project founded by Kenneth Reitz' -author = 'Python Packaging Authority' +author = "Python Packaging Authority" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = about['__version__'] +version = about["__version__"] # The full version, including alpha/beta/rc tags. -release = about['__version__'] +release = about["__version__"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -77,10 +77,10 @@ language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True @@ -90,42 +90,47 @@ todo_include_todos = True # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { - 'show_powered_by': False, - 'github_user': 'pypa', - 'github_repo': 'pipenv', - 'github_banner': False, - 'show_related': False + "show_powered_by": False, + "github_user": "pypa", + "github_repo": "pipenv", + "github_banner": False, + "show_related": False, } html_sidebars = { - 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html', - 'hacks.html'], - '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', - 'sourcelink.html', 'searchbox.html', 'hacks.html'] + "index": ["sidebarintro.html", "sourcelink.html", "searchbox.html", "hacks.html"], + "**": [ + "sidebarlogo.html", + "localtoc.html", + "relations.html", + "sourcelink.html", + "searchbox.html", + "hacks.html", + ], } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] def setup(app): - app.add_stylesheet('custom.css') + app.add_stylesheet("custom.css") # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'pipenvdoc' +htmlhelp_basename = "pipenvdoc" # -- Options for LaTeX output --------------------------------------------- @@ -134,15 +139,12 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -152,8 +154,7 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'pipenv.tex', 'pipenv Documentation', - 'Kenneth Reitz', 'manual'), + (master_doc, "pipenv.tex", "pipenv Documentation", "Kenneth Reitz", "manual"), ] @@ -161,10 +162,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pipenv', 'pipenv Documentation', - [author], 1) -] +man_pages = [(master_doc, "pipenv", "pipenv Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -173,9 +171,15 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'pipenv', 'pipenv Documentation', - author, 'pipenv', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "pipenv", + "pipenv Documentation", + author, + "pipenv", + "One line description of project.", + "Miscellaneous", + ), ] @@ -197,4 +201,4 @@ epub_copyright = copyright # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] diff --git a/docs/dev/contributing.rst b/docs/dev/contributing.rst index d43e3ec5..b8f5d426 100644 --- a/docs/dev/contributing.rst +++ b/docs/dev/contributing.rst @@ -8,7 +8,7 @@ contributing to the Pipenv project is *very* generous of you. This document lays out guidelines and advice for contributing to this project. If you're thinking of contributing, please start by reading this document and -getting a feel for how contributing to this project works. +getting a feel for how contributing to this project works. The guide is split into sections based on the type of contribution you're thinking of making, with a section that covers general guidelines for all @@ -106,6 +106,18 @@ See `pypa/pipenv#2557`_ for more details. .. _pypa/pipenv#2557: https://github.com/pypa/pipenv/issues/2557 +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: + + $ ``pipenv install --dev`` + # This will configure running the pre-commit checks at start of each commit + $ ``pre-commit install`` + # Should you want to check the pre-commit configuration against all configured project files + $ ``pre-commit run --all-files --verbose`` + .. _testing: @@ -241,7 +253,7 @@ you run it before you open a PR. Taking this second approach, will allow you, for example, to run a single test case, or ``fail fast`` if you need it. -2. Manually, which repeat the steps of the scripts above: +2. Manually, which repeat the steps of the scripts above: .. code-block:: console @@ -272,10 +284,11 @@ It is important that your environment is setup correctly, and this may take some work, for example, on a specific Mac installation, the following steps may be needed:: +.. code-block:: bash # Make sure the tests can access github if [ "$SSH_AGENT_PID" = "" ] then - eval `ssh-agent` + eval ``ssh-agent`` ssh-add fi diff --git a/docs/index.rst b/docs/index.rst index 54e3260b..b6a22d30 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,7 @@ .. pipenv documentation master file, created by sphinx-quickstart on Mon Jan 30 13:28:36 2017. You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. + contain the root ``toctree`` directive. Pipenv: Python Dev Workflow for Humans ====================================== diff --git a/get-pipenv.py b/get-pipenv.py index 88d82b79..0d74fa58 100755 --- a/get-pipenv.py +++ b/get-pipenv.py @@ -23,44 +23,44 @@ # Don't manually edit this script! Check ths instructions in the link above to # create get-pip.py and patch the following: -#+++ ./get-pip/public/get-pip.py 2022-01-12 16:52:11.920161471 +0100 -#--- ./pipenv/get-pipenv.py 2022-01-12 20:11:35.816906142 +0100 -#@@ -55,7 +28,7 @@ +# +++ ./get-pip/public/get-pip.py 2022-01-12 16:52:11.920161471 +0100 +# --- ./pipenv/get-pipenv.py 2022-01-12 20:11:35.816906142 +0100 +# @@ -55,7 +28,7 @@ # message_parts = [ # "This script does not work on Python {}.{}".format(*this_python), # "The minimum supported Python version is {}.{}.".format(*min_version), -#- "Please use an alternative installation https://pipenv.pypa.io/en/latest/install/", -#+ "Please use https://bootstrap.pypa.io/pip/{}.{}/get-pip.py instead.".format(*this_python), +# - "Please use an alternative installation https://pipenv.pypa.io/en/latest/install/", +# + "Please use https://bootstrap.pypa.io/pip/{}.{}/get-pip.py instead.".format(*this_python), # ] # print("ERROR: " + " ".join(message_parts)) # sys.exit(1) -#@@ -70,7 +43,7 @@ -# +# @@ -70,7 +43,7 @@ +# # def determine_pip_install_arguments(): # implicit_pip = True -#+ implicit_setuptools = False -#- implicit_setuptools = True +# + implicit_setuptools = False +# - implicit_setuptools = True # implicit_wheel = True -# +# # # Check if the user has requested us not to install setuptools -#@@ -87,8 +60,6 @@ -# +# @@ -87,8 +60,6 @@ +# # # We only want to implicitly install setuptools and wheel if they don't # # already exist on the target platform. -#+ # No need for doing this, since pipenv already has setuptools as -#+ # a dependency in setup.py +# + # No need for doing this, since pipenv already has setuptools as +# + # a dependency in setup.py # if implicit_setuptools: # try: # import setuptools # noqa -#@@ -109,8 +80,6 @@ +# @@ -109,8 +80,6 @@ # args += ["setuptools"] # if implicit_wheel: # args += ["wheel"] -#+ -#+ args += ["pipenv"] -# +# + +# + args += ["pipenv"] +# # return ["install", "--upgrade", "--force-reinstall"] + args - + # YMMV, so dig a bit to find how to add pipenv to the args passed to pip. import sys @@ -103,17 +103,19 @@ def determine_pip_install_arguments(): # We only want to implicitly install setuptools and wheel if they don't # already exist on the target platform. - # No need for doing this, since pipenv already has setuptools as + # No need for doing this, since pipenv already has setuptools as # a dependency in setup.py if implicit_setuptools: try: import setuptools # noqa + implicit_setuptools = False except ImportError: pass if implicit_wheel: try: import wheel # noqa + implicit_wheel = False except ImportError: pass @@ -125,7 +127,7 @@ def determine_pip_install_arguments(): args += ["setuptools"] if implicit_wheel: args += ["wheel"] - + args += ["pipenv"] return ["install", "--upgrade", "--force-reinstall"] + args @@ -164,6 +166,7 @@ def bootstrap(tmpdir): # Execute the included pip and use it to install the latest pip and # setuptools from PyPI from pip._internal.cli.main import main as pip_entry_point + args = determine_pip_install_arguments() sys.exit(pip_entry_point(args)) diff --git a/news/4992.process.rst b/news/4992.process.rst new file mode 100644 index 00000000..d9d82af5 --- /dev/null +++ b/news/4992.process.rst @@ -0,0 +1 @@ +Internal to pipenv, the utils.py was split into a utils module with unused code removed. diff --git a/news/4993.process.rst b/news/4993.process.rst new file mode 100644 index 00000000..4cb59bef --- /dev/null +++ b/news/4993.process.rst @@ -0,0 +1,2 @@ +Added code linting using pre-commit-hooks, black, flake8, isort, pygrep-hooks, news-fragments and check-manifest. +Very similar to pip's configuration; adds a towncrier new's type ``process`` for change to Development processes. diff --git a/pipenv/__init__.py b/pipenv/__init__.py index 8cc14037..54ec1100 100644 --- a/pipenv/__init__.py +++ b/pipenv/__init__.py @@ -7,8 +7,7 @@ import os import sys import warnings -from pipenv.__version__ import __version__ # noqa - +from pipenv.__version__ import __version__ # noqa PIPENV_ROOT = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) PIPENV_VENDOR = os.sep.join([PIPENV_ROOT, "vendor"]) @@ -54,8 +53,8 @@ if os.name == "nt": sys.stdout = stdout sys.stderr = stderr -from .cli import cli from . import resolver # noqa +from .cli import cli if __name__ == "__main__": cli() diff --git a/pipenv/__main__.py b/pipenv/__main__.py index ad23c8e8..c59bb2fd 100644 --- a/pipenv/__main__.py +++ b/pipenv/__main__.py @@ -1,5 +1,4 @@ from pipenv.cli import cli - if __name__ == "__main__": cli() diff --git a/pipenv/_compat.py b/pipenv/_compat.py index 4c8b643f..610d8869 100644 --- a/pipenv/_compat.py +++ b/pipenv/_compat.py @@ -12,13 +12,19 @@ warnings.filterwarnings("ignore", category=ResourceWarning) __all__ = [ - "getpreferredencoding", "DEFAULT_ENCODING", "canonical_encoding_name", - "force_encoding", "UNICODE_TO_ASCII_TRANSLATION_MAP", "decode_output", "fix_utf8" + "getpreferredencoding", + "DEFAULT_ENCODING", + "canonical_encoding_name", + "force_encoding", + "UNICODE_TO_ASCII_TRANSLATION_MAP", + "decode_output", + "fix_utf8", ] def getpreferredencoding(): import locale + # Borrowed from Invoke # (see https://github.com/pyinvoke/invoke/blob/93af29d/invoke/runners.py#L881) return locale.getpreferredencoding(False) @@ -29,6 +35,7 @@ DEFAULT_ENCODING = getpreferredencoding() def canonical_encoding_name(name): import codecs + try: codec = codecs.lookup(name) except LookupError: @@ -55,7 +62,7 @@ def force_encoding(): if stdout_encoding != "utf-8" or stderr_encoding != "utf-8": try: - from ctypes import pythonapi, py_object, c_char_p + from ctypes import c_char_p, py_object, pythonapi except ImportError: return DEFAULT_ENCODING, DEFAULT_ENCODING try: diff --git a/pipenv/cli/__init__.py b/pipenv/cli/__init__.py index 9d53abf8..8876fdf8 100644 --- a/pipenv/cli/__init__.py +++ b/pipenv/cli/__init__.py @@ -1 +1 @@ -from .command import cli # noqa +from .command import cli # noqa diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index 0753aa73..99dd30a1 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -5,25 +5,41 @@ from pipenv import environments from pipenv.__version__ import __version__ from pipenv._compat import fix_utf8 from pipenv.cli.options import ( - CONTEXT_SETTINGS, PipenvGroup, common_options, deploy_option, - general_options, install_options, lock_options, pass_state, - pypi_mirror_option, python_option, site_packages_option, skip_lock_option, - sync_options, system_option, three_option, uninstall_options, verbose_option + CONTEXT_SETTINGS, + PipenvGroup, + common_options, + deploy_option, + general_options, + install_options, + lock_options, + pass_state, + pypi_mirror_option, + python_option, + site_packages_option, + skip_lock_option, + sync_options, + system_option, + three_option, + uninstall_options, + verbose_option, ) from pipenv.exceptions import PipenvOptionsError from pipenv.patched import crayons from pipenv.utils.processes import subprocess_run from pipenv.vendor.click import ( - Choice, argument, echo, edit, group, option, pass_context, secho, types, - version_option + Choice, + argument, + echo, + edit, + group, + option, + pass_context, + secho, + version_option, ) - subcommand_context = CONTEXT_SETTINGS.copy() -subcommand_context.update({ - "ignore_unknown_options": True, - "allow_extra_args": True -}) +subcommand_context.update({"ignore_unknown_options": True, "allow_extra_args": True}) subcommand_context_no_interspersion = subcommand_context.copy() subcommand_context_no_interspersion["allow_interspersed_args"] = False @@ -31,8 +47,12 @@ subcommand_context_no_interspersion["allow_interspersed_args"] = False @group(cls=PipenvGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS) @option("--where", is_flag=True, default=False, help="Output project home information.") @option("--venv", is_flag=True, default=False, help="Output virtualenv information.") -@option("--py", is_flag=True, default=False, help="Output Python interpreter information.") -@option("--envs", is_flag=True, default=False, help="Output Environment Variable options.") +@option( + "--py", is_flag=True, default=False, help="Output Python interpreter information." +) +@option( + "--envs", is_flag=True, default=False, help="Output Environment Variable options." +) @option("--rm", is_flag=True, default=False, help="Remove the virtualenv.") @option("--bare", is_flag=True, default=False, help="Minimal output.") @option("--man", is_flag=True, default=False, help="Display manpage.") @@ -58,21 +78,33 @@ def cli( support=None, help=False, site_packages=None, - **kwargs + **kwargs, ): - from ..core import ( - cleanup_virtualenv, do_clear, do_py, do_where, ensure_project, - format_help, system_which, warn_in_virtualenv - ) from pipenv.utils.spinner import create_spinner + from ..core import ( + cleanup_virtualenv, + do_clear, + do_py, + do_where, + ensure_project, + format_help, + system_which, + warn_in_virtualenv, + ) + if man: if system_which("man"): path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pipenv.1") os.execle(system_which("man"), "man", path, os.environ) return 0 else: - secho("man does not appear to be available on your system.", fg="yellow", bold=True, err=True) + secho( + "man does not appear to be available on your system.", + fg="yellow", + bold=True, + err=True, + ) return 1 if envs: echo("The following environment variables can be set, to do various things:\n") @@ -114,7 +146,7 @@ def cli( "{}({}){}".format( crayons.red("No virtualenv has been created for this project"), crayons.normal(state.project.project_directory, bold=True), - crayons.red(" yet!") + crayons.red(" yet!"), ), err=True, ) @@ -183,10 +215,7 @@ def cli( @skip_lock_option @install_options @pass_state -def install( - state, - **kwargs -): +def install(state, **kwargs): """Installs provided packages and adds them to Pipfile, or (if no packages are given), installs all packages from Pipfile.""" from ..core import do_install @@ -210,13 +239,13 @@ def install( extra_index_url=state.extra_index_urls, packages=state.installstate.packages, editable_packages=state.installstate.editables, - site_packages=state.site_packages + site_packages=state.site_packages, ) @cli.command( short_help="Uninstalls a provided package and removes it from Pipfile.", - context_settings=subcommand_context + context_settings=subcommand_context, ) @option( "--all-dev", @@ -233,15 +262,10 @@ def install( @uninstall_options @pass_state @pass_context -def uninstall( - ctx, - state, - all_dev=False, - all=False, - **kwargs -): +def uninstall(ctx, state, all_dev=False, all=False, **kwargs): """Uninstalls a provided package and removes it from Pipfile.""" from ..core import do_uninstall + retcode = do_uninstall( state.project, packages=state.installstate.packages, @@ -254,7 +278,7 @@ def uninstall( all=all, keep_outdated=state.installstate.keep_outdated, pypi_mirror=state.pypi_mirror, - ctx=ctx + ctx=ctx, ) if retcode: sys.exit(retcode) @@ -280,11 +304,7 @@ LOCK_DEV_NOTE = """\ @lock_options @pass_state @pass_context -def lock( - ctx, - state, - **kwargs -): +def lock(ctx, state, **kwargs): """Generates Pipfile.lock.""" from ..core import do_init, do_lock, ensure_project @@ -292,8 +312,12 @@ def lock( # Note that we don't pass clear on to ensure_project as it is also # handled in do_lock ensure_project( - state.project, three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, - warn=(not state.quiet), site_packages=state.site_packages, + state.project, + three=state.three, + python=state.python, + pypi_mirror=state.pypi_mirror, + warn=(not state.quiet), + site_packages=state.site_packages, ) emit_requirements = state.lockoptions.emit_requirements dev = state.installstate.dev @@ -325,7 +349,7 @@ def lock( raise PipenvOptionsError( "--dev-only", "--dev-only is only permitted in combination with --requirements. " - "Aborting." + "Aborting.", ) do_lock( state.project, @@ -347,7 +371,7 @@ def lock( is_flag=True, default=False, help="Run in shell in fancy mode. Make sure the shell have no path manipulating" - " scripts. Run $pipenv shell for issues with compatibility mode.", + " scripts. Run $pipenv shell for issues with compatibility mode.", ) @option( "--anyway", @@ -409,6 +433,7 @@ def shell( def run(state, command, args): """Spawns a command installed into the virtualenv.""" from ..core import do_run + do_run( state.project, command=command, @@ -416,21 +441,21 @@ def run(state, command, args): three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, - quiet=state.quiet + quiet=state.quiet, ) @cli.command( short_help="Checks for PyUp Safety security vulnerabilities and against" - " PEP 508 markers provided in Pipfile.", - context_settings=subcommand_context + " PEP 508 markers provided in Pipfile.", + context_settings=subcommand_context, ) @option( "--db", nargs=1, - default=lambda: os.environ.get('PIPENV_SAFETY_DB'), + default=lambda: os.environ.get("PIPENV_SAFETY_DB"), help="Path to a local PyUp Safety vulnerabilities database." - " Default: ENV PIPENV_SAFETY_DB or None.", + " Default: ENV PIPENV_SAFETY_DB or None.", ) @option( "--ignore", @@ -447,8 +472,11 @@ def run(state, command, args): @option( "--key", help="Safety API key from PyUp.io for scanning dependencies against a live" - " vulnerabilities database. Leave blank for scanning against a" - " database that only updates once a month.", + " vulnerabilities database. Leave blank for scanning against a" + " database that only updates once a month.", +) +@option( + "--quiet", is_flag=True, help="Quiet standard output, except vulnerability report." ) @common_options @system_option @@ -461,7 +489,7 @@ def check( output="default", key=None, quiet=False, - **kwargs + **kwargs, ): """Checks for PyUp Safety security vulnerabilities and against PEP 508 markers provided in Pipfile.""" from ..core import do_check @@ -482,31 +510,33 @@ def check( @cli.command(short_help="Runs lock, then sync.", context_settings=CONTEXT_SETTINGS) @option("--bare", is_flag=True, default=False, help="Minimal output.") -@option( - "--outdated", is_flag=True, default=False, help="List out-of-date dependencies." -) +@option("--outdated", is_flag=True, default=False, help="List out-of-date dependencies.") @option("--dry-run", is_flag=True, default=None, help="List out-of-date dependencies.") @install_options @pass_state @pass_context -def update( - ctx, - state, - bare=False, - dry_run=None, - outdated=False, - **kwargs -): +def update(ctx, state, bare=False, dry_run=None, outdated=False, **kwargs): """Runs lock, then sync.""" from ..core import do_lock, do_outdated, do_sync, ensure_project + ensure_project( - state.project, three=state.three, python=state.python, pypi_mirror=state.pypi_mirror, - warn=(not state.quiet), site_packages=state.site_packages, clear=state.clear + state.project, + three=state.three, + python=state.python, + pypi_mirror=state.pypi_mirror, + warn=(not state.quiet), + site_packages=state.site_packages, + clear=state.clear, ) if not outdated: outdated = bool(dry_run) if outdated: - do_outdated(state.project, clear=state.clear, pre=state.installstate.pre, pypi_mirror=state.pypi_mirror) + do_outdated( + state.project, + clear=state.clear, + pre=state.installstate.pre, + pypi_mirror=state.pypi_mirror, + ) packages = [p for p in state.installstate.packages if p] editable = [p for p in state.installstate.editables if p] if not packages: @@ -557,7 +587,7 @@ def update( @cli.command( short_help="Displays currently-installed dependency graph information.", - context_settings=CONTEXT_SETTINGS + context_settings=CONTEXT_SETTINGS, ) @option("--bare", is_flag=True, default=False, help="Minimal output.") @option("--json", is_flag=True, default=False, help="Output JSON.") @@ -572,8 +602,9 @@ def graph(state, bare=False, json=False, json_tree=False, reverse=False): @cli.command( - short_help="View a given module in your editor.", name="open", - context_settings=CONTEXT_SETTINGS + short_help="View a given module in your editor.", + name="open", + context_settings=CONTEXT_SETTINGS, ) @common_options @argument("module", nargs=1) @@ -590,11 +621,18 @@ def run_open(state, module, *args, **kwargs): # Ensure that virtualenv is available. ensure_project( - state.project, three=state.three, python=state.python, - validate=False, pypi_mirror=state.pypi_mirror, + state.project, + three=state.three, + python=state.python, + validate=False, + pypi_mirror=state.pypi_mirror, ) c = subprocess_run( - [state.project._which("python"), "-c", "import {0}; print({0}.__file__)".format(module)] + [ + state.project._which("python"), + "-c", + "import {0}; print({0}.__file__)".format(module), + ] ) if c.returncode: echo(crayons.red("Module not found!")) @@ -611,21 +649,14 @@ def run_open(state, module, *args, **kwargs): @cli.command( short_help="Installs all packages specified in Pipfile.lock.", - context_settings=CONTEXT_SETTINGS + context_settings=CONTEXT_SETTINGS, ) @system_option @option("--bare", is_flag=True, default=False, help="Minimal output.") @sync_options @pass_state @pass_context -def sync( - ctx, - state, - bare=False, - user=False, - unused=False, - **kwargs -): +def sync(ctx, state, bare=False, user=False, unused=False, **kwargs): """Installs all packages specified in Pipfile.lock.""" from ..core import do_sync @@ -641,7 +672,7 @@ def sync( unused=unused, sequential=state.installstate.sequential, pypi_mirror=state.pypi_mirror, - system=state.system + system=state.system, ) if retcode: ctx.abort() @@ -649,7 +680,7 @@ def sync( @cli.command( short_help="Uninstalls all packages not specified in Pipfile.lock.", - context_settings=CONTEXT_SETTINGS + context_settings=CONTEXT_SETTINGS, ) @option("--bare", is_flag=True, default=False, help="Minimal output.") @option("--dry-run", is_flag=True, default=False, help="Just output unneeded packages.") @@ -660,8 +691,14 @@ def sync( def clean(state, dry_run=False, bare=False, user=False): """Uninstalls all packages not specified in Pipfile.lock.""" from ..core import do_clean - do_clean(state.project, three=state.three, python=state.python, dry_run=dry_run, - system=state.system) + + do_clean( + state.project, + three=state.three, + python=state.python, + dry_run=dry_run, + system=state.system, + ) @cli.command( @@ -675,7 +712,7 @@ def scripts(state): if not state.project.pipfile_exists: echo("No Pipfile present at project home.", err=True) sys.exit(1) - scripts = state.project.parsed_pipfile.get('scripts', {}) + scripts = state.project.parsed_pipfile.get("scripts", {}) first_column_width = max(len(word) for word in ["Command"] + list(scripts)) second_column_width = max(len(word) for word in ["Script"] + list(scripts.values())) lines = ["{0:<{width}} Script".format("Command", width=first_column_width)] @@ -699,13 +736,13 @@ def verify(state): sys.exit(1) if state.project.get_lockfile_hash() != state.project.calculate_pipfile_hash(): echo( - 'Pipfile.lock is out-of-date. Run {} to update.'.format( + "Pipfile.lock is out-of-date. Run {} to update.".format( crayons.yellow("$ pipenv lock", bold=True) ), - err=True + err=True, ) sys.exit(1) - echo(crayons.green('Pipfile.lock is up-to-date.')) + echo(crayons.green("Pipfile.lock is up-to-date.")) sys.exit(0) diff --git a/pipenv/cli/options.py b/pipenv/cli/options.py index 09ace754..2e1e9277 100644 --- a/pipenv/cli/options.py +++ b/pipenv/cli/options.py @@ -3,17 +3,19 @@ import os from pipenv.project import Project from pipenv.utils.internet import is_valid_url from pipenv.vendor.click import ( - BadArgumentUsage, BadParameter, Group, Option, argument, echo, - make_pass_decorator, option + BadArgumentUsage, + BadParameter, + Group, + Option, + argument, + echo, + make_pass_decorator, + option, ) from pipenv.vendor.click import types as click_types from pipenv.vendor.click_didyoumean import DYMMixin - -CONTEXT_SETTINGS = { - "help_option_names": ["-h", "--help"], - "auto_envvar_prefix": "PIPENV" -} +CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"], "auto_envvar_prefix": "PIPENV"} class PipenvGroup(DYMMixin, Group): @@ -53,6 +55,7 @@ class PipenvGroup(DYMMixin, Group): """ return super().main(*args, **kwargs, windows_expand_args=False) + class State: def __init__(self): self.index = None @@ -102,9 +105,16 @@ def index_option(f): state = ctx.ensure_object(State) state.index = value return value - return option('-i', '--index', expose_value=False, envvar="PIP_INDEX_URL", - help='Target PyPI-compatible package index url.', nargs=1, - callback=callback)(f) + + return option( + "-i", + "--index", + expose_value=False, + envvar="PIP_INDEX_URL", + help="Target PyPI-compatible package index url.", + nargs=1, + callback=callback, + )(f) def extra_index_option(f): @@ -112,9 +122,15 @@ def extra_index_option(f): state = ctx.ensure_object(State) state.extra_index_urls.extend(list(value)) return value - return option("--extra-index-url", multiple=True, expose_value=False, - help="URLs to the extra PyPI compatible indexes to query for package look-ups.", - callback=callback, envvar="PIP_EXTRA_INDEX_URL")(f) + + return option( + "--extra-index-url", + multiple=True, + expose_value=False, + help="URLs to the extra PyPI compatible indexes to query for package look-ups.", + callback=callback, + envvar="PIP_EXTRA_INDEX_URL", + )(f) def editable_option(f): @@ -122,11 +138,16 @@ def editable_option(f): state = ctx.ensure_object(State) state.installstate.editables.extend(value) return value - return option('-e', '--editable', expose_value=False, multiple=True, - callback=callback, type=click_types.STRING, help=( - "An editable Python package URL or path, often to a VCS " - "repository." - ))(f) + + return option( + "-e", + "--editable", + expose_value=False, + multiple=True, + callback=callback, + type=click_types.STRING, + help="An editable Python package URL or path, often to a VCS repository.", + )(f) def sequential_option(f): @@ -134,9 +155,17 @@ def sequential_option(f): state = ctx.ensure_object(State) state.installstate.sequential = value return value - return option("--sequential", is_flag=True, default=False, expose_value=False, - help="Install dependencies one-at-a-time, instead of concurrently.", - callback=callback, type=click_types.BOOL, show_envvar=True)(f) + + return option( + "--sequential", + is_flag=True, + default=False, + expose_value=False, + help="Install dependencies one-at-a-time, instead of concurrently.", + callback=callback, + type=click_types.BOOL, + show_envvar=True, + )(f) def skip_lock_option(f): @@ -144,10 +173,18 @@ def skip_lock_option(f): state = ctx.ensure_object(State) state.installstate.skip_lock = value return value - return option("--skip-lock", is_flag=True, default=False, expose_value=False, - help="Skip locking mechanisms and use the Pipfile instead during operation.", - envvar="PIPENV_SKIP_LOCK", callback=callback, type=click_types.BOOL, - show_envvar=True)(f) + + return option( + "--skip-lock", + is_flag=True, + default=False, + expose_value=False, + help="Skip locking mechanisms and use the Pipfile instead during operation.", + envvar="PIPENV_SKIP_LOCK", + callback=callback, + type=click_types.BOOL, + show_envvar=True, + )(f) def keep_outdated_option(f): @@ -155,9 +192,17 @@ def keep_outdated_option(f): state = ctx.ensure_object(State) state.installstate.keep_outdated = value return value - return option("--keep-outdated", is_flag=True, default=False, expose_value=False, - help="Keep out-dated dependencies from being updated in Pipfile.lock.", - callback=callback, type=click_types.BOOL, show_envvar=True)(f) + + return option( + "--keep-outdated", + is_flag=True, + default=False, + expose_value=False, + help="Keep out-dated dependencies from being updated in Pipfile.lock.", + callback=callback, + type=click_types.BOOL, + show_envvar=True, + )(f) def selective_upgrade_option(f): @@ -165,9 +210,16 @@ def selective_upgrade_option(f): state = ctx.ensure_object(State) state.installstate.selective_upgrade = value return value - return option("--selective-upgrade", is_flag=True, default=False, type=click_types.BOOL, - help="Update specified packages.", callback=callback, - expose_value=False)(f) + + return option( + "--selective-upgrade", + is_flag=True, + default=False, + type=click_types.BOOL, + help="Update specified packages.", + callback=callback, + expose_value=False, + )(f) def ignore_pipfile_option(f): @@ -175,9 +227,17 @@ def ignore_pipfile_option(f): state = ctx.ensure_object(State) state.installstate.ignore_pipfile = value return value - return option("--ignore-pipfile", is_flag=True, default=False, expose_value=False, - help="Ignore Pipfile when installing, using the Pipfile.lock.", - callback=callback, type=click_types.BOOL, show_envvar=True)(f) + + return option( + "--ignore-pipfile", + is_flag=True, + default=False, + expose_value=False, + help="Ignore Pipfile when installing, using the Pipfile.lock.", + callback=callback, + type=click_types.BOOL, + show_envvar=True, + )(f) def _dev_option(f, help_text): @@ -185,9 +245,18 @@ def _dev_option(f, help_text): state = ctx.ensure_object(State) state.installstate.dev = value return value - return option("--dev", "-d", is_flag=True, default=False, type=click_types.BOOL, - help=help_text, callback=callback, - expose_value=False, show_envvar=True)(f) + + return option( + "--dev", + "-d", + is_flag=True, + default=False, + type=click_types.BOOL, + help=help_text, + callback=callback, + expose_value=False, + show_envvar=True, + )(f) def install_dev_option(f): @@ -199,7 +268,9 @@ def lock_dev_option(f): def uninstall_dev_option(f): - return _dev_option(f, "Deprecated (as it has no effect). May be removed in a future release.") + return _dev_option( + f, "Deprecated (as it has no effect). May be removed in a future release." + ) def pre_option(f): @@ -207,8 +278,16 @@ def pre_option(f): state = ctx.ensure_object(State) state.installstate.pre = value return value - return option("--pre", is_flag=True, default=False, help="Allow pre-releases.", - callback=callback, type=click_types.BOOL, expose_value=False)(f) + + return option( + "--pre", + is_flag=True, + default=False, + help="Allow pre-releases.", + callback=callback, + type=click_types.BOOL, + expose_value=False, + )(f) def package_arg(f): @@ -216,8 +295,14 @@ def package_arg(f): state = ctx.ensure_object(State) state.installstate.packages.extend(value) return value - return argument('packages', nargs=-1, callback=callback, expose_value=False, - type=click_types.STRING)(f) + + return argument( + "packages", + nargs=-1, + callback=callback, + expose_value=False, + type=click_types.STRING, + )(f) def three_option(f): @@ -226,9 +311,15 @@ def three_option(f): if value is not None: state.three = value return value - return option("--three", is_flag=True, default=None, - help="Use Python 3/2 when creating virtualenv.", callback=callback, - expose_value=False)(f) + + return option( + "--three", + is_flag=True, + default=None, + help="Use Python 3 when creating virtualenv.", + callback=callback, + expose_value=False, + )(f) def python_option(f): @@ -237,10 +328,17 @@ def python_option(f): if value is not None: state.python = validate_python_path(ctx, param, value) return value - return option("--python", default="", nargs=1, callback=callback, - help="Specify which version of Python virtualenv should use.", - expose_value=False, allow_from_autoenv=False, - type=click_types.STRING)(f) + + return option( + "--python", + default="", + nargs=1, + callback=callback, + help="Specify which version of Python virtualenv should use.", + expose_value=False, + allow_from_autoenv=False, + type=click_types.STRING, + )(f) def pypi_mirror_option(f): @@ -250,8 +348,14 @@ def pypi_mirror_option(f): if value is not None: state.pypi_mirror = validate_pypi_mirror(ctx, param, value) return value - return option("--pypi-mirror", nargs=1, callback=callback, - help="Specify a PyPI mirror.", expose_value=False)(f) + + return option( + "--pypi-mirror", + nargs=1, + callback=callback, + help="Specify a PyPI mirror.", + expose_value=False, + )(f) def verbose_option(f): @@ -261,12 +365,20 @@ def verbose_option(f): if state.quiet: raise BadArgumentUsage( "--verbose and --quiet are mutually exclusive! Please choose one!", - ctx=ctx + ctx=ctx, ) state.verbose = True setup_verbosity(ctx, param, 1) - return option("--verbose", "-v", is_flag=True, expose_value=False, - callback=callback, help="Verbose mode.", type=click_types.BOOL)(f) + + return option( + "--verbose", + "-v", + is_flag=True, + expose_value=False, + callback=callback, + help="Verbose mode.", + type=click_types.BOOL, + )(f) def quiet_option(f): @@ -276,12 +388,20 @@ def quiet_option(f): if state.verbose: raise BadArgumentUsage( "--verbose and --quiet are mutually exclusive! Please choose one!", - ctx=ctx + ctx=ctx, ) state.quiet = True setup_verbosity(ctx, param, -1) - return option("--quiet", "-q", is_flag=True, expose_value=False, - callback=callback, help="Quiet mode.", type=click_types.BOOL)(f) + + return option( + "--quiet", + "-q", + is_flag=True, + expose_value=False, + callback=callback, + help="Quiet mode.", + type=click_types.BOOL, + )(f) def site_packages_option(f): @@ -290,9 +410,16 @@ def site_packages_option(f): validate_bool_or_none(ctx, param, value) state.site_packages = value return value - return option("--site-packages/--no-site-packages", is_flag=True, default=None, - help="Enable site-packages for the virtualenv.", callback=callback, - expose_value=False, show_envvar=True)(f) + + return option( + "--site-packages/--no-site-packages", + is_flag=True, + default=None, + help="Enable site-packages for the virtualenv.", + callback=callback, + expose_value=False, + show_envvar=True, + )(f) def clear_option(f): @@ -300,9 +427,16 @@ def clear_option(f): state = ctx.ensure_object(State) state.clear = value return value - return option("--clear", is_flag=True, callback=callback, type=click_types.BOOL, - help="Clears caches (pipenv, pip).", - expose_value=False, show_envvar=True)(f) + + return option( + "--clear", + is_flag=True, + callback=callback, + type=click_types.BOOL, + help="Clears caches (pipenv, pip).", + expose_value=False, + show_envvar=True, + )(f) def system_option(f): @@ -311,9 +445,17 @@ def system_option(f): if value is not None: state.system = value return value - return option("--system", is_flag=True, default=False, help="System pip management.", - callback=callback, type=click_types.BOOL, expose_value=False, - show_envvar=True)(f) + + return option( + "--system", + is_flag=True, + default=False, + help="System pip management.", + callback=callback, + type=click_types.BOOL, + expose_value=False, + show_envvar=True, + )(f) def requirementstxt_option(f): @@ -322,9 +464,17 @@ def requirementstxt_option(f): if value: state.installstate.requirementstxt = value return value - return option("--requirements", "-r", nargs=1, default="", expose_value=False, - help="Import a requirements.txt file.", callback=callback, - type=click_types.STRING)(f) + + return option( + "--requirements", + "-r", + nargs=1, + default="", + expose_value=False, + help="Import a requirements.txt file.", + callback=callback, + type=click_types.STRING, + )(f) def emit_requirements_flag(f): @@ -333,8 +483,16 @@ def emit_requirements_flag(f): if value: state.lockoptions.emit_requirements = value return value - return option("--requirements", "-r", default=False, is_flag=True, expose_value=False, - help="Generate output in requirements.txt format.", callback=callback)(f) + + return option( + "--requirements", + "-r", + default=False, + is_flag=True, + expose_value=False, + help="Generate output in requirements.txt format.", + callback=callback, + )(f) def emit_requirements_header_flag(f): @@ -343,8 +501,15 @@ def emit_requirements_header_flag(f): if value: state.lockoptions.emit_requirements_header = value return value - return option("--header/--no-header", default=True, is_flag=True, expose_value=False, - help="Add header to generated requirements", callback=callback)(f) + + return option( + "--header/--no-header", + default=True, + is_flag=True, + expose_value=False, + help="Add header to generated requirements", + callback=callback, + )(f) def dev_only_flag(f): @@ -353,8 +518,15 @@ def dev_only_flag(f): if value: state.lockoptions.dev_only = value return value - return option("--dev-only", default=False, is_flag=True, expose_value=False, - help="Emit development dependencies *only* (overrides --dev)", callback=callback)(f) + + return option( + "--dev-only", + default=False, + is_flag=True, + expose_value=False, + help="Emit development dependencies *only* (overrides --dev)", + callback=callback, + )(f) def deploy_option(f): @@ -362,15 +534,23 @@ def deploy_option(f): state = ctx.ensure_object(State) state.installstate.deploy = value return value - return option("--deploy", is_flag=True, default=False, type=click_types.BOOL, - help="Abort if the Pipfile.lock is out-of-date, or Python version is" - " wrong.", callback=callback, expose_value=False)(f) + + return option( + "--deploy", + is_flag=True, + default=False, + type=click_types.BOOL, + help="Abort if the Pipfile.lock is out-of-date, or Python version is wrong.", + callback=callback, + expose_value=False, + )(f) def setup_verbosity(ctx, param, value): if not value: return import logging + loggers = ("pip",) if value == 1: for logger in loggers: diff --git a/pipenv/cmdparse.py b/pipenv/cmdparse.py index 760460c2..673cf80c 100644 --- a/pipenv/cmdparse.py +++ b/pipenv/cmdparse.py @@ -94,7 +94,9 @@ class Script(object): See also: https://docs.python.org/3/library/subprocess.html#converting-argument-sequence """ - return " ".join(itertools.chain( - [_quote_if_contains(self.command, r'[\s^()]')], - (_quote_if_contains(arg, r'[\s^]') for arg in self.args), - )) + return " ".join( + itertools.chain( + [_quote_if_contains(self.command, r"[\s^()]")], + (_quote_if_contains(arg, r"[\s^]") for arg in self.args), + ) + ) diff --git a/pipenv/core.py b/pipenv/core.py index 11c8b526..53d24747 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -1,11 +1,11 @@ import json as simplejson import logging import os -from pathlib import Path -from posixpath import expandvars import sys import time import warnings +from pathlib import Path +from posixpath import expandvars import click import dotenv @@ -15,7 +15,6 @@ import vistir from pipenv import environments, exceptions, pep508checker, progress from pipenv._compat import decode_for_output, fix_utf8 from pipenv.patched import crayons - from pipenv.utils.dependencies import ( convert_deps_to_pip, get_canonical_names, @@ -23,20 +22,26 @@ from pipenv.utils.dependencies import ( is_required_version, is_star, pep423_name, - python_version + python_version, ) -from pipenv.utils.internet import download_file, get_host_and_port, is_valid_url, proper_case from pipenv.utils.indexes import get_source_list, parse_indexes, prepare_pip_source_args -from pipenv.utils.resolver import venv_resolve_deps -from pipenv.utils.shell import cmd_list_to_shell, find_python, is_python_command, subprocess_run -from pipenv.utils.spinner import create_spinner +from pipenv.utils.internet import download_file, get_host_and_port, is_valid_url from pipenv.utils.processes import run_command +from pipenv.utils.resolver import venv_resolve_deps +from pipenv.utils.shell import ( + cmd_list_to_shell, + find_python, + is_python_command, + subprocess_run, +) +from pipenv.utils.spinner import create_spinner if environments.is_type_checking(): from typing import Dict, List, Optional, Union from pipenv.project import Project from pipenv.vendor.requirementslib.models.requirements import Requirement + TSourceDict = Dict[str, Union[str, bool]] @@ -86,12 +91,14 @@ def do_clear(project): from pip import locations try: - vistir.path.rmtree(project.s.PIPENV_CACHE_DIR, onerror=vistir.path.handle_remove_readonly) + vistir.path.rmtree( + project.s.PIPENV_CACHE_DIR, onerror=vistir.path.handle_remove_readonly + ) # Other processes may be writing into this directory simultaneously. vistir.path.rmtree( locations.USER_CACHE_DIR, ignore_errors=environments.PIPENV_IS_CI, - onerror=vistir.path.handle_remove_readonly + onerror=vistir.path.handle_remove_readonly, ) except OSError as e: # Ignore FileNotFoundError. This is needed for Python 2.7. @@ -126,7 +133,9 @@ def load_dot_env(project, as_dict=False, quiet=False): elif os.path.isfile(dotenv_file): if not quiet: click.echo( - crayons.normal(fix_utf8("Loading .env environment variables..."), bold=True), + crayons.normal( + fix_utf8("Loading .env environment variables..."), bold=True + ), err=True, ) dotenv.load_dotenv(dotenv_file, override=True) @@ -152,8 +161,10 @@ def cleanup_virtualenv(project, bare=True): def import_requirements(project, r=None, dev=False): + from pipenv.patched.notpip._internal.req.constructors import ( + install_req_from_parsed_requirement, + ) from pipenv.patched.notpip._vendor import requests as pip_requests - from pipenv.patched.notpip._internal.req.constructors import install_req_from_parsed_requirement from pipenv.vendor.pip_shims.shims import parse_requirements # Parse requirements.txt file with Pip's parser. @@ -179,14 +190,15 @@ def import_requirements(project, r=None, dev=False): trusted_hosts.append(get_host_and_port(trusted_host)) indexes = sorted(set(indexes)) trusted_hosts = sorted(set(trusted_hosts)) - reqs = [install_req_from_parsed_requirement(f) for f in parse_requirements(r, session=pip_requests)] + reqs = [ + install_req_from_parsed_requirement(f) + for f in parse_requirements(r, session=pip_requests) + ] for package in reqs: if package.name not in BAD_PACKAGES: if package.link is not None: package_string = ( - f"-e {package.link}" - if package.editable - else str(package.link) + f"-e {package.link}" if package.editable else str(package.link) ) project.add_package_to_pipfile(package_string, dev=dev) else: @@ -194,10 +206,17 @@ def import_requirements(project, r=None, dev=False): for index in indexes: # don't require HTTPS for trusted hosts (see: https://pip.pypa.io/en/stable/cli/pip/#cmdoption-trusted-host) host_and_port = get_host_and_port(index) - require_valid_https = not any((v in trusted_hosts for v in ( - host_and_port, - host_and_port.partition(':')[0], # also check if hostname without port is in trusted_hosts - ))) + require_valid_https = not any( + ( + v in trusted_hosts + for v in ( + host_and_port, + host_and_port.partition(":")[ + 0 + ], # also check if hostname without port is in trusted_hosts + ) + ) + ) project.add_index_to_pipfile(index, verify_ssl=require_valid_https) project.recase_pipfile() @@ -222,14 +241,18 @@ def ensure_pipfile(project, validate=True, skip_requirements=False, system=False """Creates a Pipfile for the project, if it doesn't exist.""" # Assert Pipfile exists. - python = project._which("python") if not (project.s.USING_DEFAULT_PYTHON or system) else None + python = ( + project._which("python") + if not (project.s.USING_DEFAULT_PYTHON or system) + else None + ) if project.pipfile_is_empty: # Show an error message and exit if system is passed and no pipfile exists if system and not project.s.PIPENV_VIRTUALENV: raise exceptions.PipenvOptionsError( "--system", "--system is intended to be used for pre-existing Pipfile " - "installation, not installation of specific packages. Aborting." + "installation, not installation of specific packages. Aborting.", ) # If there's a requirements file, but no Pipfile... if project.requirements_exists and not skip_requirements: @@ -264,7 +287,9 @@ def ensure_pipfile(project, validate=True, skip_requirements=False, system=False ) else: click.echo( - crayons.normal(fix_utf8("Creating a Pipfile for this project..."), bold=True), + crayons.normal( + fix_utf8("Creating a Pipfile for this project..."), bold=True + ), err=True, ) # Create the pipfile if it doesn't exist. @@ -296,6 +321,7 @@ def find_a_system_python(line): """ from .vendor.pythonfinder import Finder + finder = Finder(system=False, global_search=True) if not line: return next(iter(finder.find_all_python_versions()), None) @@ -311,15 +337,13 @@ def ensure_python(project, three=None, python=None): if project.s.PIPENV_PYTHON and python is False and three is None: python = project.s.PIPENV_PYTHON - def abort(msg=''): + def abort(msg=""): click.echo( "{}\nYou can specify specific versions of Python with:\n{}".format( crayons.red(msg), crayons.yellow( - "$ pipenv --python {}".format( - os.sep.join(("path", "to", "python")) - ) - ) + "$ pipenv --python {}".format(os.sep.join(("path", "to", "python"))) + ), ), err=True, ) @@ -374,7 +398,7 @@ def ensure_python(project, three=None, python=None): except ValueError: abort() except InstallerError as e: - abort(f'Something went wrong while installing Python:\n{e.err}') + abort(f"Something went wrong while installing Python:\n{e.err}") s = "{} {} {}".format( "Would you like us to install", crayons.green(f"CPython {version}"), @@ -398,8 +422,8 @@ def ensure_python(project, three=None, python=None): try: c = installer.install(version) except InstallerError as e: - sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format( - "Failed...") + sp.fail( + environments.PIPENV_SPINNER_FAIL_TEXT.format("Failed...") ) click.echo(fix_utf8("Something went wrong..."), err=True) click.echo(crayons.cyan(e.err), err=True) @@ -409,6 +433,7 @@ def ensure_python(project, three=None, python=None): click.echo(crayons.cyan(c.stdout), err=True) # Clear the pythonfinder caches from .vendor.pythonfinder import Finder + finder = Finder(system=False, global_search=True) finder.find_python_version.cache_clear() finder.find_all_python_versions.cache_clear() @@ -430,7 +455,9 @@ def ensure_python(project, three=None, python=None): return path_to_python -def ensure_virtualenv(project, three=None, python=None, site_packages=None, pypi_mirror=None): +def ensure_virtualenv( + project, three=None, python=None, site_packages=None, pypi_mirror=None +): """Creates a virtualenv, if one doesn't exist.""" def abort(): @@ -455,7 +482,10 @@ def ensure_virtualenv(project, three=None, python=None, site_packages=None, pypi ) sys.exit(1) do_create_virtualenv( - project, python=python, site_packages=site_packages, pypi_mirror=pypi_mirror + project, + python=python, + site_packages=site_packages, + pypi_mirror=pypi_mirror, ) except KeyboardInterrupt: # If interrupted, cleanup the virtualenv. @@ -475,7 +505,8 @@ def ensure_virtualenv(project, three=None, python=None, site_packages=None, pypi # about, so confirm first. if "VIRTUAL_ENV" in os.environ: if not ( - project.s.PIPENV_YES or click.confirm("Use existing virtualenv?", default=True) + project.s.PIPENV_YES + or click.confirm("Use existing virtualenv?", default=True) ): abort() click.echo( @@ -512,7 +543,7 @@ def ensure_project( if project.s.PIPENV_USE_SYSTEM or project.virtualenv_exists: system_or_exists = True else: - system_or_exists = system # default to False + system_or_exists = system # default to False if not project.pipfile_exists and deploy: raise exceptions.PipfileNotFound # Skip virtualenv creation when --system was used. @@ -557,7 +588,10 @@ def ensure_project( raise exceptions.DeployException # Ensure the Pipfile exists. ensure_pipfile( - project, validate=validate, skip_requirements=skip_requirements, system=system_or_exists + project, + validate=validate, + skip_requirements=skip_requirements, + system=system_or_exists, ) @@ -600,9 +634,7 @@ def do_where(project, virtualenv=False, bare=True): else: location = project.virtualenv_location if not bare: - click.echo( - f"Virtualenv location: {crayons.green(location)}", err=True - ) + click.echo(f"Virtualenv location: {crayons.green(location)}", err=True) else: click.echo(location) @@ -626,13 +658,15 @@ 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("{}".format( - crayons.yellow( - "Failed initial installation: Failed to overwrite existing " - "package, likely due to path aliasing. Expanding and trying " - "again!" + click.echo( + "{}".format( + crayons.yellow( + "Failed initial installation: Failed to overwrite existing " + "package, likely due to path aliasing. Expanding and trying " + "again!" + ) ) - )) + ) dep = c.dep.copy() dep.use_pep517 = True elif "Disabling PEP 517 processing is invalid" in err: @@ -654,22 +688,34 @@ def _cleanup_procs(project, procs, failed_deps_queue, retry=True): "{} {}! Will try again.".format( crayons.red("An error occurred while installing"), crayons.green(dep.as_line()), - ), err=True + ), + err=True, ) # Save the Failed Dependency for later. failed_deps_queue.put(dep) -def batch_install(project, deps_list, procs, failed_deps_queue, - requirements_dir, no_deps=True, ignore_hashes=False, - allow_global=False, blocking=False, pypi_mirror=None, - retry=True, sequential_deps=None): +def batch_install( + project, + deps_list, + procs, + failed_deps_queue, + requirements_dir, + no_deps=True, + ignore_hashes=False, + allow_global=False, + blocking=False, + pypi_mirror=None, + retry=True, + sequential_deps=None, +): from .vendor.requirementslib.models.utils import ( - strip_extras_markers_from_requirement + strip_extras_markers_from_requirement, ) + if sequential_deps is None: sequential_deps = [] - failed = (not retry) + failed = not retry install_deps = not no_deps if not failed: label = INSTALL_LABEL if not environments.PIPENV_HIDE_EMOJIS else "" @@ -683,10 +729,7 @@ def batch_install(project, deps_list, procs, failed_deps_queue, ] sequential_dep_names = [d.name for d in sequential_deps] - deps_list_bar = progress.bar( - deps_to_install, width=32, - label=label - ) + deps_list_bar = progress.bar(deps_to_install, width=32, label=label) trusted_hosts = [] # Install these because @@ -698,9 +741,10 @@ def batch_install(project, deps_list, procs, failed_deps_queue, dep.markers = str(strip_extras_markers_from_requirement(dep.get_markers())) # Install the module. is_artifact = False - if dep.is_file_or_url and (dep.is_direct_url or any( - dep.req.uri.endswith(ext) for ext in ["zip", "tar.gz"] - )): + if dep.is_file_or_url and ( + dep.is_direct_url + or any(dep.req.uri.endswith(ext) for ext in ["zip", "tar.gz"]) + ): is_artifact = True elif dep.is_vcs: is_artifact = True @@ -755,20 +799,23 @@ def do_install_dependencies( requirements_dir=None, pypi_mirror=None, ): - """" + """ " Executes the install functionality. If emit_requirements is True, simply spits out a requirements format to stdout. """ import queue + if emit_requirements: bare = True # 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( - crayons.normal(fix_utf8("Installing dependencies from Pipfile..."), bold=True) + crayons.normal( + 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) @@ -777,9 +824,11 @@ def do_install_dependencies( if not bare: click.echo( crayons.normal( - fix_utf8("Installing dependencies from Pipfile.lock ({})...".format( - lockfile["_meta"].get("hash", {}).get("sha256")[-6:] - )), + fix_utf8( + "Installing dependencies from Pipfile.lock ({})...".format( + lockfile["_meta"].get("hash", {}).get("sha256")[-6:] + ) + ), bold=True, ) ) @@ -792,9 +841,7 @@ def do_install_dependencies( get_source_list(project, pypi_mirror=pypi_mirror) ) index_args = " ".join(index_args).replace(" -", "\n-") - deps = [ - req.as_line(sources=False, include_hashes=False) for req in deps_list - ] + deps = [req.as_line(sources=False, include_hashes=False) for req in deps_list] click.echo(index_args) click.echo("\n".join(sorted(deps))) sys.exit(0) @@ -809,9 +856,12 @@ def do_install_dependencies( editable_or_vcs_deps = [dep for dep in deps_list if (dep.editable or dep.vcs)] normal_deps = [dep for dep in deps_list if not (dep.editable or dep.vcs)] install_kwargs = { - "no_deps": no_deps, "ignore_hashes": ignore_hashes, "allow_global": allow_global, - "blocking": not concurrent, "pypi_mirror": pypi_mirror, - "sequential_deps": editable_or_vcs_deps + "no_deps": no_deps, + "ignore_hashes": ignore_hashes, + "allow_global": allow_global, + "blocking": not concurrent, + "pypi_mirror": pypi_mirror, + "sequential_deps": editable_or_vcs_deps, } batch_install( @@ -836,7 +886,9 @@ def do_install_dependencies( # Iterate over the hopefully-poorly-packaged dependencies... if not failed_deps_queue.empty(): click.echo( - crayons.normal(fix_utf8("Installing initially failed dependencies..."), bold=True) + crayons.normal( + fix_utf8("Installing initially failed dependencies..."), bold=True + ) ) retry_list = [] while not failed_deps_queue.empty(): @@ -844,7 +896,12 @@ def do_install_dependencies( retry_list.append(failed_dep) install_kwargs.update({"retry": False}) batch_install( - project, retry_list, procs, failed_deps_queue, requirements_dir, **install_kwargs + project, + retry_list, + procs, + failed_deps_queue, + requirements_dir, + **install_kwargs, ) if not procs.empty(): _cleanup_procs(project, procs, failed_deps_queue, retry=False) @@ -869,7 +926,8 @@ def do_create_virtualenv(project, python=None, site_packages=None, pypi_mirror=N """Creates a virtualenv.""" click.echo( - crayons.normal(fix_utf8("Creating a virtualenv for this project..."), bold=True), err=True + crayons.normal(fix_utf8("Creating a virtualenv for this project..."), bold=True), + err=True, ) click.echo( f"Pipfile: {crayons.yellow(project.pipfile_location, bold=True)}", @@ -903,7 +961,8 @@ 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( - crayons.normal(fix_utf8("Making site-packages available..."), bold=True), err=True + crayons.normal(fix_utf8("Making site-packages available..."), bold=True), + err=True, ) cmd.append("--system-site-packages") @@ -918,14 +977,22 @@ def do_create_virtualenv(project, python=None, site_packages=None, pypi_mirror=N c = subprocess_run(cmd, env=pip_config) click.echo(crayons.cyan(f"{c.stdout}"), err=True) if c.returncode != 0: - error = c.stderr if project.s.is_verbose() else exceptions.prettify_exc(c.stderr) - sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Failed creating virtual environment")) + error = ( + c.stderr if project.s.is_verbose() else exceptions.prettify_exc(c.stderr) + ) + sp.fail( + environments.PIPENV_SPINNER_FAIL_TEXT.format( + "Failed creating virtual environment" + ) + ) else: - sp.green.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Successfully created virtual environment!")) + sp.green.ok( + environments.PIPENV_SPINNER_OK_TEXT.format( + "Successfully created virtual environment!" + ) + ) if error is not None: - raise exceptions.VirtualenvCreationException( - extra=crayons.red(f"{error}") - ) + raise exceptions.VirtualenvCreationException(extra=crayons.red(f"{error}")) # Associate project directory with the environment. # This mimics Pew's "setproject". @@ -933,6 +1000,7 @@ def do_create_virtualenv(project, python=None, site_packages=None, pypi_mirror=N with open(project_file_name, "w") as f: f.write(vistir.misc.fs_str(project.project_directory)) from .environment import Environment + sources = project.pipfile_sources # project.get_location_for_virtualenv is only for if we are creating a new virtualenv # whereas virtualenv_location is for the current path to the runtime @@ -941,7 +1009,7 @@ def do_create_virtualenv(project, python=None, site_packages=None, pypi_mirror=N is_venv=True, sources=sources, pipfile=project.parsed_pipfile, - project=project + project=project, ) project._environment.add_dist("pipenv") # Say where the virtualenv is. @@ -1014,8 +1082,9 @@ def do_lock( if keep_outdated: if not project.lockfile_exists: raise exceptions.PipenvOptionsError( - "--keep-outdated", ctx=ctx, - message="Pipfile.lock must exist to use --keep-outdated!" + "--keep-outdated", + ctx=ctx, + message="Pipfile.lock must exist to use --keep-outdated!", ) cached_lockfile = project.lockfile_content # Create the lockfile. @@ -1056,12 +1125,13 @@ def do_lock( pypi_mirror=pypi_mirror, pipfile=packages, lockfile=lockfile, - keep_outdated=keep_outdated + keep_outdated=keep_outdated, ) # Support for --keep-outdated... if keep_outdated: from pipenv.vendor.packaging.utils import canonicalize_name + for section_name, section in ( ("default", project.packages), ("develop", project.dev_packages), @@ -1080,7 +1150,9 @@ def do_lock( for missing_pkg in missing: lockfile[key][missing_pkg] = cached_lockfile[key][missing_pkg].copy() # Overwrite any develop packages with default packages. - lockfile["develop"].update(overwrite_dev(lockfile.get("default", {}), lockfile["develop"])) + lockfile["develop"].update( + overwrite_dev(lockfile.get("default", {}), lockfile["develop"]) + ) if write: project.write_lockfile(lockfile) click.echo( @@ -1103,13 +1175,16 @@ def do_purge(project, bare=False, downloads=False, allow_global=False): if downloads: if not bare: - click.echo(crayons.normal(fix_utf8("Clearing out downloads directory..."), bold=True)) + click.echo( + crayons.normal(fix_utf8("Clearing out downloads directory..."), bold=True) + ) vistir.path.rmtree(project.download_location) return # Remove comments from the output, if any. installed = { - pep423_name(pkg.project_name) for pkg in project.environment.get_installed_packages() + pep423_name(pkg.project_name) + for pkg in project.environment.get_installed_packages() } bad_pkgs = {pep423_name(pkg) for pkg in BAD_PACKAGES} # Remove setuptools, pip, etc from targets for removal @@ -1123,19 +1198,20 @@ def do_purge(project, bare=False, downloads=False, allow_global=False): return installed if not bare: - click.echo( - fix_utf8(f"Found {len(to_remove)} installed package(s), purging...") - ) + click.echo(fix_utf8(f"Found {len(to_remove)} installed package(s), purging...")) command = [ which_pip(project, allow_global=allow_global), - "uninstall", "-y", + "uninstall", + "-y", ] + list(to_remove) if project.s.is_verbose(): click.echo(f"$ {cmd_list_to_shell(command)}") c = subprocess_run(command) if c.returncode != 0: - raise exceptions.UninstallError(installed, cmd_list_to_shell(command), c.stdout + c.stderr, c.returncode) + raise exceptions.UninstallError( + installed, cmd_list_to_shell(command), c.stdout + c.stderr, c.returncode + ) if not bare: click.echo(crayons.cyan(c.stdout)) click.echo(crayons.green("Environment now purged and fresh!")) @@ -1169,7 +1245,9 @@ def do_init( if not system and not project.s.PIPENV_USE_SYSTEM: if not project.virtualenv_exists: try: - do_create_virtualenv(project, python=python, three=None, pypi_mirror=pypi_mirror) + do_create_virtualenv( + project, python=python, three=None, pypi_mirror=pypi_mirror + ) except KeyboardInterrupt: cleanup_virtualenv(project, bare=False) sys.exit(1) @@ -1196,13 +1274,13 @@ def do_init( raise exceptions.DeployException elif (system or allow_global) and not (project.s.PIPENV_VIRTUALENV): click.echo( - crayons.yellow(fix_utf8( - "Pipfile.lock ({}) out of date, but installation " - "uses {} re-building lockfile must happen in " - "isolation. Please rebuild lockfile in a virtualenv. " - "Continuing anyway...".format( - old_hash[-6:], "--system" - )) + crayons.yellow( + fix_utf8( + "Pipfile.lock ({}) out of date, but installation " + "uses {} re-building lockfile must happen in " + "isolation. Please rebuild lockfile in a virtualenv. " + "Continuing anyway...".format(old_hash[-6:], "--system") + ) ), err=True, ) @@ -1232,11 +1310,13 @@ def do_init( "--system", "--system is intended to be used for Pipfile installation, " "not installation of specific packages. Aborting.\n" - "See also: --deploy flag." + "See also: --deploy flag.", ) else: click.echo( - crayons.normal(fix_utf8("Pipfile.lock not found, creating..."), bold=True), + crayons.normal( + fix_utf8("Pipfile.lock not found, creating..."), bold=True + ), err=True, ) do_lock( @@ -1284,6 +1364,7 @@ def get_pip_args( ): # type: (...) -> List[str] from .vendor.packaging.version import parse as parse_version + arg_map = { "pre": ["--pre"], "verbose": ["--verbose"], @@ -1294,7 +1375,7 @@ def get_pip_args( "no_deps": ["--no-deps"], "selective_upgrade": [ "--upgrade-strategy=only-if-needed", - "--exists-action={}".format(project.s.PIP_EXISTS_ACTION or "i") + "--exists-action={}".format(project.s.PIP_EXISTS_ACTION or "i"), ], "src_dir": src_dir, } @@ -1321,19 +1402,17 @@ def get_requirement_line( line = None if requirement.vcs or requirement.is_file_or_url: if src_dir and requirement.line_instance.wheel_kwargs: - requirement.line_instance._wheel_kwargs.update({ - "src_dir": src_dir - }) + requirement.line_instance._wheel_kwargs.update({"src_dir": src_dir}) requirement.line_instance.vcsrepo line = requirement.line_instance.line if requirement.line_instance.markers: - line = f'{line}; {requirement.line_instance.markers}' + line = f"{line}; {requirement.line_instance.markers}" if not format_for_file: line = f'"{line}"' if requirement.editable: if not format_for_file: return ["-e", line] - return f'-e {line}' + return f"-e {line}" if not format_for_file: return [line] return line @@ -1345,24 +1424,23 @@ def write_requirement_to_file( requirement, # type: Requirement requirements_dir=None, # type: Optional[str] src_dir=None, # type: Optional[str] - include_hashes=True # type: bool + include_hashes=True, # type: bool ): # type: (...) -> str if not requirements_dir: requirements_dir = vistir.path.create_tracked_tempdir( - prefix="pipenv", suffix="requirements") + prefix="pipenv", suffix="requirements" + ) line = requirement.line_instance.get_line( with_prefix=True, with_hashes=include_hashes, with_markers=True, as_list=False ) f = vistir.compat.NamedTemporaryFile( - prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir, - delete=False + prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir, delete=False ) if project.s.is_verbose(): click.echo( - f"Writing supplied requirement line to temporary file: {line!r}", - err=True + f"Writing supplied requirement line to temporary file: {line!r}", err=True ) f.write(vistir.misc.to_bytes(line)) r = f.name @@ -1385,7 +1463,7 @@ def pip_install( extra_indexes=None, pypi_mirror=None, trusted_hosts=None, - use_pep517=True + use_pep517=True, ): piplogger = logging.getLogger("pipenv.patched.notpip._internal.commands.install") src_dir = None @@ -1394,7 +1472,9 @@ def pip_install( trusted_hosts.extend(os.environ.get("PIP_TRUSTED_HOSTS", [])) if not allow_global: - src_dir = os.getenv("PIP_SRC", os.getenv("PIP_SRC_DIR", project.virtualenv_src_location)) + src_dir = os.getenv( + "PIP_SRC", os.getenv("PIP_SRC_DIR", project.virtualenv_src_location) + ) else: src_dir = os.getenv("PIP_SRC", os.getenv("PIP_SRC_DIR")) if requirement: @@ -1409,7 +1489,9 @@ def pip_install( if index and not extra_indexes: extra_indexes = [] if requirement.index: - extra_indexes = list(filter(lambda d: d.get('name') == requirement.index, project.sources)) + extra_indexes = list( + filter(lambda d: d.get("name") == requirement.index, project.sources) + ) if not extra_indexes: extra_indexes = list(project.sources) if requirement and requirement.vcs or requirement.editable: @@ -1424,15 +1506,21 @@ def pip_install( no_deps = True r = write_requirement_to_file( - project, requirement, requirements_dir=requirements_dir, src_dir=src_dir, - include_hashes=not ignore_hashes + project, + requirement, + requirements_dir=requirements_dir, + src_dir=src_dir, + include_hashes=not ignore_hashes, ) sources = get_source_list( - project, index, extra_indexes=extra_indexes, trusted_hosts=trusted_hosts, - pypi_mirror=pypi_mirror + project, + index, + extra_indexes=extra_indexes, + trusted_hosts=trusted_hosts, + pypi_mirror=pypi_mirror, ) if requirement.index in sources: - sources = list(filter(lambda d: d.get('name') == requirement.index, sources)) + sources = list(filter(lambda d: d.get("name") == requirement.index, sources)) if r: with open(r, "r") as fh: if "--hash" not in fh.read(): @@ -1445,11 +1533,21 @@ def pip_install( err=True, ) - pip_command = [project._which("python", allow_global=allow_global), "-m", "pip", "install"] + pip_command = [ + project._which("python", allow_global=allow_global), + "-m", + "pip", + "install", + ] pip_args = get_pip_args( - project, pre=pre, verbose=project.s.is_verbose(), upgrade=True, - selective_upgrade=selective_upgrade, no_use_pep517=not use_pep517, - no_deps=no_deps, require_hashes=not ignore_hashes, + project, + pre=pre, + verbose=project.s.is_verbose(), + upgrade=True, + selective_upgrade=selective_upgrade, + no_use_pep517=not use_pep517, + no_deps=no_deps, + require_hashes=not ignore_hashes, ) pip_command.extend(pip_args) if r: @@ -1463,22 +1561,20 @@ def pip_install( DEFAULT_EXISTS_ACTION = "w" if selective_upgrade: DEFAULT_EXISTS_ACTION = "i" - exists_action = vistir.misc.fs_str(project.s.PIP_EXISTS_ACTION or DEFAULT_EXISTS_ACTION) + exists_action = vistir.misc.fs_str( + project.s.PIP_EXISTS_ACTION or DEFAULT_EXISTS_ACTION + ) pip_config = { "PIP_CACHE_DIR": vistir.misc.fs_str(cache_dir.as_posix()), "PIP_WHEEL_DIR": vistir.misc.fs_str(cache_dir.joinpath("wheels").as_posix()), - "PIP_DESTINATION_DIR": vistir.misc.fs_str( - cache_dir.joinpath("pkgs").as_posix() - ), + "PIP_DESTINATION_DIR": vistir.misc.fs_str(cache_dir.joinpath("pkgs").as_posix()), "PIP_EXISTS_ACTION": exists_action, "PATH": vistir.misc.fs_str(os.environ.get("PATH")), } if src_dir: if project.s.is_verbose(): click.echo(f"Using source directory: {src_dir!r}", err=True) - pip_config.update( - {"PIP_SRC": vistir.misc.fs_str(src_dir)} - ) + pip_config.update({"PIP_SRC": vistir.misc.fs_str(src_dir)}) c = subprocess_run(pip_command, block=block, env=pip_config) c.env = pip_config return c @@ -1489,17 +1585,17 @@ def pip_download(project, package_name): pip_config = { "PIP_CACHE_DIR": vistir.misc.fs_str(cache_dir.as_posix()), "PIP_WHEEL_DIR": vistir.misc.fs_str(cache_dir.joinpath("wheels").as_posix()), - "PIP_DESTINATION_DIR": vistir.misc.fs_str( - cache_dir.joinpath("pkgs").as_posix() - ), + "PIP_DESTINATION_DIR": vistir.misc.fs_str(cache_dir.joinpath("pkgs").as_posix()), } for source in project.sources: cmd = [ which_pip(project), "download", package_name, - "-i", source["url"], - "-d", project.download_location, + "-i", + source["url"], + "-d", + project.download_location, ] c = subprocess_run(cmd, env=pip_config) if c.returncode == 0: @@ -1524,6 +1620,7 @@ def fallback_which(command, location=None, allow_global=False, system=False): """ from .vendor.pythonfinder import Finder + if not command: raise ValueError("fallback_which: Must provide a command to search for...") if not isinstance(command, str): @@ -1572,7 +1669,7 @@ def system_which(command, path=None): result = shutil.which(command, path=path) if result is None: _which = "where" if os.name == "nt" else "which -a" - env = {'PATH': path} if path else None + env = {"PATH": path} if path else None c = subprocess_run(f"{_which} {command}", shell=True, env=env) if c.returncode == 127: click.echo( @@ -1707,9 +1804,11 @@ def ensure_lockfile(project, keep_outdated=False, pypi_mirror=None): if new_hash != old_hash: click.echo( crayons.yellow( - fix_utf8("Pipfile.lock ({}) out of date, updating to ({})...".format( - old_hash[-6:], new_hash[-6:] - )), + fix_utf8( + "Pipfile.lock ({}) out of date, updating to ({})...".format( + old_hash[-6:], new_hash[-6:] + ) + ), bold=True, ), err=True, @@ -1725,7 +1824,7 @@ def do_py(project, ctx=None, system=False): "{}({}){}".format( crayons.red("No virtualenv has been created for this project "), crayons.yellow(project.project_directory, bold=True), - crayons.red(" yet!") + crayons.red(" yet!"), ), err=True, ) @@ -1751,8 +1850,9 @@ def do_outdated(project, pypi_mirror=None, pre=False, clear=False): installed_packages = project.environment.get_installed_packages() outdated_packages = { - canonicalize_name(pkg.project_name): package_info - (pkg.project_name, pkg.parsed_version, pkg.latest_version) + canonicalize_name(pkg.project_name): package_info( + pkg.project_name, pkg.parsed_version, pkg.latest_version + ) for pkg in project.environment.get_outdated_packages() } reverse_deps = { @@ -1763,7 +1863,9 @@ def do_outdated(project, pypi_mirror=None, pre=False, clear=False): dep = Requirement.from_line(str(result.as_requirement())) packages.update(dep.as_pipfile()) updated_packages = {} - lockfile = do_lock(project, clear=clear, pre=pre, write=False, pypi_mirror=pypi_mirror) + lockfile = do_lock( + project, clear=clear, pre=pre, write=False, pypi_mirror=pypi_mirror + ) for section in ("develop", "default"): for package in lockfile[section]: try: @@ -1801,7 +1903,8 @@ def do_outdated(project, pypi_mirror=None, pre=False, clear=False): "{!s} available.".format( package, old_version, required, pipfile_version_text, new_version ) - ), err=True + ), + err=True, ) if not outdated: click.echo(crayons.green("All packages are up to date!", bold=True)) @@ -1923,7 +2026,8 @@ def do_install( error, traceback = None, None click.echo( crayons.normal( - fix_utf8("Requirements file provided! Importing into Pipfile..."), bold=True + fix_utf8("Requirements file provided! Importing into Pipfile..."), + bold=True, ), err=True, ) @@ -1952,10 +2056,9 @@ def do_install( click.echo(crayons.red(error)) click.echo(crayons.yellow(str(traceback)), err=True) sys.exit(1) + # Allow more than one package to be provided. - package_args = [p for p in packages] + [ - f"-e {pkg}" for pkg in editable_packages - ] + package_args = [p for p in packages] + [f"-e {pkg}" for pkg in editable_packages] # Support for --selective-upgrade. # We should do this part first to make sure that we actually do selectively upgrade # the items specified @@ -1993,7 +2096,7 @@ def do_install( pre=pre, requirements_dir=requirements_directory, pypi_mirror=pypi_mirror, - keep_outdated=keep_outdated + keep_outdated=keep_outdated, ) # This is for if the user passed in dependencies, then we want to make sure we @@ -2001,7 +2104,7 @@ def do_install( from .vendor.requirementslib.models.requirements import Requirement # make a tuple of (display_name, entry) - pkg_list = packages + [f'-e {pkg}' for pkg in editable_packages] + pkg_list = packages + [f"-e {pkg}" for pkg in editable_packages] if not system and not project.virtualenv_exists: do_init( project, @@ -2024,7 +2127,9 @@ def do_install( ) ) # pip install: - with vistir.contextmanagers.temp_environ(), create_spinner("Installing...", project.s) as sp: + with vistir.contextmanagers.temp_environ(), create_spinner( + "Installing...", project.s + ) as sp: if not system: os.environ["PIP_USER"] = vistir.compat.fs_str("0") if "PYTHONHOME" in os.environ: @@ -2033,15 +2138,23 @@ def do_install( try: pkg_requirement = Requirement.from_line(pkg_line) except ValueError as e: - sp.write_err(vistir.compat.fs_str("{}: {}".format(crayons.red("WARNING"), e))) - sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Installation Failed")) + sp.write_err( + vistir.compat.fs_str("{}: {}".format(crayons.red("WARNING"), e)) + ) + sp.red.fail( + environments.PIPENV_SPINNER_FAIL_TEXT.format( + "Installation Failed" + ) + ) sys.exit(1) no_deps = False sp.text = "Installing..." try: sp.text = f"Installing {pkg_requirement.name}..." if project.s.is_verbose(): - sp.hide_and_write(f"Installing package: {pkg_requirement.as_line(include_hashes=False)}") + sp.hide_and_write( + f"Installing package: {pkg_requirement.as_line(include_hashes=False)}" + ) c = pip_install( project, pkg_requirement, @@ -2061,31 +2174,49 @@ def do_install( crayons.red("Error: ", bold=True), crayons.green(pkg_line) ), ) + sp.write_err(vistir.compat.fs_str(f"Error text: {c.stdout}")) sp.write_err( - vistir.compat.fs_str(f"Error text: {c.stdout}") + crayons.cyan(vistir.compat.fs_str(format_pip_error(c.stderr))) ) - sp.write_err(crayons.cyan(vistir.compat.fs_str(format_pip_error(c.stderr)))) if project.s.is_verbose(): - sp.write_err(crayons.cyan(vistir.compat.fs_str(format_pip_output(c.stdout)))) - if "setup.py egg_info" in c.stderr: - sp.write_err(vistir.compat.fs_str( - "This is likely caused by a bug in {}. " - "Report this to its maintainers.".format( - crayons.green(pkg_requirement.name) + sp.write_err( + crayons.cyan( + vistir.compat.fs_str(format_pip_output(c.stdout)) ) - )) - sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Installation Failed")) + ) + if "setup.py egg_info" in c.stderr: + sp.write_err( + vistir.compat.fs_str( + "This is likely caused by a bug in {}. " + "Report this to its maintainers.".format( + crayons.green(pkg_requirement.name) + ) + ) + ) + sp.red.fail( + environments.PIPENV_SPINNER_FAIL_TEXT.format( + "Installation Failed" + ) + ) sys.exit(1) except (ValueError, RuntimeError) as e: - sp.write_err(vistir.compat.fs_str( - "{}: {}".format(crayons.red("WARNING"), e), - )) - sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format( - "Installation Failed", - )) + sp.write_err( + vistir.compat.fs_str( + "{}: {}".format(crayons.red("WARNING"), e), + ) + ) + sp.red.fail( + environments.PIPENV_SPINNER_FAIL_TEXT.format( + "Installation Failed", + ) + ) sys.exit(1) # Warn if --editable wasn't passed. - if pkg_requirement.is_vcs and not pkg_requirement.editable and not project.s.PIPENV_RESOLVE_VCS: + if ( + pkg_requirement.is_vcs + and not pkg_requirement.editable + and not project.s.PIPENV_RESOLVE_VCS + ): sp.write_err( "{}: You installed a VCS dependency in non-editable mode. " "This will work fine, but sub-dependencies will not be resolved by {}." @@ -2095,15 +2226,19 @@ def do_install( crayons.yellow("$ pipenv lock"), ) ) - sp.write(vistir.compat.fs_str( - "{} {} {} {}{}".format( - crayons.normal("Adding", bold=True), - crayons.green(f"{pkg_requirement.name}", bold=True), - crayons.normal("to Pipfile's", bold=True), - crayons.yellow("[dev-packages]" if dev else "[packages]", bold=True), - crayons.normal(fix_utf8("..."), bold=True), + sp.write( + vistir.compat.fs_str( + "{} {} {} {}{}".format( + crayons.normal("Adding", bold=True), + crayons.green(f"{pkg_requirement.name}", bold=True), + crayons.normal("to Pipfile's", bold=True), + crayons.yellow( + "[dev-packages]" if dev else "[packages]", bold=True + ), + crayons.normal(fix_utf8("..."), bold=True), + ) ) - )) + ) # Add the package to the Pipfile. indexes = list(filter(None, [index_url, *extra_index_url])) for index in indexes: @@ -2116,15 +2251,20 @@ def do_install( project.add_package_to_pipfile(pkg_requirement, dev) except ValueError: import traceback + sp.write_err( "{} {}".format( crayons.red("Error:", bold=True), traceback.format_exc() ) ) - sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format( - "Failed adding package to Pipfile" - )) - sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Installation Succeeded")) + sp.fail( + environments.PIPENV_SPINNER_FAIL_TEXT.format( + "Failed adding package to Pipfile" + ) + ) + sp.ok( + environments.PIPENV_SPINNER_OK_TEXT.format("Installation Succeeded") + ) # Update project settings with pre preference. if pre: project.update_settings({"allow_prereleases": pre}) @@ -2157,7 +2297,7 @@ def do_uninstall( all=False, keep_outdated=False, pypi_mirror=None, - ctx=None + ctx=None, ): from .vendor.packaging.utils import canonicalize_name from .vendor.requirementslib.models.requirements import Requirement @@ -2177,9 +2317,7 @@ def do_uninstall( ] packages += editable_pkgs package_names = {p for p in packages if p} - package_map = { - canonicalize_name(p): p for p in packages if p - } + package_map = {canonicalize_name(p): p for p in packages if p} installed_package_names = project.installed_package_names # Intelligently detect if --dev should be used or not. lockfile_packages = set() @@ -2200,7 +2338,8 @@ def do_uninstall( return click.echo( crayons.normal( - fix_utf8("Un-installing {}...".format(crayons.yellow("[dev-packages]"))), bold=True + fix_utf8("Un-installing {}...".format(crayons.yellow("[dev-packages]"))), + bold=True, ) ) package_names = set(project_pkg_names["dev"]) - set(project_pkg_names["default"]) @@ -2218,20 +2357,22 @@ def do_uninstall( if all: click.echo( crayons.normal( - fix_utf8("Un-installing all {} and {}...".format( - crayons.yellow("[dev-packages]"), - crayons.yellow("[packages]"), - )), bold=True + fix_utf8( + "Un-installing all {} and {}...".format( + crayons.yellow("[dev-packages]"), + crayons.yellow("[packages]"), + ) + ), + bold=True, ) ) do_purge(project, bare=False, allow_global=system) sys.exit(0) - selected_pkg_map = { - canonicalize_name(p): p for p in package_names - } + selected_pkg_map = {canonicalize_name(p): p for p in package_names} packages_to_remove = [ - p for normalized, p in selected_pkg_map.items() + p + for normalized, p in selected_pkg_map.items() if normalized in (used_packages - bad_pkgs) ] pip_path = None @@ -2253,15 +2394,15 @@ def do_uninstall( failure = True if not failure and pipfile_remove: 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 - ) + in_dev_packages = project.get_package_name_in_pipfile(package_name, dev=True) if normalized in lockfile_packages: - click.echo("{} {} {} {}".format( - crayons.cyan("Removing"), - crayons.green(package_name), - crayons.cyan("from"), - crayons.white(fix_utf8("Pipfile.lock..."))) + click.echo( + "{} {} {} {}".format( + crayons.cyan("Removing"), + crayons.green(package_name), + crayons.cyan("from"), + crayons.white(fix_utf8("Pipfile.lock...")), + ) ) lockfile = project.get_or_create_lockfile() if normalized in lockfile.default: @@ -2288,14 +2429,22 @@ def do_uninstall( if in_packages: project.remove_package_from_pipfile(package_name, dev=False) if lock: - do_lock(project, system=system, keep_outdated=keep_outdated, pypi_mirror=pypi_mirror) + do_lock( + project, system=system, keep_outdated=keep_outdated, pypi_mirror=pypi_mirror + ) sys.exit(int(failure)) -def do_shell(project, three=None, python=False, fancy=False, shell_args=None, pypi_mirror=None): +def do_shell( + project, three=None, python=False, fancy=False, shell_args=None, pypi_mirror=None +): # Ensure that virtualenv is available. ensure_project( - project, three=three, python=python, validate=False, pypi_mirror=pypi_mirror, + project, + three=three, + python=python, + validate=False, + pypi_mirror=pypi_mirror, ) # Support shell compatibility mode. @@ -2327,9 +2476,11 @@ def do_shell(project, three=None, python=False, fancy=False, shell_args=None, py try: shell.fork_compat(*fork_args) except (AttributeError, ImportError): - click.echo(fix_utf8( - "Compatibility mode not supported. " - "Trying to continue as well-configured shell..."), + click.echo( + fix_utf8( + "Compatibility mode not supported. " + "Trying to continue as well-configured shell..." + ), err=True, ) shell.fork(*fork_args) @@ -2347,9 +2498,7 @@ def _inline_activate_virtualenv(project): except Exception: click.echo( "{}: There was an unexpected error while activating your " - "virtualenv. Continuing anyway...".format( - crayons.red("Warning", bold=True) - ), + "virtualenv. Continuing anyway...".format(crayons.red("Warning", bold=True)), err=True, ) @@ -2444,11 +2593,13 @@ def do_run_posix(project, script, command, env): os.execve( command_path, [command_path, *(os.path.expandvars(arg) for arg in script.args)], - env + env, ) -def do_run(project, command, args, three=None, python=False, pypi_mirror=None, quiet=False): +def do_run( + project, command, args, three=None, python=False, pypi_mirror=None, quiet=False +): """Attempt to run command either pulling from project or interpreting as executable. Args are appended to the command in [scripts] section of project if found. @@ -2457,16 +2608,22 @@ def do_run(project, command, args, three=None, python=False, pypi_mirror=None, q # Ensure that virtualenv is available. ensure_project( - project, three=three, python=python, validate=False, pypi_mirror=pypi_mirror, + project, + three=three, + python=python, + validate=False, + pypi_mirror=pypi_mirror, ) load_dot_env(project, quiet=quiet) env = os.environ.copy() env.pop("PIP_SHIMS_BASE_MODULE", None) - path = env.get('PATH', '') + path = env.get("PATH", "") if project.virtualenv_location: - new_path = os.path.join(project.virtualenv_location, 'Scripts' if os.name == 'nt' else 'bin') + new_path = os.path.join( + project.virtualenv_location, "Scripts" if os.name == "nt" else "bin" + ) paths = path.split(os.pathsep) paths.insert(0, new_path) path = os.pathsep.join(paths) @@ -2488,7 +2645,7 @@ def do_run(project, command, args, three=None, python=False, pypi_mirror=None, q except ScriptEmptyError: click.echo("Can't run script {0!r}-it's empty?", err=True) run_args = [project, script] - run_kwargs = {'env': env} + run_kwargs = {"env": env} # We're using `do_run_nt` on CI (even if we're running on a non-nt machine) # as a workaround for https://github.com/pypa/pipenv/issues/4909. if os.name == "nt" or environments.PIPENV_IS_CI: @@ -2509,7 +2666,7 @@ def do_check( output="default", key=None, quiet=False, - pypi_mirror=None + pypi_mirror=None, ): from pipenv.vendor.vistir.compat import JSONDecodeError @@ -2524,7 +2681,11 @@ def do_check( pypi_mirror=pypi_mirror, ) if not quiet and not project.s.is_quiet(): - click.echo(crayons.normal(decode_for_output("Checking PEP 508 requirements..."), bold=True)) + click.echo( + crayons.normal( + decode_for_output("Checking PEP 508 requirements..."), bold=True + ) + ) pep508checker_path = pep508checker.__file__.rstrip("cdo") safety_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "patched", "safety" @@ -2545,11 +2706,15 @@ def do_check( try: results = simplejson.loads(c.stdout.strip()) except JSONDecodeError: - click.echo("{}\n{}\n{}".format( - crayons.white(decode_for_output("Failed parsing pep508 results: "), bold=True), - c.stdout.strip(), - c.stderr.strip() - )) + click.echo( + "{}\n{}\n{}".format( + crayons.white( + decode_for_output("Failed parsing pep508 results: "), bold=True + ), + c.stdout.strip(), + c.stderr.strip(), + ) + ) sys.exit(1) # Load the pipfile. p = pipfile.Pipfile.load(project.pipfile_location) @@ -2577,8 +2742,10 @@ def do_check( if not quiet and not project.s.is_quiet(): click.echo(crayons.green("Passed!")) if not quiet and not project.s.is_quiet(): - click.echo(crayons.normal( - decode_for_output("Checking installed package safety..."), bold=True) + click.echo( + crayons.normal( + decode_for_output("Checking installed package safety..."), bold=True + ) ) if ignore: if not isinstance(ignore, (tuple, list)): @@ -2615,7 +2782,9 @@ def do_check( except (ValueError, JSONDecodeError): raise exceptions.JSONParseError(c.stdout, c.stderr) except Exception: - raise exceptions.PipenvCmdError(cmd_list_to_shell(c.args), c.stdout, c.stderr, c.returncode) + raise exceptions.PipenvCmdError( + cmd_list_to_shell(c.args), c.stdout, c.stderr, c.returncode + ) for (package, resolved, installed, description, vuln, *_) in results: click.echo( "{}: {} {} resolved ({} installed)!".format( @@ -2640,6 +2809,7 @@ def do_check( def do_graph(project, bare=False, json=False, json_tree=False, reverse=False): from pipenv.vendor import pipdeptree from pipenv.vendor.vistir.compat import JSONDecodeError + pipdeptree_path = pipdeptree.__file__.rstrip("cdo") try: python_path = project._which("python") @@ -2656,7 +2826,7 @@ def do_graph(project, bare=False, json=False, json_tree=False, reverse=False): except RuntimeError: pass else: - if not os.name == 'nt': # bugfix #4388 + if not os.name == "nt": # bugfix #4388 python_path = Path(python_path).as_posix() pipdeptree_path = Path(pipdeptree_path).as_posix() @@ -2829,12 +2999,20 @@ def do_sync( def do_clean( - project, three=None, python=None, dry_run=False, bare=False, pypi_mirror=None, - system=False + project, + three=None, + python=None, + dry_run=False, + bare=False, + pypi_mirror=None, + system=False, ): # Ensure that virtualenv is available. from packaging.utils import canonicalize_name - ensure_project(project, three=three, python=python, validate=False, pypi_mirror=pypi_mirror) + + ensure_project( + project, three=three, python=python, validate=False, pypi_mirror=pypi_mirror + ) ensure_lockfile(project, pypi_mirror=pypi_mirror) # Make sure that the virtualenv's site packages are configured correctly # otherwise we may end up removing from the global site packages directory diff --git a/pipenv/environment.py b/pipenv/environment.py index e1a99458..b832b526 100644 --- a/pipenv/environment.py +++ b/pipenv/environment.py @@ -12,21 +12,17 @@ from sysconfig import get_paths, get_python_version import pkg_resources import pipenv - from pipenv.environments import is_type_checking -from pipenv.utils.shell import make_posix, normalize_path -from pipenv.utils.processes import subprocess_run 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 from pipenv.vendor import vistir from pipenv.vendor.cached_property import cached_property from pipenv.vendor.packaging.utils import canonicalize_name - if is_type_checking(): from types import ModuleType - from typing import ( - ContextManager, Dict, Generator, List, Optional, Set, Union - ) + from typing import ContextManager, Dict, Generator, List, Optional, Set, Union import pip_shims.shims import tomlkit @@ -47,10 +43,10 @@ class Environment: base_working_set=None, # type: pkg_resources.WorkingSet pipfile=None, # type: Optional[Union[tomlkit.toml_document.TOMLDocument, TPipfile]] sources=None, # type: Optional[List[TSource]] - project=None # type: Optional[Project] + project=None, # type: Optional[Project] ): super().__init__() - self._modules = {'pkg_resources': pkg_resources, 'pipenv': pipenv} + self._modules = {"pkg_resources": pkg_resources, "pipenv": pipenv} self.base_working_set = base_working_set if base_working_set else BASE_WORKING_SET prefix = normalize_path(prefix) self._python = None @@ -82,9 +78,10 @@ class Environment: self._modules[name] = importlib.import_module(name) module = self._modules[name] if not module: - dist = next(iter( - dist for dist in self.base_working_set if dist.project_name == name - ), None) + dist = next( + iter(dist for dist in self.base_working_set if dist.project_name == name), + None, + ) if dist: dist.activate() module = importlib.import_module(name) @@ -152,7 +149,9 @@ class Environment: if not os.path.exists(include_dir): include_dirs = self.get_include_path() if include_dirs: - include_path = include_dirs.get("include", include_dirs.get("platinclude")) + include_path = include_dirs.get( + "include", include_dirs.get("platinclude") + ) if not include_path: return {} include_dir = Path(include_path) @@ -169,7 +168,8 @@ class Environment: base, leaf = os.path.split(path) base, parent = os.path.split(base) leaf = os.path.join(parent, leaf).replace( - replace_version, self.python_info.get("py_version_short", get_python_version()) + replace_version, + self.python_info.get("py_version_short", get_python_version()), ) return os.path.join(base, leaf) return path @@ -213,16 +213,21 @@ class Environment: try: paths = self.get_paths() except Exception: - install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix' - paths = get_paths(install_scheme, vars={ - 'base': prefix, - 'platbase': prefix, - }) + install_scheme = "nt" if (os.name == "nt") else "posix_prefix" + paths = get_paths( + install_scheme, + vars={ + "base": prefix, + "platbase": prefix, + }, + ) current_version = get_python_version() try: for k in list(paths.keys()): if not os.path.exists(paths[k]): - paths[k] = self._replace_parent_version(paths[k], current_version) + paths[k] = self._replace_parent_version( + paths[k], current_version + ) except OSError: # Sometimes virtualenvs are made using virtualenv interpreters and there is no # include directory, which will cause this approach to fail. This failsafe @@ -231,11 +236,14 @@ class Environment: paths.update(self.get_lib_paths()) paths["scripts"] = self.script_basedir if not paths: - install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix' - paths = get_paths(install_scheme, vars={ - 'base': prefix, - 'platbase': prefix, - }) + install_scheme = "nt" if (os.name == "nt") else "posix_prefix" + paths = get_paths( + install_scheme, + vars={ + "base": prefix, + "platbase": prefix, + }, + ) if not os.path.exists(paths["purelib"]) and not os.path.exists(paths["platlib"]): lib_paths = self.get_lib_paths() paths.update(lib_paths) @@ -249,7 +257,7 @@ class Environment: else: lib_dirs = purelib + os.pathsep + platlib paths["libdir"] = purelib - paths['PYTHONPATH'] = os.pathsep.join(["", ".", lib_dirs]) + paths["PYTHONPATH"] = os.pathsep.join(["", ".", lib_dirs]) paths["libdirs"] = lib_dirs return paths @@ -258,11 +266,14 @@ class Environment: # type: () -> str """Path to the environment scripts dir""" prefix = make_posix(self.prefix.as_posix()) - install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix' - paths = get_paths(install_scheme, vars={ - 'base': prefix, - 'platbase': prefix, - }) + install_scheme = "nt" if (os.name == "nt") else "posix_prefix" + paths = get_paths( + install_scheme, + vars={ + "base": prefix, + "platbase": prefix, + }, + ) return paths["scripts"] @property @@ -291,20 +302,30 @@ class Environment: """ from .vendor.vistir.compat import JSONDecodeError + current_executable = Path(sys.executable).as_posix() if not self.python or self.python == current_executable: return sys.path elif any([sys.prefix == self.prefix, not self.is_venv]): return sys.path cmd_args = [self.python, "-c", "import json, sys; print(json.dumps(sys.path))"] - path, _ = vistir.misc.run(cmd_args, return_object=False, nospin=True, block=True, combine_stderr=False, write_to_stdout=False) + path, _ = vistir.misc.run( + cmd_args, + return_object=False, + nospin=True, + block=True, + combine_stderr=False, + write_to_stdout=False, + ) try: path = json.loads(path.strip()) except JSONDecodeError: path = sys.path return path - def build_command(self, python_lib=False, python_inc=False, scripts=False, py_version=False): + def build_command( + self, python_lib=False, python_inc=False, scripts=False, py_version=False + ): # type: (bool, bool, bool, bool) -> str """Build the text for running a command in the given environment @@ -332,16 +353,26 @@ class Environment: dist_prefix = f"{key}lib" # XXX: We need to get 'stdlib' or 'platstdlib' sys_prefix = "{}stdlib".format("" if key == "pure" else key) - pylib_lines.append(f"u'{dist_prefix}': u'{{{{0}}}}'.format({distutils_line.format(var, val)})") - pylib_lines.append(f"u'{sys_prefix}': u'{{{{0}}}}'.format({sysconfig_line.format(sys_prefix)})") + pylib_lines.append( + f"u'{dist_prefix}': u'{{{{0}}}}'.format({distutils_line.format(var, val)})" + ) + pylib_lines.append( + f"u'{sys_prefix}': u'{{{{0}}}}'.format({sysconfig_line.format(sys_prefix)})" + ) if python_inc: for key, var, val in (("include", "inc", "0"), ("platinclude", "inc", "1")): - pylib_lines.append(f"u'{key}': u'{{{{0}}}}'.format({distutils_line.format(var, val)})") + pylib_lines.append( + f"u'{key}': u'{{{{0}}}}'.format({distutils_line.format(var, val)})" + ) lines = pylib_lines + pyinc_lines if scripts: - lines.append("u'scripts': u'{{0}}'.format(%s)" % sysconfig_line.format("scripts")) + lines.append( + "u'scripts': u'{{0}}'.format(%s)" % sysconfig_line.format("scripts") + ) if py_version: - lines.append("u'py_version_short': u'{{0}}'.format(distutils.sysconfig.get_python_version()),") + lines.append( + "u'py_version_short': u'{{0}}'.format(distutils.sysconfig.get_python_version())," + ) lines_as_str = ",".join(lines) py_command = py_command % lines_as_str return py_command @@ -357,7 +388,9 @@ class Environment: tmpfile = vistir.path.create_tracked_tempfile(suffix=".json") tmpfile.close() tmpfile_path = make_posix(tmpfile.name) - py_command = self.build_command(python_lib=True, python_inc=True, scripts=True, py_version=True) + py_command = self.build_command( + python_lib=True, python_inc=True, scripts=True, py_version=True + ) command = [self.python, "-c", py_command.format(tmpfile_path)] c = subprocess_run(command) if c.returncode == 0: @@ -366,7 +399,14 @@ class Environment: paths = json.load(fh) if "purelib" in paths: paths["libdir"] = paths["purelib"] = make_posix(paths["purelib"]) - for key in ("platlib", "scripts", "platstdlib", "stdlib", "include", "platinclude"): + for key in ( + "platlib", + "scripts", + "platstdlib", + "stdlib", + "include", + "platinclude", + ): if key in paths: paths[key] = make_posix(paths[key]) return paths @@ -405,16 +445,27 @@ class Environment: if not paths: if not self.prefix.joinpath("lib").exists(): return {} - stdlib_path = next(iter([ - p for p in self.prefix.joinpath("lib").iterdir() - if p.name.startswith("python") - ]), None) + stdlib_path = next( + iter( + [ + p + for p in self.prefix.joinpath("lib").iterdir() + if p.name.startswith("python") + ] + ), + None, + ) lib_path = None if stdlib_path: - lib_path = next(iter([ - p.as_posix() for p in stdlib_path.iterdir() - if p.name == "site-packages" - ])) + lib_path = next( + iter( + [ + p.as_posix() + for p in stdlib_path.iterdir() + if p.name == "site-packages" + ] + ) + ) paths = {"stdlib": stdlib_path.as_posix()} if lib_path: paths["purelib"] = lib_path @@ -503,9 +554,10 @@ class Environment: when installing. """ from .vendor.packaging.version import parse as parse_version - pip = next(iter( - pkg for pkg in self.get_installed_packages() if pkg.key == "pip" - ), None) + + pip = next( + iter(pkg for pkg in self.get_installed_packages() if pkg.key == "pip"), None + ) if pip is not None: return parse_version(pip.version) return parse_version("20.2") @@ -542,8 +594,12 @@ class Environment: :rtype: iterator """ - pip_target_dir = os.environ.get('PIP_TARGET') - libdirs = [pip_target_dir] if pip_target_dir else self.base_paths["libdirs"].split(os.pathsep) + pip_target_dir = os.environ.get("PIP_TARGET") + libdirs = ( + [pip_target_dir] + if pip_target_dir + else self.base_paths["libdirs"].split(os.pathsep) + ) dists = (pkg_resources.find_distributions(libdir) for libdir in libdirs) yield from itertools.chain.from_iterable(dists) @@ -575,8 +631,10 @@ class Environment: # type: (pkg_resources.Distribution) -> bool """Determine whether the supplied distribution is in the environment.""" from .environments import normalize_pipfile_path as _normalized + prefixes = [ - _normalized(prefix) for prefix in self.base_paths["libdirs"].split(os.pathsep) + _normalized(prefix) + for prefix in self.base_paths["libdirs"].split(os.pathsep) if _normalized(prefix).startswith(_normalized(self.prefix.as_posix())) ] location = self.locate_dist(dist) @@ -590,7 +648,8 @@ class Environment: """Returns all of the installed packages in a given environment""" workingset = self.get_working_set() packages = [ - pkg for pkg in workingset + pkg + for pkg in workingset if self.dist_is_in_project(pkg) and pkg.key != "python" ] return packages @@ -606,20 +665,23 @@ class Environment: pip_options.cache_dir = self.project.s.PIPENV_CACHE_DIR pip_options.pre = self.pipfile.get("pre", pre) with pip_command._build_session(pip_options) as session: - finder = get_package_finder(install_cmd=pip_command, options=pip_options, session=session) + finder = get_package_finder( + install_cmd=pip_command, options=pip_options, session=session + ) yield finder def get_package_info(self, pre=False): # type: (bool) -> Generator[pkg_resources.Distribution, None, None] from .vendor.pip_shims.shims import parse_version, pip_version + dependency_links = [] packages = self.get_installed_packages() # This code is borrowed from pip's current implementation if parse_version(pip_version) < parse_version("19.0"): for dist in packages: - if dist.has_metadata('dependency_links.txt'): + if dist.has_metadata("dependency_links.txt"): dependency_links.extend( - dist.get_metadata_lines('dependency_links.txt') + dist.get_metadata_lines("dependency_links.txt") ) with self.get_finder() as finder: @@ -627,24 +689,29 @@ class Environment: finder.add_dependency_links(dependency_links) for dist in packages: - typ = 'unknown' + typ = "unknown" all_candidates = finder.find_all_candidates(dist.key) if not self.pipfile.get("pre", finder.allow_all_prereleases): # Remove prereleases all_candidates = [ - candidate for candidate in all_candidates + candidate + for candidate in all_candidates if not candidate.version.is_prerelease ] if not all_candidates: continue - candidate_evaluator = finder.make_candidate_evaluator(project_name=dist.key) - best_candidate_result = candidate_evaluator.compute_best_candidate(all_candidates) + candidate_evaluator = finder.make_candidate_evaluator( + project_name=dist.key + ) + best_candidate_result = candidate_evaluator.compute_best_candidate( + all_candidates + ) remote_version = best_candidate_result.best_candidate.version if best_candidate_result.best_candidate.link.is_wheel: - typ = 'wheel' + typ = "wheel" else: - typ = 'sdist' + typ = "sdist" # This is dirty but makes the rest of the code much cleaner dist.latest_version = remote_version dist.latest_filetype = typ @@ -653,7 +720,8 @@ class Environment: def get_outdated_packages(self, pre=False): # type: (bool) -> List[pkg_resources.Distribution] return [ - pkg for pkg in self.get_package_info(pre=pre) + pkg + for pkg in self.get_package_info(pre=pre) if pkg.latest_version._key > pkg.parsed_version._key ] @@ -664,15 +732,16 @@ class Environment: d = node.as_dict() if parent: - d['required_version'] = node.version_spec if node.version_spec else 'Any' + d["required_version"] = node.version_spec if node.version_spec else "Any" else: - d['required_version'] = d['installed_version'] + d["required_version"] = d["installed_version"] - get_children = lambda n: key_tree.get(n.key, []) # noqa + get_children = lambda n: key_tree.get(n.key, []) # noqa - d['dependencies'] = [ - cls._get_requirements_for_package(c, key_tree, parent=node, - chain=chain+[c.project_name]) + d["dependencies"] = [ + cls._get_requirements_for_package( + c, key_tree, parent=node, chain=chain + [c.project_name] + ) for c in get_children(node) if c.project_name not in chain ] @@ -701,7 +770,7 @@ class Environment: new_node = { "package_name": node["package_name"], "installed_version": node["installed_version"], - "required_version": node["required_version"] + "required_version": node["required_version"], } for dependency in node.get("dependencies", []): for dep in cls.reverse_dependency(dependency): @@ -712,6 +781,7 @@ class Environment: def reverse_dependencies(self): from vistir.misc import chunked, unnest + rdeps = {} for req in self.get_package_requirements(): for d in self.reverse_dependency(req): @@ -720,7 +790,7 @@ class Environment: pkg = { name: { "installed": d["installed_version"], - "required": d["required_version"] + "required": d["required_version"], } } parents = tuple(d.get("parent", ())) @@ -735,7 +805,7 @@ class Environment: entry = rdeps[k] if entry.get("parents"): rdeps[k]["parents"] = { - p for p, version in chunked(2, unnest(entry["parents"])) + p for p, version in chunked(2, unnest(entry["parents"])) } return rdeps @@ -762,21 +832,26 @@ class Environment: def is_satisfied(self, req): match = next( iter( - d for d in self.get_distributions() + d + for d in self.get_distributions() if canonicalize_name(d.project_name) == req.normalized_name - ), None + ), + None, ) if match is not None: if req.editable and req.line_instance.is_local and self.find_egg(match): requested_path = req.line_instance.path - return requested_path and vistir.compat.samefile(requested_path, match.location) + return requested_path and vistir.compat.samefile( + requested_path, match.location + ) elif match.has_metadata("direct_url.json"): direct_url_metadata = json.loads(match.get_metadata("direct_url.json")) commit_id = direct_url_metadata.get("vcs_info", {}).get("commit_id", "") vcs_type = direct_url_metadata.get("vcs_info", {}).get("vcs", "") _, pipfile_part = req.as_pipfile().popitem() return ( - vcs_type == req.vcs and commit_id == req.commit_hash + vcs_type == req.vcs + and commit_id == req.commit_hash and direct_url_metadata["url"] == pipfile_part[req.vcs] ) elif req.is_vcs or req.is_file_or_url: @@ -801,7 +876,13 @@ class Environment: c = None with self.activated(): script = vistir.cmdparse.Script.parse(cmd) - c = vistir.misc.run(script._parts, return_object=True, nospin=True, cwd=cwd, write_to_stdout=False) + c = vistir.misc.run( + script._parts, + return_object=True, + nospin=True, + cwd=cwd, + write_to_stdout=False, + ) return c def run_py(self, cmd, cwd=os.curdir): @@ -820,7 +901,13 @@ class Environment: else: script = vistir.cmdparse.Script.parse([self.python, "-c"] + list(cmd)) with self.activated(): - c = vistir.misc.run(script._parts, return_object=True, nospin=True, cwd=cwd, write_to_stdout=False) + c = vistir.misc.run( + script._parts, + return_object=True, + nospin=True, + cwd=cwd, + write_to_stdout=False, + ) return c def run_activate_this(self): @@ -865,30 +952,34 @@ class Environment: self.add_dist("pip") prefix = self.prefix.as_posix() with vistir.contextmanagers.temp_environ(), vistir.contextmanagers.temp_path(): - os.environ["PATH"] = os.pathsep.join([ - vistir.compat.fs_str(self.script_basedir), - vistir.compat.fs_str(self.prefix.as_posix()), - os.environ.get("PATH", "") - ]) + os.environ["PATH"] = os.pathsep.join( + [ + vistir.compat.fs_str(self.script_basedir), + vistir.compat.fs_str(self.prefix.as_posix()), + os.environ.get("PATH", ""), + ] + ) os.environ["PYTHONIOENCODING"] = vistir.compat.fs_str("utf-8") os.environ["PYTHONDONTWRITEBYTECODE"] = vistir.compat.fs_str("1") if self.is_venv: os.environ["PYTHONPATH"] = self.base_paths["PYTHONPATH"] os.environ["VIRTUAL_ENV"] = vistir.compat.fs_str(prefix) else: - if not self.project.s.PIPENV_USE_SYSTEM and not os.environ.get("VIRTUAL_ENV"): + if not self.project.s.PIPENV_USE_SYSTEM and not os.environ.get( + "VIRTUAL_ENV" + ): os.environ["PYTHONPATH"] = self.base_paths["PYTHONPATH"] os.environ.pop("PYTHONHOME", None) sys.path = self.sys_path sys.prefix = self.sys_prefix site.addsitedir(self.base_paths["purelib"]) - pip = self.safe_import("pip") # noqa + pip = self.safe_import("pip") # noqa pip_vendor = self.safe_import("pip._vendor") pep517_dir = os.path.join(os.path.dirname(pip_vendor.__file__), "pep517") site.addsitedir(pep517_dir) - os.environ["PYTHONPATH"] = os.pathsep.join([ - os.environ.get("PYTHONPATH", self.base_paths["PYTHONPATH"]), pep517_dir - ]) + os.environ["PYTHONPATH"] = os.pathsep.join( + [os.environ.get("PYTHONPATH", self.base_paths["PYTHONPATH"]), pep517_dir] + ) if include_extras: site.addsitedir(parent_path) sys.path.extend([parent_path, patched_dir, vendor_dir]) @@ -905,6 +996,7 @@ class Environment: @cached_property def finders(self): from pipenv.vendor.pythonfinder import Finder + finders = [ Finder(path=self.base_paths["scripts"], global_search=gs, system=False) for gs in (False, True) @@ -929,14 +1021,18 @@ class Environment: install_arg = "install" if not editable else "develop" install_keys = ["headers", "purelib", "platlib", "scripts", "data"] install_args = [ - self.environment.python, "-u", "-c", SETUPTOOLS_SHIM % setup_path, - install_arg, "--single-version-externally-managed", "--no-deps", - "--prefix={}".format(self.base_paths["prefix"]), "--no-warn-script-location" + self.environment.python, + "-u", + "-c", + SETUPTOOLS_SHIM % setup_path, + install_arg, + "--single-version-externally-managed", + "--no-deps", + "--prefix={}".format(self.base_paths["prefix"]), + "--no-warn-script-location", ] for key in install_keys: - install_args.append( - f"--install-{key}={self.base_paths[key]}" - ) + install_args.append(f"--install-{key}={self.base_paths[key]}") return install_args def install(self, requirements): @@ -944,28 +1040,33 @@ class Environment: requirements = [requirements] with self.get_finder() as finder: args = [] - for format_control in ('no_binary', 'only_binary'): + for format_control in ("no_binary", "only_binary"): formats = getattr(finder.format_control, format_control) - args.extend(('--' + format_control.replace('_', '-'), - ','.join(sorted(formats or {':none:'})))) + args.extend( + ( + "--" + format_control.replace("_", "-"), + ",".join(sorted(formats or {":none:"})), + ) + ) if finder.index_urls: - args.extend(['-i', finder.index_urls[0]]) + args.extend(["-i", finder.index_urls[0]]) for extra_index in finder.index_urls[1:]: - args.extend(['--extra-index-url', extra_index]) + args.extend(["--extra-index-url", extra_index]) else: - args.append('--no-index') + args.append("--no-index") for link in finder.find_links: - args.extend(['--find-links', link]) + args.extend(["--find-links", link]) for _, host, _ in finder.secure_origins: - args.extend(['--trusted-host', host]) + args.extend(["--trusted-host", host]) if finder.allow_all_prereleases: - args.append('--pre') + args.append("--pre") if finder.process_dependency_links: - args.append('--process-dependency-links') - args.append('--') + args.append("--process-dependency-links") + args.append("--") args.extend(requirements) - out, _ = vistir.misc.run(args, return_object=False, nospin=True, block=True, - combine_stderr=False) + out, _ = vistir.misc.run( + args, return_object=False, nospin=True, block=True, combine_stderr=False + ) @contextlib.contextmanager def uninstall(self, pkgname, *args, **kwargs): @@ -983,18 +1084,21 @@ class Environment: auto_confirm = kwargs.pop("auto_confirm", True) verbose = kwargs.pop("verbose", False) with self.activated(): - monkey_patch = next(iter( - dist for dist in self.base_working_set - if dist.project_name == "recursive-monkey-patch" - ), None) + monkey_patch = next( + iter( + dist + for dist in self.base_working_set + if dist.project_name == "recursive-monkey-patch" + ), + None, + ) if monkey_patch: monkey_patch.activate() pip_shims = self.safe_import("pip_shims") pathset_base = pip_shims.UninstallPathSet pathset_base._permitted = PatchedUninstaller._permitted dist = next( - iter(d for d in self.get_working_set() if d.project_name == pkgname), - None + iter(d for d in self.get_working_set() if d.project_name == pkgname), None ) pathset = pathset_base.from_dist(dist) if pathset is not None: diff --git a/pipenv/environments.py b/pipenv/environments.py index e4e5e461..41a35659 100644 --- a/pipenv/environments.py +++ b/pipenv/environments.py @@ -10,7 +10,6 @@ from vistir.path import normalize_drive from pipenv._compat import fix_utf8 from pipenv.vendor.vistir.misc import _isatty, fs_str - # HACK: avoid resolver.py uses the wrong byte code files. # I hope I can remove this one day. os.environ["PYTHONDONTWRITEBYTECODE"] = fs_str("1") @@ -36,8 +35,7 @@ def env_to_bool(val): def _is_env_truthy(name): - """An environment variable is truthy if it exists and isn't one of (0, false, no, off) - """ + """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 @@ -86,8 +84,8 @@ def normalize_pipfile_path(p): except OSError: loc = loc.absolute() # Recase the path properly on Windows. From https://stackoverflow.com/a/35229734/5043728 - if os.name == 'nt': - matches = glob.glob(re.sub(r'([^:/\\])(?=[/\\]|$)', r'[\1]', str(loc))) + if os.name == "nt": + matches = glob.glob(re.sub(r"([^:/\\])(?=[/\\]|$)", r"[\1]", str(loc))) path_str = matches and matches[0] or str(loc) else: path_str = str(loc) @@ -99,7 +97,7 @@ def normalize_pipfile_path(p): 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_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. @@ -125,14 +123,18 @@ class Setting: def initialize(self): - self.PIPENV_CACHE_DIR = os.environ.get("PIPENV_CACHE_DIR", user_cache_dir("pipenv")) + self.PIPENV_CACHE_DIR = os.environ.get( + "PIPENV_CACHE_DIR", user_cache_dir("pipenv") + ) """Location for Pipenv to store it's package cache. Default is to use appdir's user cache directory. """ # 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 = os.environ.get( + "PIPENV_DEFAULT_PYTHON_VERSION" + ) """Use this Python version when creating new virtual environments by default. This can be set to a version string, e.g. ``3.9``, or a path. Default is to use @@ -180,7 +182,9 @@ class Setting: and enables the user to use any user-built environments with Pipenv. """ - self.PIPENV_INSTALL_TIMEOUT = int(os.environ.get("PIPENV_INSTALL_TIMEOUT", 60 * 15)) + self.PIPENV_INSTALL_TIMEOUT = int( + os.environ.get("PIPENV_INSTALL_TIMEOUT", 60 * 15) + ) """Max number of seconds to wait for package installation. Defaults to 900 (15 minutes), a very long arbitrary time. @@ -248,7 +252,7 @@ class Setting: pipenv_pipfile = normalize_pipfile_path(pipenv_pipfile) # Overwrite environment variable so that subprocesses can get the correct path. # See https://github.com/pypa/pipenv/issues/3584 - os.environ['PIPENV_PIPFILE'] = pipenv_pipfile + os.environ["PIPENV_PIPFILE"] = pipenv_pipfile self.PIPENV_PIPFILE = pipenv_pipfile """If set, this specifies a custom Pipfile location. @@ -332,10 +336,9 @@ class Setting: 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 = os.environ.get( + "PIPENV_RESOLVE_VCS" + ) is None or _is_env_truthy("PIPENV_RESOLVE_VCS") """Tells Pipenv whether to resolve all VCS dependencies in full. @@ -344,9 +347,7 @@ class Setting: approach, you may set this to '0', 'off', or 'false'. """ - self.PIPENV_PYUP_API_KEY = os.environ.get( - "PIPENV_PYUP_API_KEY", None - ) + self.PIPENV_PYUP_API_KEY = os.environ.get("PIPENV_PYUP_API_KEY", None) # Internal, support running in a different Python from sys.executable. self.PIPENV_PYTHON = os.environ.get("PIPENV_PYTHON") @@ -396,12 +397,12 @@ class Setting: def is_using_venv(): # type: () -> bool """Check for venv-based virtual environment which sets sys.base_prefix""" - if getattr(sys, 'real_prefix', None) is not None: + if getattr(sys, "real_prefix", None) is not None: # virtualenv venvs result = True else: # PEP 405 venvs - result = sys.prefix != getattr(sys, 'base_prefix', sys.prefix) + result = sys.prefix != getattr(sys, "base_prefix", sys.prefix) return result diff --git a/pipenv/exceptions.py b/pipenv/exceptions.py index 33b86e5f..88aa9712 100644 --- a/pipenv/exceptions.py +++ b/pipenv/exceptions.py @@ -1,30 +1,29 @@ import itertools import re import sys - from collections import namedtuple from traceback import format_tb from pipenv import environments from pipenv._compat import decode_for_output from pipenv.patched import crayons -from pipenv.vendor.click.exceptions import ( - ClickException, FileError, UsageError -) -from pipenv.vendor.vistir.misc import echo as click_echo from pipenv.vendor import vistir +from pipenv.vendor.click.exceptions import ClickException, FileError, UsageError +from pipenv.vendor.vistir.misc import echo as click_echo ANSI_REMOVAL_RE = re.compile(r"\033\[((?:\d|;)*)([a-zA-Z])", re.MULTILINE) STRING_TYPES = ((str,), crayons.ColoredString) if sys.version_info[:2] >= (3, 7): KnownException = namedtuple( - 'KnownException', ['exception_name', 'match_string', 'show_from_string', 'prefix'], - defaults=[None, None, None, ""] + "KnownException", + ["exception_name", "match_string", "show_from_string", "prefix"], + defaults=[None, None, None, ""], ) else: KnownException = namedtuple( - 'KnownException', ['exception_name', 'match_string', 'show_from_string', 'prefix'], + "KnownException", + ["exception_name", "match_string", "show_from_string", "prefix"], ) KnownException.__new__.__defaults__ = (None, None, None, "") @@ -33,8 +32,8 @@ KNOWN_EXCEPTIONS = [ KnownException( "VirtualenvCreationException", match_string="do_create_virtualenv", - show_from_string=None - ) + show_from_string=None, + ), ] @@ -51,9 +50,7 @@ def handle_exception(exc_type, exception, traceback, hook=sys.excepthook): line = f" {line}" else: line = f" {line}" - line = "[{!s}]: {}".format( - exception.__class__.__name__, line - ) + line = "[{!s}]: {}".format(exception.__class__.__name__, line) formatted_lines.append(line) # use new exception prettification rules to format exceptions according to # UX rules @@ -102,20 +99,27 @@ class PipenvCmdError(PipenvException): def show(self, file=None): if file is None: file = vistir.misc.get_text_stderr() - click_echo("{} {}".format( - crayons.red("Error running command: "), - crayons.normal(decode_for_output(f"$ {self.cmd}", file), bold=True) - ), err=True) + click_echo( + "{} {}".format( + crayons.red("Error running command: "), + crayons.normal(decode_for_output(f"$ {self.cmd}", file), bold=True), + ), + err=True, + ) if self.out: - click_echo("{} {}".format( - crayons.normal("OUTPUT: "), - decode_for_output(self.out, file) - ), err=True) + click_echo( + "{} {}".format( + crayons.normal("OUTPUT: "), decode_for_output(self.out, file) + ), + err=True, + ) if self.err: - click_echo("{} {}".format( - crayons.normal("STDERR: "), - decode_for_output(self.err, file) - ), err=True) + click_echo( + "{} {}".format( + crayons.normal("STDERR: "), decode_for_output(self.err, file) + ), + err=True, + ) class JSONParseError(PipenvException): @@ -128,18 +132,20 @@ class JSONParseError(PipenvException): file = vistir.misc.get_text_stderr() message = "{}\n{}".format( crayons.normal("Failed parsing JSON results:", bold=True), - decode_for_output(self.message.strip(), file) + decode_for_output(self.message.strip(), file), ) click_echo(message, err=True) if self.error_text: - click_echo("{} {}".format( - crayons.normal("ERROR TEXT:", bold=True), - decode_for_output(self.error_text, file) - ), err=True) + click_echo( + "{} {}".format( + crayons.normal("ERROR TEXT:", bold=True), + decode_for_output(self.error_text, file), + ), + err=True, + ) class PipenvUsageError(UsageError): - def __init__(self, message=None, ctx=None, **kwargs): formatted_message = "{0}: {1}" msg_prefix = crayons.red("ERROR:", bold=True) @@ -164,29 +170,30 @@ class PipenvUsageError(UsageError): if color: extra = getattr(crayons, color, "blue")(extra) click_echo(decode_for_output(extra, file), file=file) - hint = '' + hint = "" if self.cmd is not None and self.cmd.get_help_option(self.ctx) is not None: - hint = ('Try "%s %s" for help.\n' - % (self.ctx.command_path, self.ctx.help_option_names[0])) + hint = 'Try "%s %s" for help.\n' % ( + self.ctx.command_path, + self.ctx.help_option_names[0], + ) if self.ctx is not None: - click_echo(self.ctx.get_usage() + '\n%s' % hint, file=file, color=color) + click_echo(self.ctx.get_usage() + "\n%s" % hint, file=file, color=color) click_echo(self.message, file=file) class PipenvFileError(FileError): - formatted_message = "{0} {{0}} {{1}}".format( - crayons.red("ERROR:", bold=True) - ) + formatted_message = "{0} {{0}} {{1}}".format(crayons.red("ERROR:", bold=True)) def __init__(self, filename, message=None, **kwargs): extra = kwargs.pop("extra", []) if not message: message = crayons.normal("Please ensure that the file exists!", bold=True) message = self.formatted_message.format( - crayons.normal(f"{filename} not found!", bold=True), - message + crayons.normal(f"{filename} not found!", bold=True), message + ) + FileError.__init__( + self, filename=filename, hint=decode_for_output(message), **kwargs ) - FileError.__init__(self, filename=filename, hint=decode_for_output(message), **kwargs) self.extra = extra def show(self, file=None): @@ -203,14 +210,13 @@ class PipenvFileError(FileError): class PipfileNotFound(PipenvFileError): def __init__(self, filename="Pipfile", extra=None, **kwargs): extra = kwargs.pop("extra", []) - message = ( - "{} {}".format( - crayons.red("Aborting!", bold=True), - crayons.normal( - "Please ensure that the file exists and is located in your" - " project root directory.", bold=True - ) - ) + message = "{} {}".format( + crayons.red("Aborting!", bold=True), + crayons.normal( + "Please ensure that the file exists and is located in your" + " project root directory.", + bold=True, + ), ) super().__init__(filename, message=message, extra=extra, **kwargs) @@ -221,7 +227,7 @@ class LockfileNotFound(PipenvFileError): message = "{} {} {}".format( crayons.normal("You need to run", bold=True), crayons.red("$ pipenv lock", bold=True), - crayons.normal("before you can continue.", bold=True) + crayons.normal("before you can continue.", bold=True), ) super().__init__(filename, message=message, extra=extra, **kwargs) @@ -264,7 +270,6 @@ class SetupException(PipenvException): class VirtualenvException(PipenvException): - def __init__(self, message=None, **kwargs): if not message: message = ( @@ -309,7 +314,7 @@ class UninstallError(PipenvException): extra = [ "{} {}".format( crayons.cyan("Attempted to run command: "), - crayons.yellow(f"$ {command!r}", bold=True) + crayons.yellow(f"$ {command!r}", bold=True), ) ] extra.extend([crayons.cyan(line.strip()) for line in return_values.splitlines()]) @@ -317,7 +322,7 @@ class UninstallError(PipenvException): package = " ".join(package) message = "{!s} {!s}...".format( crayons.normal("Failed to uninstall package(s)"), - crayons.yellow(f"{package}!s", bold=True) + crayons.yellow(f"{package}!s", bold=True), ) self.exit_code = return_code PipenvException.__init__(self, message=message, extra=extra) @@ -332,8 +337,7 @@ class InstallError(PipenvException): crayons.normal(f"{package!s}", bold=True) ) message = "{} {}".format( - f"{package_message}", - crayons.yellow("Package installation failed...") + f"{package_message}", crayons.yellow("Package installation failed...") ) extra = kwargs.pop("extra", []) PipenvException.__init__(self, message=message, extra=extra, **kwargs) @@ -344,17 +348,21 @@ class CacheError(PipenvException): message = "{} {}\n{}".format( crayons.cyan("Corrupt cache file"), crayons.normal(f"{path!s}"), - crayons.normal('Consider trying "pipenv lock --clear" to clear the cache.') + crayons.normal('Consider trying "pipenv lock --clear" to clear the cache.'), ) PipenvException.__init__(self, message=message) class DependencyConflict(PipenvException): def __init__(self, message): - extra = ["{} {}".format( - crayons.red("The operation failed...", bold=True), - crayons.red("A dependency conflict was detected and could not be resolved."), - )] + extra = [ + "{} {}".format( + crayons.red("The operation failed...", bold=True), + crayons.red( + "A dependency conflict was detected and could not be resolved." + ), + ) + ] PipenvException.__init__(self, message, extra=extra) @@ -382,21 +390,28 @@ class ResolutionFailure(PipenvException): crayons.cyan( "Please check your version specifier and version number. " "See PEP440 for more information." - ) + ), ) PipenvException.__init__(self, message, extra=extra) class RequirementError(PipenvException): - def __init__(self, req=None): from .utils import VCS_LIST - keys = ("name", "path",) + VCS_LIST + ("line", "uri", "url", "relpath") + + keys = ( + ( + "name", + "path", + ) + + VCS_LIST + + ("line", "uri", "url", "relpath") + ) if req is not None: possible_display_values = [getattr(req, value, None) for value in keys] - req_value = next(iter( - val for val in possible_display_values if val is not None - ), None) + req_value = next( + iter(val for val in possible_display_values if val is not None), None + ) if not req_value: getstate_fn = getattr(req, "__getstate__", None) slots = getattr(req, "__slots__", None) @@ -405,22 +420,17 @@ class RequirementError(PipenvException): req_value = getstate_fn() elif slots: slot_vals = [ - (k, getattr(req, k, None)) for k in slots - if getattr(req, k, None) + (k, getattr(req, k, None)) for k in slots if getattr(req, k, None) ] - req_value = "\n".join([ - f" {k}: {v}" for k, v in slot_vals - ]) + req_value = "\n".join([f" {k}: {v}" for k, v in slot_vals]) elif keys_fn: values = [(k, req.get(k)) for k in keys_fn() if req.get(k)] - req_value = "\n".join([ - f" {k}: {v}" for k, v in values - ]) + req_value = "\n".join([f" {k}: {v}" for k, v in values]) else: req_value = getattr(req.line_instance, "line", None) message = "{} {}".format( crayons.normal(decode_for_output("Failed creating requirement instance")), - crayons.normal(decode_for_output(f"{req_value!r}")) + crayons.normal(decode_for_output(f"{req_value!r}")), ) extra = [str(req)] PipenvException.__init__(self, message, extra=extra) @@ -432,7 +442,9 @@ def prettify_exc(error): errors = [] for exc in KNOWN_EXCEPTIONS: search_string = exc.match_string if exc.match_string else exc.exception_name - split_string = exc.show_from_string if exc.show_from_string else exc.exception_name + split_string = ( + exc.show_from_string if exc.show_from_string else exc.exception_name + ) if search_string in error: # for known exceptions with no display rules and no prefix # we should simply show nothing diff --git a/pipenv/help.py b/pipenv/help.py index 2882bca1..f6722f94 100644 --- a/pipenv/help.py +++ b/pipenv/help.py @@ -3,7 +3,6 @@ import pprint import sys import pipenv - from pipenv.pep508checker import lookup from pipenv.vendor import pythonfinder @@ -69,9 +68,7 @@ def get_pipenv_diagnostics(project): print("") if project.lockfile_exists: print("") - print_utf( - f"Contents of `Pipfile.lock` ({project.lockfile_location!r}):" - ) + print_utf(f"Contents of `Pipfile.lock` ({project.lockfile_location!r}):") print("") print("```json") with open(project.lockfile_location) as f: @@ -82,4 +79,5 @@ def get_pipenv_diagnostics(project): if __name__ == "__main__": from pipenv.project import Project + get_pipenv_diagnostics(Project()) diff --git a/pipenv/installers.py b/pipenv/installers.py index 5928b198..2e49da96 100644 --- a/pipenv/installers.py +++ b/pipenv/installers.py @@ -1,12 +1,12 @@ -import os import operator +import os import re import sys from abc import ABCMeta, abstractmethod -from pipenv.vendor import attr from pipenv.utils.processes import subprocess_run from pipenv.utils.shell import find_windows_executable +from pipenv.vendor import attr @attr.s @@ -20,15 +20,14 @@ class Version: parts = [self.major, self.minor] if self.patch is not None: parts.append(self.patch) - return '.'.join(str(p) for p in parts) + return ".".join(str(p) for p in parts) @classmethod def parse(cls, name): - """Parse an X.Y.Z or X.Y string into a version tuple. - """ - match = re.match(r'^(\d+)\.(\d+)(?:\.(\d+))?$', name) + """Parse an X.Y.Z or X.Y string into a version tuple.""" + match = re.match(r"^(\d+)\.(\d+)(?:\.(\d+))?$", name) if not match: - raise ValueError(f'invalid version name {name!r}') + raise ValueError(f"invalid version name {name!r}") major = int(match.group(1)) minor = int(match.group(2)) patch = match.group(3) @@ -47,8 +46,7 @@ class Version: return (self.major, self.minor, self.patch or 0) def matches_minor(self, other): - """Check whether this version matches the other in (major, minor). - """ + """Check whether this version matches the other in (major, minor).""" return (self.major, self.minor) == (other.major, other.minor) @@ -64,7 +62,6 @@ class InstallerError(RuntimeError): class Installer(metaclass=ABCMeta): - def __init__(self, project): self.cmd = self._find_installer() self.project = project @@ -103,32 +100,37 @@ class Installer(metaclass=ABCMeta): # Look for the Python installer using the equivalent of 'which'. On # Homebrew-installed systems, the env var may not be set, but this # strategy will work. - find_windows_executable('', name), + find_windows_executable("", name), # Check for explicitly set install locations (e.g. PYENV_ROOT, ASDF_DIR). - os.path.join(os.path.expanduser(os.getenv(env_var, '/dev/null')), 'bin', name), + os.path.join( + os.path.expanduser(os.getenv(env_var, "/dev/null")), "bin", name + ), # Check the pyenv/asdf-recommended from-source install locations - os.path.join(os.path.expanduser(f'~/.{name}'), 'bin', name), + os.path.join(os.path.expanduser(f"~/.{name}"), "bin", name), ): - if candidate is not None and os.path.isfile(candidate) and os.access(candidate, os.X_OK): + if ( + candidate is not None + and os.path.isfile(candidate) + and os.access(candidate, os.X_OK) + ): return candidate raise InstallerNotFound() def _run(self, *args, **kwargs): - timeout = kwargs.pop('timeout', 30) - shell = kwargs.pop('shell', False) + timeout = kwargs.pop("timeout", 30) + shell = kwargs.pop("shell", False) if kwargs: k = list(kwargs.keys())[0] - raise TypeError(f'unexpected keyword argument {k!r}') + raise TypeError(f"unexpected keyword argument {k!r}") args = (self.cmd,) + tuple(args) c = subprocess_run(args, timeout=timeout, shell=shell) if c.returncode != 0: - raise InstallerError(f'failed to run {args}', c) + raise InstallerError(f"failed to run {args}", c) return c @abstractmethod def iter_installable_versions(self): - """Iterate through CPython versions available for Pipenv to install. - """ + """Iterate through CPython versions available for Pipenv to install.""" pass def find_version_to_install(self, name): @@ -140,14 +142,17 @@ class Installer(metaclass=ABCMeta): if version.patch is not None: return name try: - best_match = max(( - inst_version - for inst_version in self.iter_installable_versions() - if inst_version.matches_minor(version) - ), key=operator.attrgetter('cmpkey')) + best_match = max( + ( + inst_version + for inst_version in self.iter_installable_versions() + if inst_version.matches_minor(version) + ), + key=operator.attrgetter("cmpkey"), + ) except ValueError: raise ValueError( - f'no installable version found for {name!r}', + f"no installable version found for {name!r}", ) return best_match @@ -168,17 +173,16 @@ class Pyenv(Installer): WIN = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") def _find_installer(self): - return self._find_python_installer_by_name_and_env('pyenv', 'PYENV_ROOT') + return self._find_python_installer_by_name_and_env("pyenv", "PYENV_ROOT") def _run(self, *args, **kwargs): if Pyenv.WIN: - kwargs['shell'] = True + kwargs["shell"] = True return super(Pyenv, self)._run(*args, **kwargs) def iter_installable_versions(self): - """Iterate through CPython versions available for Pipenv to install. - """ - for name in self._run('install', '--list').stdout.splitlines(): + """Iterate through CPython versions available for Pipenv to install.""" + for name in self._run("install", "--list").stdout.splitlines(): try: version = Version.parse(name.strip()) except ValueError: @@ -192,7 +196,7 @@ class Pyenv(Installer): A ValueError is raised if the given version does not have a match in pyenv. A InstallerError is raised if the pyenv command fails. """ - args = ['install', '-s', str(version)] + args = ["install", "-s", str(version)] if Pyenv.WIN: # pyenv-win skips installed versions by default and does not support -s del args[1] @@ -200,14 +204,12 @@ class Pyenv(Installer): class Asdf(Installer): - def _find_installer(self): - return self._find_python_installer_by_name_and_env('asdf', 'ASDF_DIR') + return self._find_python_installer_by_name_and_env("asdf", "ASDF_DIR") def iter_installable_versions(self): - """Iterate through CPython versions available for asdf to install. - """ - for name in self._run('list-all', 'python').stdout.splitlines(): + """Iterate through CPython versions available for asdf to install.""" + for name in self._run("list-all", "python").stdout.splitlines(): try: version = Version.parse(name.strip()) except ValueError: @@ -222,7 +224,9 @@ class Asdf(Installer): asdf. A InstallerError is raised if the asdf command fails. """ c = self._run( - 'install', 'python', str(version), + "install", + "python", + str(version), timeout=self.project.s.PIPENV_INSTALL_TIMEOUT, ) return c diff --git a/pipenv/pipenv.1 b/pipenv/pipenv.1 index fe4f266b..b69adb03 100644 --- a/pipenv/pipenv.1 +++ b/pipenv/pipenv.1 @@ -1058,28 +1058,28 @@ Update vendored dependencies and invocations .INDENT 2.0 .IP \(bu 2 Update vendored and patched dependencies -\- Update patches on \fBpiptools\fP, \fBpip\fP, \fBpip\-shims\fP, +\- Update patches on \fBpiptools\fP, \fBpip\fP, \fBpip\-shims\fP, .nf \(ga\(ga .fi tomlkit\(ga .IP \(bu 2 Fix invocations of dependencies -\- Fix custom +\- Fix custom .nf \(ga\(ga .fi InstallCommand\(ga instantiation -\- Update +\- Update .nf \(ga\(ga .fi PackageFinder\(ga usage -\- Fix +\- Fix .nf \(ga\(ga .fi -Bool\(ga stringify attempts from +Bool\(ga stringify attempts from .nf \(ga\(ga .fi diff --git a/pipenv/process.py b/pipenv/process.py index a7888e66..58766459 100644 --- a/pipenv/process.py +++ b/pipenv/process.py @@ -11,18 +11,28 @@ class PopenProcess: """A wrapper of subprocess.Popen that doesn't need to worry about the Pipe buffer exceeding the limit. """ + def __init__( - self, args, *, block=True, encoding=DEFAULT_ENCODING, env=None, timeout=None, **other_kwargs + self, + args, + *, + block=True, + encoding=DEFAULT_ENCODING, + env=None, + timeout=None, + **other_kwargs ): self.blocking = block self.env = env self.script = Script.parse(args) if env is not None: env = dict(os.environ, **env) - other_kwargs['env'] = env - other_kwargs['stdout'] = subprocess.PIPE - other_kwargs['stderr'] = subprocess.PIPE - self._process = subprocess.Popen(args, universal_newlines=True, encoding=encoding, **other_kwargs) + other_kwargs["env"] = env + other_kwargs["stdout"] = subprocess.PIPE + other_kwargs["stderr"] = subprocess.PIPE + self._process = subprocess.Popen( + args, universal_newlines=True, encoding=encoding, **other_kwargs + ) self._endtime = None if timeout is not None: self._endtime = _time() + timeout diff --git a/pipenv/progress.py b/pipenv/progress.py index 04df813e..344f7e9d 100644 --- a/pipenv/progress.py +++ b/pipenv/progress.py @@ -15,7 +15,6 @@ import crayons from pipenv.environments import PIPENV_COLORBLIND, PIPENV_HIDE_EMOJIS - STREAM = sys.stderr MILL_TEMPLATE = "%s %s %i/%i\r" DOTS_CHAR = "." diff --git a/pipenv/project.py b/pipenv/project.py index a63306ff..d9732652 100644 --- a/pipenv/project.py +++ b/pipenv/project.py @@ -6,10 +6,10 @@ import io import json import operator import os -from pathlib import Path import re import sys import urllib.parse +from pathlib import Path import pipfile import pipfile.api @@ -20,11 +20,21 @@ import vistir from pipenv.cmdparse import Script from pipenv.core import system_which from pipenv.environment import Environment -from pipenv.environments import Setting, is_type_checking, is_in_virtualenv, normalize_pipfile_path -from pipenv.utils.dependencies import get_canonical_names, is_editable, is_installable_file, is_star, python_version +from pipenv.environments import ( + Setting, + is_in_virtualenv, + is_type_checking, + normalize_pipfile_path, +) +from pipenv.utils.dependencies import ( + get_canonical_names, + is_editable, + is_installable_file, + is_star, + python_version, +) from pipenv.utils.internet import get_url_name, is_valid_url, proper_case from pipenv.utils.resolver import pep423_name -from pipenv.utils.toml import cleanup_toml, convert_toml_outline_tables from pipenv.utils.shell import ( find_requirements, find_windows_executable, @@ -32,19 +42,17 @@ from pipenv.utils.shell import ( get_workon_home, is_virtual_environment, looks_like_dir, - safe_expandvars + safe_expandvars, ) - +from pipenv.utils.toml import cleanup_toml, convert_toml_outline_tables from pipenv.vendor.cached_property import cached_property -from pipenv.vendor.requirementslib.models.utils import ( - get_default_pyproject_backend -) - +from pipenv.vendor.requirementslib.models.utils import get_default_pyproject_backend if is_type_checking(): from typing import Dict, List, Optional, Set, Text, Tuple, Union import pkg_resources + TSource = Dict[Text, Union[Text, bool]] TPackageEntry = Dict[str, Union[bool, str, List[str]]] TPackage = Dict[str, TPackageEntry] @@ -114,22 +122,20 @@ class Project: self._requirements_location = None self._original_dir = os.path.abspath(os.curdir) self._environment = None - self._build_system = { - "requires": ["setuptools", "wheel"] - } + self._build_system = {"requires": ["setuptools", "wheel"]} self.python_version = python_version self.s = Setting() if self.s.PIPENV_TEST_INDEX: self.default_source = { - u"url": self.s.PIPENV_TEST_INDEX, - u"verify_ssl": True, - u"name": u"custom", + "url": self.s.PIPENV_TEST_INDEX, + "verify_ssl": True, + "name": "custom", } else: self.default_source = { - u"url": u"https://pypi.org/simple", - u"verify_ssl": True, - u"name": u"pypi", + "url": "https://pypi.org/simple", + "verify_ssl": True, + "name": "pypi", } pipfile.api.DEFAULT_SOURCE = self.default_source @@ -151,6 +157,7 @@ class Project: def _build_package_list(self, package_section): """Returns a list of packages for pip-tools to consume.""" from pipenv.vendor.requirementslib.utils import is_vcs + ps = {} # TODO: Separate the logic for showing packages from the filters for supplying pip-tools for k, v in self.parsed_pipfile.get(package_section, {}).items(): @@ -217,9 +224,7 @@ class Project: def required_python_version(self): # type: () -> str if self.pipfile_exists: - required = self.parsed_pipfile.get("requires", {}).get( - "python_full_version" - ) + required = self.parsed_pipfile.get("requires", {}).get("python_full_version") if not required: required = self.parsed_pipfile.get("requires", {}).get("python_version") if required != "*": @@ -293,8 +298,10 @@ class Project: def working_set(self): # type: () -> pkg_resources.WorkingSet from pipenv.utils.shell import load_path + sys_path = load_path(self.which("python")) import pkg_resources + return pkg_resources.WorkingSet(sys_path) @property @@ -314,7 +321,7 @@ class Project: return { "dev": dev_keys, "default": default_keys, - "combined": dev_keys | default_keys + "combined": dev_keys | default_keys, } @property @@ -325,7 +332,7 @@ class Project: return { "dev": dev_keys, "default": default_keys, - "combined": dev_keys | default_keys + "combined": dev_keys | default_keys, } def get_environment(self, allow_global=False): @@ -339,8 +346,12 @@ class Project: python = None sources = self.sources if self.sources else [self.default_source] environment = Environment( - prefix=prefix, python=python, is_venv=is_venv, sources=sources, - pipfile=self.parsed_pipfile, project=self + prefix=prefix, + python=python, + is_venv=is_venv, + sources=sources, + pipfile=self.parsed_pipfile, + project=self, ) pipenv_dist = get_pipenv_dist(pkg="pipenv") if pipenv_dist: @@ -442,7 +453,8 @@ class Project: virtualenv_env = os.getenv("VIRTUAL_ENV") if ( "PIPENV_ACTIVE" not in os.environ - and not self.s.PIPENV_IGNORE_VIRTUALENVS and virtualenv_env + and not self.s.PIPENV_IGNORE_VIRTUALENVS + and virtualenv_env ): return virtualenv_env @@ -491,7 +503,7 @@ class Project: # type: (str) -> None """Registers a proper name to the database.""" with self.proper_names_db_path.open("a") as f: - f.write(u"{0}\n".format(name)) + f.write("{0}\n".format(name)) @property def pipfile_location(self): @@ -632,8 +644,8 @@ class Project: @property def _pipfile(self): - from .vendor.requirementslib.models.pipfile import \ - Pipfile as ReqLibPipfile + from .vendor.requirementslib.models.pipfile import Pipfile as ReqLibPipfile + pf = ReqLibPipfile.load(self.pipfile_location) return pf @@ -660,6 +672,7 @@ class Project: def _get_vcs_packages(self, dev=False): from pipenv.vendor.requirementslib.utils import is_vcs + section = "dev-packages" if dev else "packages" packages = { k: v @@ -727,15 +740,13 @@ class Project: source_name = "pip_index_{}".format(i) verify_ssl = index.startswith("https") - sources.append( - {u"url": index, u"verify_ssl": verify_ssl, u"name": source_name} - ) + sources.append({"url": index, "verify_ssl": verify_ssl, "name": source_name}) data = { - u"source": sources, + "source": sources, # Default packages. - u"packages": {}, - u"dev-packages": {}, + "packages": {}, + "dev-packages": {}, } # Default requires. required_python = python @@ -746,7 +757,7 @@ class Project: required_python = self.which("python") version = python_version(required_python) or self.s.PIPENV_DEFAULT_PYTHON_VERSION if version and len(version.split(".")) > 2: - data[u"requires"] = {"python_version": ".".join(version.split(".")[:2])} + data["requires"] = {"python_version": ".".join(version.split(".")[:2])} self.write_toml(data) @classmethod @@ -762,13 +773,15 @@ class Project: return source def get_or_create_lockfile(self, from_pipfile=False): - from pipenv.vendor.requirementslib.models.lockfile import \ - Lockfile as Req_Lockfile + from pipenv.vendor.requirementslib.models.lockfile import ( + Lockfile as Req_Lockfile, + ) + lockfile = None if from_pipfile and self.pipfile_exists: lockfile_dict = { "default": self._lockfile["default"].copy(), - "develop": self._lockfile["develop"].copy() + "develop": self._lockfile["develop"].copy(), } lockfile_dict.update({"_meta": self.get_lockfile_meta()}) lockfile = Req_Lockfile.from_data( @@ -778,9 +791,13 @@ class Project: try: lockfile = Req_Lockfile.load(self.lockfile_location) except OSError: - lockfile = Req_Lockfile.from_data(self.lockfile_location, self.lockfile_content) + lockfile = Req_Lockfile.from_data( + self.lockfile_location, self.lockfile_content + ) else: - lockfile = Req_Lockfile.from_data(path=self.lockfile_location, data=self._lockfile, meta_from_project=False) + lockfile = Req_Lockfile.from_data( + path=self.lockfile_location, data=self._lockfile, meta_from_project=False + ) if lockfile._lockfile is not None: return lockfile if self.lockfile_exists and self.lockfile_content: @@ -790,9 +807,7 @@ class Project: sources = self.pipfile_sources elif not isinstance(sources, list): sources = [sources] - lockfile_dict["_meta"]["sources"] = [ - self.populate_source(s) for s in sources - ] + lockfile_dict["_meta"]["sources"] = [self.populate_source(s) for s in sources] _created_lockfile = Req_Lockfile.from_data( path=self.lockfile_location, data=lockfile_dict, meta_from_project=False ) @@ -803,6 +818,7 @@ class Project: def get_lockfile_meta(self): from .vendor.plette.lockfiles import PIPFILE_SPEC_CURRENT + if self.lockfile_exists: sources = self.lockfile_content.get("_meta", {}).get("sources", []) elif "source" in self.parsed_pipfile: @@ -815,7 +831,7 @@ class Project: "hash": {"sha256": self.calculate_pipfile_hash()}, "pipfile-spec": PIPFILE_SPEC_CURRENT, "sources": [self.populate_source(s) for s in sources], - "requires": self.parsed_pipfile.get("requires", {}) + "requires": self.parsed_pipfile.get("requires", {}), } def write_toml(self, data, path=None): @@ -836,13 +852,12 @@ class Project: table.update(data[section][package]) document[section][package] = table else: - document[section][package] = tomlkit.string(data[section][package]) + document[section][package] = tomlkit.string( + data[section][package] + ) formatted_data = tomlkit.dumps(document).rstrip() - if ( - Path(path).absolute() - == Path(self.pipfile_location).absolute() - ): + if Path(path).absolute() == Path(self.pipfile_location).absolute(): newlines = self._pipfile_newlines else: newlines = DEFAULT_NEWLINES @@ -853,8 +868,7 @@ class Project: self.clear_pipfile_cache() def write_lockfile(self, content): - """Write out the lockfile. - """ + """Write out the lockfile.""" s = self._lockfile_encoder.encode(content) open_kwargs = {"newline": self._lockfile_newlines, "encoding": "utf-8"} with vistir.contextmanagers.atomic_open_for_write( @@ -863,8 +877,8 @@ class Project: f.write(s) # Write newline at end of document. GH-319. # Only need '\n' here; the file object handles the rest. - if not s.endswith(u"\n"): - f.write(u"\n") + if not s.endswith("\n"): + f.write("\n") @property def pipfile_sources(self): @@ -914,14 +928,18 @@ class Project: def find_source(sources, name=None, url=None): source = None if name: - source = next(iter( - s for s in sources if "name" in s and s["name"] == name - ), None) + source = next( + iter(s for s in sources if "name" in s and s["name"] == name), None + ) elif url: - source = next(iter( - s for s in sources - if "url" in s and is_url_equal(url, s.get("url", "")) - ), None) + source = next( + iter( + s + for s in sources + if "url" in s and is_url_equal(url, s.get("url", "")) + ), + None, + ) if source is not None: return source @@ -961,9 +979,9 @@ class Project: packages = set([pep423_name(pkg) for pkg in packages]) for section in ("dev-packages", "packages"): pipfile_section = parsed.get(section, {}) - pipfile_packages = set([ - pep423_name(pkg_name) for pkg_name in pipfile_section.keys() - ]) + pipfile_packages = set( + [pep423_name(pkg_name) for pkg_name in pipfile_section.keys()] + ) to_remove = packages & pipfile_packages # The normal toml parser can't handle deleting packages with preceding newlines is_dev = section == "dev-packages" @@ -995,9 +1013,7 @@ class Project: self.write_toml(p) def src_name_from_url(self, index_url): - name, _, tld_guess = urllib.parse.urlsplit(index_url).netloc.rpartition( - "." - ) + name, _, tld_guess = urllib.parse.urlsplit(index_url).netloc.rpartition(".") src_name = name.replace(".", "") try: self.get_source(name=src_name) @@ -1005,6 +1021,7 @@ class Project: name = src_name else: from random import randint + name = "{0}-{1}".format(src_name, randint(1, 1000)) return name @@ -1109,6 +1126,7 @@ class Project: @cached_property def finders(self): from .vendor.pythonfinder import Finder + scripts_dirname = "Scripts" if os.name == "nt" else "bin" scripts_dir = os.path.join(self.virtualenv_location, scripts_dirname) finders = [ diff --git a/pipenv/resolver.py b/pipenv/resolver.py index 4b4b9c0f..0b6ac3ec 100644 --- a/pipenv/resolver.py +++ b/pipenv/resolver.py @@ -3,12 +3,12 @@ import logging import os import sys - os.environ["PIP_PYTHON_PATH"] = str(sys.executable) def find_site_path(pkg, site_dir=None): import pkg_resources + if site_dir is None: site_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) working_set = pkg_resources.WorkingSet([site_dir] + sys.path[:]) @@ -17,7 +17,16 @@ def find_site_path(pkg, site_dir=None): base_name = dist.project_name if dist.project_name else dist.key name = None if "top_level.txt" in dist.metadata_listdir(""): - name = next(iter([line.strip() for line in dist.get_metadata_lines("top_level.txt") if line is not None]), None) + name = next( + iter( + [ + line.strip() + for line in dist.get_metadata_lines("top_level.txt") + if line is not None + ] + ), + None, + ) if name is None: name = pkg_resources.safe_name(base_name).replace("-", "_") if not any(pkg == _ for _ in [base_name, name]): @@ -26,15 +35,15 @@ def find_site_path(pkg, site_dir=None): path_options = [os.path.join(root, p) for p in path_options if p is not None] path = next(iter(p for p in path_options if os.path.exists(p)), None) if path is not None: - return (dist, path) - return (None, None) + return dist, path + return None, None def _patch_path(pipenv_site=None): import site + pipenv_libdir = os.path.dirname(os.path.abspath(__file__)) pipenv_site_dir = os.path.dirname(pipenv_libdir) - pipenv_dist = None if pipenv_site is not None: pipenv_dist, pipenv_path = find_site_path("pipenv", site_dir=pipenv_site) else: @@ -42,10 +51,16 @@ def _patch_path(pipenv_site=None): if pipenv_dist is not None: pipenv_dist.activate() else: - site.addsitedir(next(iter( - sitedir for sitedir in (pipenv_site, pipenv_site_dir) - if sitedir is not None - ), None)) + site.addsitedir( + next( + iter( + sitedir + for sitedir in (pipenv_site, pipenv_site_dir) + if sitedir is not None + ), + None, + ) + ) if pipenv_path is not None: pipenv_libdir = pipenv_path for _dir in ("vendor", "patched", pipenv_libdir): @@ -54,6 +69,7 @@ def _patch_path(pipenv_site=None): def get_parser(): from argparse import ArgumentParser + parser = ArgumentParser("pipenv-resolver") parser.add_argument("--pre", action="store_true", default=False) parser.add_argument("--clear", action="store_true", default=False) @@ -62,12 +78,24 @@ def get_parser(): parser.add_argument("--debug", action="store_true", default=False) parser.add_argument("--system", action="store_true", default=False) parser.add_argument("--parse-only", action="store_true", default=False) - parser.add_argument("--pipenv-site", metavar="pipenv_site_dir", action="store", - default=os.environ.get("PIPENV_SITE_DIR")) - parser.add_argument("--requirements-dir", metavar="requirements_dir", action="store", - default=os.environ.get("PIPENV_REQ_DIR")) - parser.add_argument("--write", metavar="write", action="store", - default=os.environ.get("PIPENV_RESOLVER_FILE")) + parser.add_argument( + "--pipenv-site", + metavar="pipenv_site_dir", + action="store", + default=os.environ.get("PIPENV_SITE_DIR"), + ) + parser.add_argument( + "--requirements-dir", + metavar="requirements_dir", + action="store", + default=os.environ.get("PIPENV_REQ_DIR"), + ) + parser.add_argument( + "--write", + metavar="write", + action="store", + default=os.environ.get("PIPENV_RESOLVER_FILE"), + ) parser.add_argument("packages", nargs="*") return parser @@ -103,6 +131,7 @@ class Entry: def __init__(self, name, entry_dict, project, resolver, reverse_deps=None, dev=False): super().__init__() from pipenv.vendor.requirementslib.models.utils import tomlkit_value_to_python + self.name = name if isinstance(entry_dict, dict): self.entry_dict = self.clean_initial_dict(entry_dict) @@ -137,6 +166,7 @@ class Entry: @staticmethod def make_requirement(name=None, entry=None, from_ireq=False): from pipenv.vendor.requirementslib.models.requirements import Requirement + if from_ireq: return Requirement.from_ireq(entry) return Requirement.from_pipfile(name, entry) @@ -152,12 +182,11 @@ class Entry: @classmethod def parse_pyparsing_exprs(cls, expr_iterable): from pipenv.vendor.pyparsing import Literal, MatchFirst + keys = [] expr_list = [] expr = expr_iterable.copy() - if isinstance(expr, Literal) or ( - expr.__class__.__name__ == Literal.__name__ - ): + if isinstance(expr, Literal) or (expr.__class__.__name__ == Literal.__name__): keys.append(expr.match) elif isinstance(expr, MatchFirst) or ( expr.__class__.__name__ == MatchFirst.__name__ @@ -174,13 +203,11 @@ class Entry: def get_markers_from_dict(cls, entry_dict): from pipenv.vendor.packaging import markers as packaging_markers from pipenv.vendor.requirementslib.models.markers import normalize_marker_str + marker_keys = cls.parse_pyparsing_exprs(packaging_markers.VARIABLE) markers = set() keys_in_dict = [k for k in marker_keys if k in entry_dict] - markers = { - normalize_marker_str(f"{k} {entry_dict.pop(k)}") - for k in keys_in_dict - } + markers = {normalize_marker_str(f"{k} {entry_dict.pop(k)}") for k in keys_in_dict} if "markers" in entry_dict: markers.add(normalize_marker_str(entry_dict["markers"])) if None in markers: @@ -209,9 +236,7 @@ class Entry: @property def original_markers(self): - original_markers, lockfile_dict = self.get_markers_from_dict( - self.lockfile_dict - ) + original_markers, lockfile_dict = self.get_markers_from_dict(self.lockfile_dict) self.lockfile_dict = lockfile_dict self._original_markers = self.marker_to_str(original_markers) return self._original_markers @@ -219,9 +244,11 @@ class Entry: @staticmethod def marker_to_str(marker): from pipenv.vendor.requirementslib.models.markers import normalize_marker_str + if not marker: return None from pipenv.vendor.vistir.compat import Mapping + marker_str = None if isinstance(marker, Mapping): marker_dict, _ = Entry.get_markers_from_dict(marker) @@ -274,7 +301,9 @@ class Entry: @property def pipfile_entry(self): if self._pipfile_entry is None: - self._pipfile_entry = self.make_requirement(self.pipfile_name, self.pipfile_dict) + self._pipfile_entry = self.make_requirement( + self.pipfile_name, self.pipfile_dict + ) return self._pipfile_entry @property @@ -300,8 +329,9 @@ class Entry: return self.project.pipfile_package_names["dev" if self.dev else "default"] def create_parent(self, name, specifier="*"): - parent = self.create(name, specifier, self.project, self.resolver, - self.reverse_deps, self.dev) + parent = self.create( + name, specifier, self.project, self.resolver, self.reverse_deps, self.dev + ) parent._deptree = self.deptree return parent @@ -318,6 +348,7 @@ class Entry: @staticmethod def clean_specifier(specifier): from pipenv.vendor.packaging.specifiers import Specifier + if not any(specifier.startswith(k) for k in Specifier._operators.keys()): if specifier.strip().lower() in ["any", "", "*"]: return "*" @@ -329,17 +360,19 @@ class Entry: @staticmethod def strip_version(specifier): from pipenv.vendor.packaging.specifiers import Specifier - op = next(iter( - k for k in Specifier._operators.keys() if specifier.startswith(k) - ), None) + + op = next( + iter(k for k in Specifier._operators.keys() if specifier.startswith(k)), None + ) if op: - specifier = specifier[len(op):] + specifier = specifier[len(op) :] while op: - op = next(iter( - k for k in Specifier._operators.keys() if specifier.startswith(k) - ), None) + op = next( + iter(k for k in Specifier._operators.keys() if specifier.startswith(k)), + None, + ) if op: - specifier = specifier[len(op):] + specifier = specifier[len(op) :] return specifier @property @@ -358,7 +391,8 @@ class Entry: def parents_in_pipfile(self): if not self._parents_in_pipfile: self._parents_in_pipfile = [ - p for p in self.flattened_parents + p + for p in self.flattened_parents if p.normalized_name in self.pipfile_packages ] return self._parents_in_pipfile @@ -370,9 +404,9 @@ class Entry: @property def requirements(self): if not self._requires: - self._requires = next(iter( - self.project.environment.get_package_requirements(self.name) - ), {}) + self._requires = next( + iter(self.project.environment.get_package_requirements(self.name)), {} + ) return self._requires @property @@ -403,14 +437,19 @@ class Entry: def get_dependency(self, name): if self.requirements: - return next(iter( - dep for dep in self.requirements.get("dependencies", []) - if dep and dep.get("package_name", "") == name - ), {}) + return next( + iter( + dep + for dep in self.requirements.get("dependencies", []) + if dep and dep.get("package_name", "") == name + ), + {}, + ) return {} def get_parent_deps(self, unnest=False): from pipenv.vendor.packaging.specifiers import Specifier + parents = [] for spec in self.reverse_deps.get(self.normalized_name, {}).get("parents", set()): spec_match = next(iter(c for c in Specifier._operators if c in spec), None) @@ -418,7 +457,9 @@ class Entry: parent = None if spec_match is not None: spec_index = spec.index(spec_match) - specifier = self.clean_specifier(spec[spec_index:len(spec_match)]).strip() + specifier = self.clean_specifier( + spec[spec_index : len(spec_match)] + ).strip() name_start = spec_index + len(spec_match) name = spec[name_start:].strip() parent = self.create_parent(name, specifier) @@ -460,13 +501,16 @@ class Entry: self.entry_dict = self.lockfile_dict.copy() elif can_use_updated: if len(satisfied_by_versions) == 1: - self.entry_dict["version"] = next(iter( - sat_by for sat_by in satisfied_by_versions if sat_by - ), None) + self.entry_dict["version"] = next( + iter(sat_by for sat_by in satisfied_by_versions if sat_by), None + ) hashes = None if self.lockfile_entry.specifiers == satisfied_by: ireq = self.lockfile_entry.as_ireq() - if not self.lockfile_entry.hashes and self.resolver._should_include_hash(ireq): + if ( + not self.lockfile_entry.hashes + and self.resolver._should_include_hash(ireq) + ): hashes = self.resolver.get_hash(ireq) else: hashes = self.lockfile_entry.hashes @@ -491,11 +535,12 @@ class Entry: :rtype: Set """ constraints = { - c for c in self.resolver.parsed_constraints - if c and c.name == self.entry.name + c for c in self.resolver.parsed_constraints if c and c.name == self.entry.name } pipfile_constraint = self.get_pipfile_constraint() - if pipfile_constraint and not (self.pipfile_entry.editable or pipfile_constraint.editable): + if pipfile_constraint and not ( + self.pipfile_entry.editable or pipfile_constraint.editable + ): constraints.add(pipfile_constraint) return constraints @@ -532,8 +577,10 @@ class Entry: msg = ( "Cannot resolve conflicting version {}{} while {}{} is " "locked.".format( - self.name, constraint.req.specifier, - self.name, self.updated_specifier + self.name, + constraint.req.specifier, + self.name, + self.updated_specifier, ) ) raise DependencyConflict(msg) @@ -545,12 +592,15 @@ class Entry: continue if not parent.validate_specifiers(): from pipenv.exceptions import DependencyConflict + msg = ( "Cannot resolve conflicting versions: (Root: {}) {}{} (Pipfile) " "Incompatible with {}{} (resolved)\n".format( - self.name, parent.pipfile_name, - parent.pipfile_entry.requirement.specifiers, parent.name, - parent.updated_specifiers + self.name, + parent.pipfile_name, + parent.pipfile_entry.requirement.specifiers, + parent.name, + parent.updated_specifiers, ) ) raise DependencyConflict(msg) @@ -586,6 +636,7 @@ class Entry: def clean_results(results, resolver, project, dev=False): from pipenv.utils.dependencies import translate_markers + if not project.lockfile_exists: return results lockfile = project.lockfile_content @@ -595,7 +646,9 @@ def clean_results(results, resolver, project, dev=False): for result in results: name = result.get("name") entry_dict = result.copy() - entry = Entry(name, entry_dict, project, resolver, reverse_deps=reverse_deps, dev=dev) + entry = Entry( + name, entry_dict, project, resolver, reverse_deps=reverse_deps, dev=dev + ) entry_dict = translate_markers(entry.get_cleaned_dict(keep_outdated=False)) new_results.append(entry_dict) return new_results @@ -611,7 +664,9 @@ def clean_outdated(results, resolver, project, dev=False): for result in results: name = result.get("name") entry_dict = result.copy() - entry = Entry(name, entry_dict, project, resolver, reverse_deps=reverse_deps, dev=dev) + entry = Entry( + name, entry_dict, project, resolver, reverse_deps=reverse_deps, dev=dev + ) # The old entry was editable but this one isnt; prefer the old one # TODO: Should this be the case for all locking? if entry.was_editable and not entry.is_editable: @@ -622,9 +677,17 @@ def clean_outdated(results, resolver, project, dev=False): if name in lockfile[alternate_section]: lockfile_entry = lockfile[alternate_section][name] if lockfile_entry and not entry.is_updated: - old_markers = next(iter(m for m in ( - entry.lockfile_entry.markers, lockfile_entry.get("markers", None) - ) if m is not None), None) + old_markers = next( + iter( + m + for m in ( + entry.lockfile_entry.markers, + lockfile_entry.get("markers", None), + ) + if m is not None + ), + None, + ) new_markers = entry_dict.get("markers", None) if old_markers: old_markers = Entry.marker_to_str(old_markers) @@ -644,14 +707,15 @@ def clean_outdated(results, resolver, project, dev=False): def parse_packages(packages, pre, clear, system, requirements_dir=None): + from pipenv.utils.indexes import parse_indexes from pipenv.vendor.requirementslib.models.requirements import Requirement from pipenv.vendor.vistir.contextmanagers import cd, temp_path - from pipenv.utils.indexes import parse_indexes + parsed_packages = [] for package in packages: *_, line = parse_indexes(package) line = " ".join(line) - pf = dict() + pf = {} req = Requirement.from_line(line) if not req.name: with temp_path(), cd(req.req.setup_info.base_dir): @@ -676,6 +740,7 @@ def parse_packages(packages, pre, clear, system, requirements_dir=None): def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packages, dev): from pipenv.utils.internet import create_mirror_source, replace_pypi_sources from pipenv.utils.resolver import resolve_deps + pypi_mirror_source = ( create_mirror_source(os.environ["PIPENV_PYPI_MIRROR"]) if "PIPENV_PYPI_MIRROR" in os.environ @@ -691,10 +756,11 @@ def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packa sources=sources, clear=clear, allow_global=system, - req_dir=requirements_dir + req_dir=requirements_dir, ) from pipenv.project import Project + project = Project() sources = ( replace_pypi_sources(project.pipfile_sources, pypi_mirror_source) @@ -729,8 +795,20 @@ def resolve_packages(pre, clear, verbose, system, write, requirements_dir, packa print(json.dumps([])) -def _main(pre, clear, verbose, system, write, requirements_dir, packages, parse_only=False, dev=False): - os.environ["PIPENV_REQUESTED_PYTHON_VERSION"] = ".".join([str(s) for s in sys.version_info[:3]]) +def _main( + pre, + clear, + verbose, + system, + write, + requirements_dir, + packages, + parse_only=False, + dev=False, +): + os.environ["PIPENV_REQUESTED_PYTHON_VERSION"] = ".".join( + [str(s) for s in sys.version_info[:3]] + ) os.environ["PIP_PYTHON_PATH"] = str(sys.executable) if parse_only: parse_packages( @@ -741,7 +819,9 @@ def _main(pre, clear, verbose, system, write, requirements_dir, packages, parse_ requirements_dir=requirements_dir, ) else: - resolve_packages(pre, clear, verbose, system, write, requirements_dir, packages, dev) + resolve_packages( + pre, clear, verbose, system, write, requirements_dir, packages, dev + ) def main(argv=None): @@ -749,8 +829,10 @@ def main(argv=None): parsed, remaining = parser.parse_known_args(argv) _patch_path(pipenv_site=parsed.pipenv_site) import warnings + from pipenv.vendor.vistir.compat import ResourceWarning from pipenv.vendor.vistir.misc import replace_with_text_stream + warnings.simplefilter("ignore", category=ResourceWarning) replace_with_text_stream("stdout") replace_with_text_stream("stderr") @@ -758,9 +840,17 @@ def main(argv=None): os.environ["PYTHONIOENCODING"] = "utf-8" os.environ["PYTHONUNBUFFERED"] = "1" parsed = handle_parsed_args(parsed) - _main(parsed.pre, parsed.clear, parsed.verbose, parsed.system, parsed.write, - parsed.requirements_dir, parsed.packages, parse_only=parsed.parse_only, - dev=parsed.dev) + _main( + parsed.pre, + parsed.clear, + parsed.verbose, + parsed.system, + parsed.write, + parsed.requirements_dir, + parsed.packages, + parse_only=parsed.parse_only, + dev=parsed.dev, + ) if __name__ == "__main__": diff --git a/pipenv/shells.py b/pipenv/shells.py index d9abc490..d54b0bcd 100644 --- a/pipenv/shells.py +++ b/pipenv/shells.py @@ -10,7 +10,6 @@ from pipenv.vendor import shellingham from pipenv.vendor.vistir.compat import Path, get_terminal_size from pipenv.vendor.vistir.contextmanagers import temp_environ - ShellDetectionFailure = shellingham.ShellDetectionFailure @@ -49,7 +48,7 @@ def _get_activate_script(cmd, venv): command = "." # Escape any special characters located within the virtualenv path to allow # for proper activation. - venv_location = re.sub(r'([ &$()\[\]])', r"\\\1", str(venv)) + venv_location = re.sub(r"([ &$()\[\]])", r"\\\1", str(venv)) # The leading space can make history cleaner in some shells. return f" {command} {venv_location}/bin/activate{suffix}" @@ -68,7 +67,7 @@ class Shell: self.args = [] def __repr__(self): - return '{type}(cmd={cmd!r})'.format( + return "{type}(cmd={cmd!r})".format( type=type(self).__name__, cmd=self.cmd, ) @@ -149,10 +148,9 @@ class Bash(Shell): base_rc_src = f'source "{bashrc_path.as_posix()}"\n' rcfile.write(base_rc_src) - export_path = 'export PATH="{}:$PATH"\n'.format(":".join( - self._format_path(python) - for python in _iter_python(venv) - )) + export_path = 'export PATH="{}:$PATH"\n'.format( + ":".join(self._format_path(python) for python in _iter_python(venv)) + ) rcfile.write(export_path) rcfile.flush() self.args.extend(["--rcfile", rcfile.name]) @@ -165,7 +163,7 @@ class MsysBash(Bash): if not python.drive: return s # Convert "C:/something" to "/c/something". - return f'/{s[0].lower()}{s[2:]}' + return f"/{s[0].lower()}{s[2:]}" class CmderEmulatedShell(Shell): @@ -207,16 +205,20 @@ SHELL_LOOKUP = collections.defaultdict( lambda: collections.defaultdict(lambda: Shell), { "bash": collections.defaultdict( - lambda: Bash, {"msys": MsysBash}, + lambda: Bash, + {"msys": MsysBash}, ), "cmd": collections.defaultdict( - lambda: Shell, {"cmder": CmderCommandPrompt}, + lambda: Shell, + {"cmder": CmderCommandPrompt}, ), "powershell": collections.defaultdict( - lambda: Shell, {"cmder": CmderPowershell}, + lambda: Shell, + {"cmder": CmderPowershell}, ), "pwsh": collections.defaultdict( - lambda: Shell, {"cmder": CmderPowershell}, + lambda: Shell, + {"cmder": CmderPowershell}, ), }, ) diff --git a/pipenv/utils/__init__.py b/pipenv/utils/__init__.py index 719d3710..7f18c65d 100644 --- a/pipenv/utils/__init__.py +++ b/pipenv/utils/__init__.py @@ -1,2 +1,3 @@ import logging + logging.basicConfig(level=logging.ERROR) diff --git a/pipenv/utils/dependencies.py b/pipenv/utils/dependencies.py index 03cdbaaf..03fbd435 100644 --- a/pipenv/utils/dependencies.py +++ b/pipenv/utils/dependencies.py @@ -1,13 +1,13 @@ import os -import re from contextlib import contextmanager from pathlib import Path - -from typing import Sequence, Mapping +from typing import Mapping, Sequence from urllib.parse import urlparse from packaging.markers import Marker + from pipenv import fs_str + from .constants import SCHEME_LIST, VCS_LIST from .shell import temp_path @@ -80,13 +80,7 @@ def pep423_name(name): return name -def get_vcs_deps( - project=None, - dev=False, - pypi_mirror=None, - packages=None, - reqs=None -): +def get_vcs_deps(project=None, dev=False, pypi_mirror=None, packages=None, reqs=None): from pipenv.vendor.requirementslib.models.requirements import Requirement section = "vcs_dev_packages" if dev else "vcs_packages" @@ -112,7 +106,7 @@ def get_vcs_deps( try: with temp_path(), locked_repository(requirement) as repo: from pipenv.vendor.requirementslib.models.requirements import ( - Requirement + Requirement, ) # from distutils.sysconfig import get_python_lib @@ -120,7 +114,7 @@ def get_vcs_deps( commit_hash = repo.get_commit_hash() name = requirement.normalized_name lockfile[name] = requirement.pipfile_entry[1] - lockfile[name]['ref'] = commit_hash + lockfile[name]["ref"] = commit_hash result.append(requirement) except OSError: continue @@ -152,7 +146,7 @@ def translate_markers(pipfile_entry): marker_str = new_pipfile.pop("markers") if marker_str: marker = str(Marker(marker_str)) - if 'extra' not in marker: + if "extra" not in marker: marker_set.add(marker) for m in pipfile_markers: entry = f"{pipfile_entry[m]}" @@ -160,15 +154,19 @@ def translate_markers(pipfile_entry): marker_set.add(str(Marker(f"{m} {entry}"))) new_pipfile.pop(m) if marker_set: - new_pipfile["markers"] = str(Marker(" or ".join( - f"{s}" if " and " in s else s - for s in sorted(dedup(marker_set)) - ))).replace('"', "'") + new_pipfile["markers"] = str( + Marker( + " or ".join( + f"{s}" if " and " in s else s for s in sorted(dedup(marker_set)) + ) + ) + ).replace('"', "'") return new_pipfile def clean_resolved_dep(dep, is_top_level=False, pipfile_entry=None): from pipenv.vendor.requirementslib.utils import is_vcs + name = pep423_name(dep["name"]) lockfile = {} # We use this to determine if there are any markers on top level packages @@ -195,7 +193,9 @@ def clean_resolved_dep(dep, is_top_level=False, pipfile_entry=None): fs_key = next(iter(k for k in ["path", "file"] if k in dep), None) pipfile_fs_key = None if pipfile_entry: - pipfile_fs_key = next(iter(k for k in ["path", "file"] if k in pipfile_entry), None) + pipfile_fs_key = next( + iter(k for k in ["path", "file"] if k in pipfile_entry), None + ) if fs_key and pipfile_fs_key and fs_key != pipfile_fs_key: lockfile[pipfile_fs_key] = pipfile_entry[pipfile_fs_key] elif fs_key is not None: @@ -248,7 +248,7 @@ def is_pinned_requirement(ireq): def convert_deps_to_pip(deps, project=None, r=True, include_index=True): - """"Converts a Pipfile-formatted dependency to a pip-formatted one.""" + """ "Converts a Pipfile-formatted dependency to a pip-formatted one.""" from pipenv.vendor.requirementslib.models.requirements import Requirement dependencies = [] @@ -266,6 +266,7 @@ def convert_deps_to_pip(deps, project=None, r=True, include_index=True): # Write requirements.txt to tmp directory. from pipenv.vendor.vistir.path import create_tracked_tempfile + f = create_tracked_tempfile(suffix="-requirements.txt", delete=False) f.write("\n".join(dependencies).encode("utf-8")) f.close() @@ -333,6 +334,7 @@ def is_installable_file(path): @contextmanager def locked_repository(requirement): from pipenv.vendor.vistir.path import create_tracked_tempdir + if not requirement.is_vcs: return original_base = os.environ.pop("PIP_SHIMS_BASE_MODULE", None) diff --git a/pipenv/utils/indexes.py b/pipenv/utils/indexes.py index 64392b2a..4d5a0236 100644 --- a/pipenv/utils/indexes.py +++ b/pipenv/utils/indexes.py @@ -1,18 +1,18 @@ import re + +from urllib3.util import parse_url + from pipenv import environments -if environments.MYPY_RUNNING: - from typing import List, Optional, Text, Tuple, Union - - from pipenv.project import Project, TSource - from pipenv.vendor.requirementslib.models.requirements import Requirement - -from urllib3 import util as urllib3_util -from requirementslib import Requirement - -from pipenv.vendor.vistir.compat import Mapping from pipenv.exceptions import PipenvUsageError +from pipenv.vendor.vistir.compat import Mapping + from .internet import create_mirror_source, is_pypi_url +if environments.MYPY_RUNNING: + from typing import List, Optional, Union # noqa + + from pipenv.project import Project, TSource # noqa + def prepare_pip_source_args(sources, pip_args=None): if pip_args is None: @@ -25,11 +25,9 @@ def prepare_pip_source_args(sources, pip_args=None): pip_args.extend(["-i", package_url]) # Trust the host if it's not verified. if not sources[0].get("verify_ssl", True): - url_parts = urllib3_util.parse_url(package_url) + url_parts = parse_url(package_url) url_port = f":{url_parts.port}" if url_parts.port else "" - pip_args.extend( - ["--trusted-host", f"{url_parts.host}{url_port}"] - ) + pip_args.extend(["--trusted-host", f"{url_parts.host}{url_port}"]) # Add additional sources as extra indexes. if len(sources) > 1: for source in sources[1:]: @@ -39,17 +37,16 @@ def prepare_pip_source_args(sources, pip_args=None): pip_args.extend(["--extra-index-url", url]) # Trust the host if it's not verified. if not source.get("verify_ssl", True): - url_parts = urllib3_util.parse_url(url) + url_parts = parse_url(url) url_port = f":{url_parts.port}" if url_parts.port else "" - pip_args.extend( - ["--trusted-host", f"{url_parts.host}{url_port}"] - ) + pip_args.extend(["--trusted-host", f"{url_parts.host}{url_port}"]) return pip_args def get_project_index(project, index=None, trusted_hosts=None): # type: (Optional[Union[str, TSource]], Optional[List[str]], Optional[Project]) -> TSource from pipenv.project import SourceNotFound + if trusted_hosts is None: trusted_hosts = [] if isinstance(index, Mapping): @@ -57,7 +54,7 @@ def get_project_index(project, index=None, trusted_hosts=None): try: source = project.find_source(index) except SourceNotFound: - index_url = urllib3_util.parse_url(index) + index_url = parse_url(index) src_name = project.src_name_from_url(index) verify_ssl = index_url.host not in trusted_hosts source = {"url": index, "verify_ssl": verify_ssl, "name": src_name} @@ -109,8 +106,9 @@ def parse_indexes(line, strict=False): index = args.index extra_index = args.extra_index trusted_host = args.trusted_host - if strict and sum( - bool(arg) for arg in (index, extra_index, trusted_host, remainder) - ) > 1: + if ( + strict + and sum(bool(arg) for arg in (index, extra_index, trusted_host, remainder)) > 1 + ): raise ValueError("Index arguments must be on their own lines.") return index, extra_index, trusted_host, remainder diff --git a/pipenv/utils/internet.py b/pipenv/utils/internet.py index 71bf1092..a26d6948 100644 --- a/pipenv/utils/internet.py +++ b/pipenv/utils/internet.py @@ -1,5 +1,6 @@ import re from urllib.parse import urlparse + from urllib3 import util as urllib3_util from pipenv.vendor import parse @@ -75,7 +76,7 @@ def get_host_and_port(url): :return: a string with the host:port pair if the URL includes port number explicitly; otherwise, returns host only """ url = urllib3_util.parse_url(url) - return '{}:{}'.format(url.host, url.port) if url.port else url.host + return "{}:{}".format(url.host, url.port) if url.port else url.host def get_url_name(url): @@ -120,9 +121,7 @@ def proper_case(package_name): f"https://pypi.org/pypi/{package_name}/json", timeout=0.3, stream=True ) if not r.ok: - raise OSError( - f"Unable to find package {package_name} in PyPI repository." - ) + raise OSError(f"Unable to find package {package_name} in PyPI repository.") r = parse.parse("https://pypi.org/pypi/{name}/json", r.url) good_name = r["name"] diff --git a/pipenv/utils/locking.py b/pipenv/utils/locking.py index 0db7db66..3b6e3247 100644 --- a/pipenv/utils/locking.py +++ b/pipenv/utils/locking.py @@ -1,6 +1,6 @@ from typing import Mapping -from .dependencies import pep423_name, translate_markers, clean_resolved_dep +from .dependencies import clean_resolved_dep, pep423_name, translate_markers def format_requirement_for_lockfile(req, markers_lookup, index_lookup, hashes=None): @@ -44,16 +44,12 @@ def get_locked_dep(dep, pipfile_section, prefer_pipfile=True): # it now for development purposes # TODO: Is this implementation clear? How can it be improved? entry = None - cleaner_kwargs = { - "is_top_level": False, - "pipfile_entry": None - } + cleaner_kwargs = {"is_top_level": False, "pipfile_entry": None} if isinstance(dep, Mapping) and dep.get("name", ""): dep_name = pep423_name(dep["name"]) - name = next(iter( - k for k in pipfile_section.keys() - if pep423_name(k) == dep_name - ), None) + name = next( + iter(k for k in pipfile_section.keys() if pep423_name(k) == dep_name), None + ) entry = pipfile_section[name] if name else None if entry: @@ -66,7 +62,12 @@ def get_locked_dep(dep, pipfile_section, prefer_pipfile=True): lockfile_name, lockfile_dict = lockfile_entry.copy().popitem() lockfile_version = lockfile_dict.get("version", "") # Keep pins from the lockfile - if prefer_pipfile and lockfile_version != version and version.startswith("==") and "*" not in version: + if ( + prefer_pipfile + and lockfile_version != version + and version.startswith("==") + and "*" not in version + ): lockfile_dict["version"] = version lockfile_entry[lockfile_name] = lockfile_dict return lockfile_entry diff --git a/pipenv/utils/processes.py b/pipenv/utils/processes.py index d1c46fb9..dcb0aa3a 100644 --- a/pipenv/utils/processes.py +++ b/pipenv/utils/processes.py @@ -4,11 +4,11 @@ import subprocess import crayons from click import echo as click_echo -from pipenv.exceptions import PipenvCmdError from pipenv import environments +from pipenv.exceptions import PipenvCmdError if environments.MYPY_RUNNING: - from typing import Tuple + from typing import Tuple # noqa def run_command(cmd, *args, is_verbose=False, **kwargs): @@ -25,6 +25,7 @@ def run_command(cmd, *args, is_verbose=False, **kwargs): from pipenv._compat import decode_for_output from pipenv.cmdparse import Script + catch_exceptions = kwargs.pop("catch_exceptions", True) if isinstance(cmd, ((str,), list, tuple)): cmd = Script.parse(cmd) @@ -38,17 +39,24 @@ def run_command(cmd, *args, is_verbose=False, **kwargs): click_echo(f"Running command: $ {cmd.cmdify()}") c = subprocess_run(command, *args, **kwargs) if is_verbose: - click_echo("Command output: {}".format( - crayons.cyan(decode_for_output(c.stdout)) - ), err=True) + click_echo( + "Command output: {}".format(crayons.cyan(decode_for_output(c.stdout))), + err=True, + ) if c.returncode and catch_exceptions: raise PipenvCmdError(cmd.cmdify(), c.stdout, c.stderr, c.returncode) return c def subprocess_run( - args, *, block=True, text=True, capture_output=True, - encoding="utf-8", env=None, **other_kwargs + args, + *, + block=True, + text=True, + capture_output=True, + encoding="utf-8", + env=None, + **other_kwargs, ): """A backward compatible version of subprocess.run(). @@ -61,17 +69,13 @@ def subprocess_run( _env.update(env) other_kwargs["env"] = _env if capture_output: - other_kwargs['stdout'] = subprocess.PIPE - other_kwargs['stderr'] = subprocess.PIPE + other_kwargs["stdout"] = subprocess.PIPE + other_kwargs["stderr"] = subprocess.PIPE if block: return subprocess.run( - args, universal_newlines=text, - encoding=encoding, **other_kwargs + args, universal_newlines=text, encoding=encoding, **other_kwargs ) else: return subprocess.Popen( - args, universal_newlines=text, - encoding=encoding, **other_kwargs + args, universal_newlines=text, encoding=encoding, **other_kwargs ) - - diff --git a/pipenv/utils/resolver.py b/pipenv/utils/resolver.py index 519e709f..e643cc24 100644 --- a/pipenv/utils/resolver.py +++ b/pipenv/utils/resolver.py @@ -7,27 +7,36 @@ import warnings from functools import lru_cache import crayons -from pipenv import environments from click import echo as click_echo -from pipenv.exceptions import ResolutionFailure, RequirementError -from pipenv.vendor.requirementslib import Requirement, Pipfile -from pipenv.vendor.vistir import open_file, TemporaryDirectory -from .dependencies import get_vcs_deps, pep423_name, translate_markers, clean_pkg_version, \ - is_pinned_requirement, convert_deps_to_pip, HackedPythonVersion +from pipenv import environments +from pipenv.exceptions import RequirementError, ResolutionFailure +from pipenv.vendor.requirementslib import Pipfile, Requirement +from pipenv.vendor.requirementslib.models.utils import DIRECT_URL_RE +from pipenv.vendor.vistir import TemporaryDirectory, open_file +from pipenv.vendor.vistir.path import create_tracked_tempdir + +from .dependencies import ( + HackedPythonVersion, + clean_pkg_version, + convert_deps_to_pip, + get_vcs_deps, + is_pinned_requirement, + pep423_name, + translate_markers, +) from .indexes import parse_indexes, prepare_pip_source_args from .internet import _get_requests_session from .locking import format_requirement_for_lockfile, prepare_lockfile -from .shell import temp_environ, subprocess_run, make_posix +from .shell import make_posix, subprocess_run, temp_environ from .spinner import create_spinner -if environments.MYPY_RUNNING: - from typing import Any, Dict, List, Optional, Tuple, Union - from pipenv.project import Project - from pipenv.vendor.requirementslib.models.pipfile import Pipfile - from pipenv.vendor.requirementslib.models.requirements import ( - Line, Requirement - ) +if environments.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 class HashCacheMixin: @@ -38,6 +47,7 @@ class HashCacheMixin: cache key includes the hash value returned from the server). This ought to avoid issues where the location on the server changes. """ + def __init__(self, directory, session): self.session = session if not os.path.isdir(directory): @@ -65,8 +75,16 @@ class HashCacheMixin: class Resolver: def __init__( - self, constraints, req_dir, project, sources, index_lookup=None, - markers_lookup=None, skipped=None, clear=False, pre=False + self, + constraints, + req_dir, + project, + sources, + index_lookup=None, + markers_lookup=None, + skipped=None, + clear=False, + pre=False, ): self.initial_constraints = constraints self.req_dir = req_dir @@ -113,9 +131,9 @@ class Resolver: from pipenv.vendor.pip_shims import shims if not self._hash_cache: - self._hash_cache = type("HashCache", (HashCacheMixin, shims.SafeFileCache), {})( - os.path.join(self.project.s.PIPENV_CACHE_DIR, "hashes"), self.session - ) + self._hash_cache = type( + "HashCache", (HashCacheMixin, shims.SafeFileCache), {} + )(os.path.join(self.project.s.PIPENV_CACHE_DIR, "hashes"), self.session) return self._hash_cache @classmethod @@ -132,23 +150,31 @@ class Resolver: ): # type: (...) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]], Dict[str, str], Dict[str, str]] constraints = set() # type: Set[str] - skipped = dict() # type: Dict[str, Dict[str, Union[str, bool, List[str]]]] + skipped = {} # type: Dict[str, Dict[str, Union[str, bool, List[str]]]] if index_lookup is None: index_lookup = {} if markers_lookup is None: markers_lookup = {} if not req_dir: - from pipenv.vendor.vistir.path import create_tracked_tempdir req_dir = create_tracked_tempdir(prefix="pipenv-", suffix="-reqdir") transient_resolver = cls( - [], req_dir, project, sources, index_lookup=index_lookup, - markers_lookup=markers_lookup, clear=clear, pre=pre + [], + req_dir, + project, + sources, + index_lookup=index_lookup, + markers_lookup=markers_lookup, + clear=clear, + pre=pre, ) for dep in deps: if not dep: continue req, req_idx, markers_idx = cls.parse_line( - dep, index_lookup=index_lookup, markers_lookup=markers_lookup, project=project + dep, + index_lookup=index_lookup, + markers_lookup=markers_lookup, + project=project, ) index_lookup.update(req_idx) markers_lookup.update(markers_idx) @@ -158,12 +184,20 @@ class Resolver: # to the lockfile directly use_sources = None if req.name in index_lookup: - use_sources = list(filter(lambda s: s.get('name') == index_lookup[req.name], sources)) + use_sources = list( + filter(lambda s: s.get("name") == index_lookup[req.name], sources) + ) if not use_sources: use_sources = sources transient_resolver = cls( - [], req_dir, project, use_sources, index_lookup=index_lookup, - markers_lookup=markers_lookup, clear=clear, pre=pre + [], + req_dir, + project, + use_sources, + index_lookup=index_lookup, + markers_lookup=markers_lookup, + clear=clear, + pre=pre, ) constraint_update, lockfile_update = cls.get_deps_from_req( req, resolver=transient_resolver, resolve_vcs=project.s.PIPENV_RESOLVE_VCS @@ -178,17 +212,17 @@ class Resolver: line, # type: str index_lookup=None, # type: Dict[str, str] markers_lookup=None, # type: Dict[str, str] - project=None # type: Optional[Project] + project=None, # type: Optional[Project] ): # type: (...) -> Tuple[Requirement, Dict[str, str], Dict[str, str]] - from pipenv.vendor.requirementslib.models.requirements import Requirement - from pipenv.vendor.requirementslib.models.utils import DIRECT_URL_RE + if index_lookup is None: index_lookup = {} if markers_lookup is None: markers_lookup = {} if project is None: from pipenv.project import Project + project = Project() index, extra_index, trust_host, remainder = parse_indexes(line) line = " ".join(remainder) @@ -202,13 +236,18 @@ class Resolver: try: req = Requirement.from_line(line) except ValueError: - raise ResolutionFailure(f"Failed to resolve requirement from line: {line!s}") + raise ResolutionFailure( + f"Failed to resolve requirement from line: {line!s}" + ) else: - raise ResolutionFailure(f"Failed to resolve requirement from line: {line!s}") + raise ResolutionFailure( + f"Failed to resolve requirement from line: {line!s}" + ) if index: try: index_lookup[req.normalized_name] = project.get_source( - url=index, refresh=True).get("name") + url=index, refresh=True + ).get("name") except TypeError: pass try: @@ -227,13 +266,13 @@ class Resolver: # type: (Requirement, Optional["Resolver"], bool) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]]] from pipenv.vendor.requirementslib.models.requirements import Requirement from pipenv.vendor.requirementslib.models.utils import ( - _requirement_to_str_lowercase_name + _requirement_to_str_lowercase_name, ) from pipenv.vendor.requirementslib.utils import is_installable_dir # TODO: this is way too complex, refactor this constraints = set() # type: Set[str] - locked_deps = dict() # type: Dict[str, Dict[str, Union[str, bool, List[str]]]] + locked_deps = {} # type: Dict[str, Dict[str, Union[str, bool, List[str]]]] if (req.is_file_or_url or req.is_vcs) and not req.is_wheel: # for local packages with setup.py files and potential direct url deps: if req.is_vcs: @@ -243,7 +282,6 @@ class Resolver: else: _, entry = req.pipfile_entry parsed_line = req.req.parsed_line # type: Line - setup_info = None # type: Any try: name = req.normalized_name except TypeError: @@ -255,8 +293,11 @@ class Resolver: # Allow users to toggle resolution off for non-editable VCS packages # but leave it on for local, installable folders on the filesystem if resolve_vcs or ( - req.editable or parsed_line.is_wheel or ( - req.is_file_or_url and parsed_line.is_local + req.editable + or parsed_line.is_wheel + or ( + req.is_file_or_url + and parsed_line.is_local and is_installable_dir(parsed_line.path) ) ): @@ -271,9 +312,7 @@ class Resolver: if r.marker and not r.marker.evaluate(): new_constraints = {} _, new_entry = req.pipfile_entry - new_lock = { - pep423_name(new_req.normalized_name): new_entry - } + new_lock = {pep423_name(new_req.normalized_name): new_entry} else: new_constraints, new_lock = cls.get_deps_from_req( new_req, resolver @@ -298,14 +337,22 @@ class Resolver: else: # if the dependency isn't installable, don't add it to constraints # and instead add it directly to the lock - if req and req.requirement and ( - req.requirement.marker and not req.requirement.marker.evaluate() + if ( + req + and req.requirement + and (req.requirement.marker and not req.requirement.marker.evaluate()) ): pypi = resolver.finder if resolver else None ireq = req.ireq - best_match = pypi.find_best_candidate(ireq.name, ireq.specifier).best_candidate if pypi else None + best_match = ( + pypi.find_best_candidate(ireq.name, ireq.specifier).best_candidate + if pypi + else None + ) if best_match: - ireq.req.specifier = ireq.specifier.__class__(f"=={best_match.version}") + ireq.req.specifier = ireq.specifier.__class__( + f"=={best_match.version}" + ) hashes = resolver.collect_hashes(ireq) if resolver else [] new_req = Requirement.from_ireq(ireq) new_req = new_req.add_hashes(hashes) @@ -314,13 +361,13 @@ class Resolver: click_echo( "{} doesn't match your environment, " "its dependencies won't be resolved.".format(req.as_line()), - err=True + err=True, ) else: click_echo( "Could not find a version of {} that matches your environment, " "it will be skipped.".format(req.as_line()), - err=True + err=True, ) return constraints, locked_deps constraints.add(req.constraint_line) @@ -337,10 +384,10 @@ class Resolver: sources=None, # type: List[str] req_dir=None, # type: str clear=False, # type: bool - pre=False # type: bool + pre=False, # type: bool ): # type: (...) -> "Resolver" - from pipenv.vendor.vistir.path import create_tracked_tempdir + if not req_dir: req_dir = create_tracked_tempdir(suffix="-requirements", prefix="pipenv-") if index_lookup is None: @@ -350,18 +397,31 @@ class Resolver: if sources is None: sources = project.sources constraints, skipped, index_lookup, markers_lookup = cls.get_metadata( - deps, index_lookup, markers_lookup, project, sources, req_dir=req_dir, - pre=pre, clear=clear + deps, + index_lookup, + markers_lookup, + project, + sources, + req_dir=req_dir, + pre=pre, + clear=clear, ) return Resolver( - constraints, req_dir, project, sources, index_lookup=index_lookup, - markers_lookup=markers_lookup, skipped=skipped, clear=clear, pre=pre + constraints, + req_dir, + project, + sources, + index_lookup=index_lookup, + markers_lookup=markers_lookup, + skipped=skipped, + clear=clear, + pre=pre, ) @classmethod def from_pipfile(cls, project, pipfile=None, dev=False, pre=False, clear=False): # type: (Optional[Project], Optional[Pipfile], bool, bool, bool) -> "Resolver" - from pipenv.vendor.vistir.path import create_tracked_tempdir + if not pipfile: pipfile = project._pipfile req_dir = create_tracked_tempdir(suffix="-requirements", prefix="pipenv-") @@ -371,12 +431,25 @@ class Resolver: deps.update({req.as_line() for req in pipfile.dev_packages}) deps.update({req.as_line() for req in pipfile.packages}) constraints, skipped, index_lookup, markers_lookup = cls.get_metadata( - list(deps), index_lookup, markers_lookup, project, project.sources, - req_dir=req_dir, pre=pre, clear=clear + list(deps), + index_lookup, + markers_lookup, + project, + project.sources, + req_dir=req_dir, + pre=pre, + clear=clear, ) return Resolver( - constraints, req_dir, project, project.sources, index_lookup=index_lookup, - markers_lookup=markers_lookup, skipped=skipped, clear=clear, pre=pre + constraints, + req_dir, + project, + project.sources, + index_lookup=index_lookup, + markers_lookup=markers_lookup, + skipped=skipped, + clear=clear, + pre=pre, ) @property @@ -410,6 +483,7 @@ class Resolver: def prepare_constraint_file(self): from pipenv.vendor.vistir.path import create_tracked_tempfile + constraints_file = create_tracked_tempfile( mode="w", prefix="pipenv-", @@ -419,7 +493,8 @@ class Resolver: ) skip_args = ("build-isolation", "use-pep517", "cache-dir") args_to_add = [ - arg for arg in self.pip_args + arg + for arg in self.pip_args if not any(bad_arg in arg for bad_arg in skip_args) ] if self.sources: @@ -446,7 +521,9 @@ class Resolver: pip_options.no_input = True pip_options.progress_bar = "off" pip_options.ignore_requires_python = True - pip_options.pre = self.pre or self.project.settings.get("allow_prereleases", False) + pip_options.pre = self.pre or self.project.settings.get( + "allow_prereleases", False + ) self._pip_options = pip_options return self._pip_options @@ -459,16 +536,17 @@ class Resolver: @property def finder(self): from pipenv.vendor.pip_shims import shims + if self._finder is None: self._finder = shims.get_package_finder( install_cmd=self.pip_command, options=self.pip_options, - session=self.session + session=self.session, ) index_mapping = {} for source in self.sources: - if source.get('name'): - index_mapping[source['name']] = source['url'] + if source.get("name"): + index_mapping[source["name"]] = source["url"] alt_index_lookup = {} for req_name, index in self.index_lookup.items(): if index_mapping.get(index): @@ -480,6 +558,7 @@ class Resolver: @property def ignore_compatibility_finder(self): from pipenv.vendor.pip_shims import shims + if self._ignore_compatibility_finder is None: ignore_compatibility_finder = shims.get_package_finder( install_cmd=self.pip_command, @@ -501,20 +580,26 @@ class Resolver: pip_options.extra_index_urls = [] if self._parsed_constraints is None: self._parsed_constraints = shims.parse_requirements( - self.constraint_file, finder=self.finder, session=self.session, - options=pip_options + self.constraint_file, + finder=self.finder, + session=self.session, + options=pip_options, ) return self._parsed_constraints @property def constraints(self): - from pipenv.patched.notpip._internal.req.constructors import install_req_from_parsed_requirement + from pipenv.patched.notpip._internal.req.constructors import ( + install_req_from_parsed_requirement, + ) if self._constraints is None: self._constraints = [ install_req_from_parsed_requirement( - c, isolated=self.pip_options.build_isolation, - use_pep517=self.pip_options.use_pep517, user_supplied=True + c, + isolated=self.pip_options.build_isolation, + use_pep517=self.pip_options.use_pep517, + user_supplied=True, ) for c in self.parsed_constraints ] @@ -523,10 +608,14 @@ class Resolver: @contextlib.contextmanager def get_resolver(self, clear=False): from pipenv.vendor.pip_shims.shims import ( - WheelCache, get_requirement_tracker, global_tempdir_manager + WheelCache, + get_requirement_tracker, + global_tempdir_manager, ) - with global_tempdir_manager(), get_requirement_tracker() as req_tracker, TemporaryDirectory(suffix="-build", prefix="pipenv-") as directory: + with global_tempdir_manager(), get_requirement_tracker() as req_tracker, TemporaryDirectory( + suffix="-build", prefix="pipenv-" + ) as directory: pip_options = self.pip_options finder = self.finder wheel_cache = WheelCache(pip_options.cache_dir, pip_options.format_control) @@ -554,8 +643,8 @@ class Resolver: yield resolver def resolve(self): - from pipenv.vendor.pip_shims.shims import InstallationError from pipenv.exceptions import ResolutionFailure + from pipenv.vendor.pip_shims.shims import InstallationError self.constraints # For some reason it is important to evaluate constraints before resolver context with temp_environ(), self.get_resolver() as resolver: @@ -570,12 +659,15 @@ class Resolver: def resolve_constraints(self): from pipenv.vendor.requirementslib.models.markers import marker_from_specifier + new_tree = set() for result in self.resolved_tree: if result.markers: self.markers[result.name] = result.markers else: - candidate = self.finder.find_best_candidate(result.name, result.specifier).best_candidate + candidate = self.finder.find_best_candidate( + result.name, result.specifier + ).best_candidate if candidate: requires_python = candidate.link.requires_python if requires_python: @@ -625,7 +717,8 @@ class Resolver: click_echo( "{}: Error generating hash for {}".format( crayons.red("Warning", bold=True), ireq.name - ), err=True + ), + err=True, ) return None @@ -688,13 +781,12 @@ class Resolver: return req.name, entry def clean_results(self): - from pipenv.vendor.requirementslib.models.requirements import ( - Requirement - ) + from pipenv.vendor.requirementslib.models.requirements import Requirement + reqs = [(Requirement.from_ireq(ireq), ireq) for ireq in self.resolved_tree] results = {} for req, ireq in reqs: - if (req.vcs and req.editable and not req.is_direct_url): + if req.vcs and req.editable and not req.is_direct_url: continue elif req.normalized_name in self.skipped.keys(): continue @@ -723,8 +815,14 @@ class Resolver: def _show_warning(message, category, filename, lineno, line): - warnings.showwarning(message=message, category=category, filename=filename, - lineno=lineno, file=sys.stderr, line=line) + warnings.showwarning( + message=message, + category=category, + filename=filename, + lineno=lineno, + file=sys.stderr, + line=line, + ) sys.stderr.flush() @@ -738,8 +836,6 @@ def actually_resolve_deps( pre, req_dir=None, ): - from pipenv.vendor.vistir.path import create_tracked_tempdir - if not req_dir: req_dir = create_tracked_tempdir(suffix="-requirements", prefix="pipenv-") warning_list = [] @@ -753,8 +849,13 @@ def actually_resolve_deps( resolver.resolve_constraints() results = resolver.clean_results() for warning in warning_list: - _show_warning(warning.message, warning.category, warning.filename, warning.lineno, - warning.line) + _show_warning( + warning.message, + warning.category, + warning.filename, + warning.lineno, + warning.line, + ) return (results, hashes, resolver.markers_lookup, resolver, resolver.skipped) @@ -762,6 +863,7 @@ def resolve(cmd, sp, project): from pipenv._compat import decode_output from pipenv.cmdparse import Script from pipenv.vendor.vistir.misc import echo + c = subprocess_run(Script.parse(cmd).cmd_args, block=False, env=os.environ.copy()) is_verbose = project.s.is_verbose() err = "" @@ -777,9 +879,7 @@ def resolve(cmd, sp, project): returncode = c.poll() out = c.stdout.read() if returncode != 0: - sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format( - "Locking Failed!" - )) + sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Locking Failed!")) echo(out.strip(), err=True) if not is_verbose: echo(err, err=True) @@ -800,7 +900,7 @@ def venv_resolve_deps( dev=False, pipfile=None, lockfile=None, - keep_outdated=False + keep_outdated=False, ): """ Resolve dependencies for a pipenv project, acts as a portal to the target environment. @@ -833,7 +933,6 @@ def venv_resolve_deps( from pipenv._compat import decode_for_output from pipenv.vendor.vistir.compat import JSONDecodeError, NamedTemporaryFile, Path from pipenv.vendor.vistir.misc import fs_str - from pipenv.vendor.vistir.path import create_tracked_tempdir results = [] pipfile_section = "dev-packages" if dev else "packages" @@ -852,7 +951,7 @@ def venv_resolve_deps( req_dir = create_tracked_tempdir(prefix="pipenv", suffix="requirements") cmd = [ which("python", allow_global=allow_global), - Path(resolver.__file__.rstrip("co")).as_posix() + Path(resolver.__file__.rstrip("co")).as_posix(), ] if pre: cmd.append("--pre") @@ -879,15 +978,15 @@ def venv_resolve_deps( os.environ.pop("PIPENV_SITE_DIR", None) if keep_outdated: os.environ["PIPENV_KEEP_OUTDATED"] = fs_str("1") - with create_spinner(text=decode_for_output("Locking..."), setting=project.s) as sp: + with create_spinner( + text=decode_for_output("Locking..."), setting=project.s + ) as sp: # This conversion is somewhat slow on local and file-type requirements since # we now download those requirements / make temporary folders to perform # dependency resolution on them, so we are including this step inside the # spinner context manager for the UX improvement sp.write(decode_for_output("Building requirements...")) - deps = convert_deps_to_pip( - deps, project, r=False, include_index=True - ) + deps = convert_deps_to_pip(deps, project, r=False, include_index=True) constraints = set(deps) os.environ["PIPENV_PACKAGES"] = str("\n".join(constraints)) sp.write(decode_for_output("Resolving dependencies...")) @@ -898,7 +997,9 @@ def venv_resolve_deps( if not project.s.is_verbose() and c.stderr.strip(): click_echo(crayons.yellow(f"Warning: {c.stderr.strip()}"), err=True) else: - sp.red.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format("Locking Failed!")) + sp.red.fail( + environments.PIPENV_SPINNER_FAIL_TEXT.format("Locking Failed!") + ) click_echo(f"Output: {c.stdout.strip()}", err=True) click_echo(f"Error: {c.stderr.strip()}", err=True) try: @@ -926,7 +1027,7 @@ def resolve_deps( clear=False, pre=False, allow_global=False, - req_dir=None + req_dir=None, ): """Given a list of dependencies, return a resolved list of dependencies, using pip-tools -- and their hashes, using the warehouse API / pip. @@ -944,7 +1045,6 @@ def resolve_deps( # First (proper) attempt: req_dir = req_dir if req_dir else os.environ.get("req_dir", None) if not req_dir: - from pipenv.vendor.vistir.path import create_tracked_tempdir req_dir = create_tracked_tempdir(prefix="pipenv-", suffix="-requirements") with HackedPythonVersion(python_version=python, python_path=python_path): try: @@ -970,7 +1070,13 @@ def resolve_deps( try: # Attempt to resolve again, with different Python version information, # particularly for particularly particular packages. - results, hashes, markers_lookup, resolver, skipped = actually_resolve_deps( + ( + results, + hashes, + markers_lookup, + resolver, + skipped, + ) = actually_resolve_deps( deps, index_lookup, markers_lookup, @@ -989,6 +1095,7 @@ def resolve_deps( def get_pipenv_sitedir(): # type: () -> Optional[str] import pkg_resources + site_dir = next( iter(d for d in pkg_resources.working_set if d.key.lower() == "pipenv"), None ) diff --git a/pipenv/utils/shell.py b/pipenv/utils/shell.py index 4360f0de..5411c6af 100644 --- a/pipenv/utils/shell.py +++ b/pipenv/utils/shell.py @@ -1,5 +1,6 @@ import errno import os +import posixpath import re import shlex import shutil @@ -8,17 +9,16 @@ import sys import warnings from contextlib import contextmanager from functools import lru_cache -import posixpath from pathlib import Path -from .constants import SCHEME_LIST -from .processes import subprocess_run from pipenv import environments from pipenv.vendor.vistir.compat import ResourceWarning +from .constants import SCHEME_LIST +from .processes import subprocess_run if environments.MYPY_RUNNING: - from typing import Text + from typing import Text # noqa @lru_cache() @@ -51,6 +51,7 @@ def make_posix(path): def get_pipenv_dist(pkg="pipenv", pipenv_site=None): from pipenv.resolver import find_site_path + pipenv_libdir = os.path.dirname(os.path.abspath(__file__)) if pipenv_site is None: pipenv_site = os.path.dirname(pipenv_libdir) @@ -80,8 +81,8 @@ def looks_like_dir(path): def load_path(python): import json - from pathlib import Path + python = Path(python).as_posix() json_dump_commmand = '"import json, sys; print(json.dumps(sys.path));"' c = subprocess_run([python, "-c", json_dump_commmand]) @@ -96,9 +97,9 @@ def path_to_url(path): def normalize_path(path): - return os.path.expandvars(os.path.expanduser( - os.path.normcase(os.path.normpath(os.path.abspath(str(path)))) - )) + return os.path.expandvars( + os.path.expanduser(os.path.normcase(os.path.normpath(os.path.abspath(str(path))))) + ) def get_windows_path(*args): @@ -157,7 +158,7 @@ def walk_up(bottom): def find_requirements(max_depth=3): """Returns the path of a requirements.txt file in parent directories.""" i = 0 - for c, d, f in walk_up(os.getcwd()): + for c, _, _ in walk_up(os.getcwd()): i += 1 if i < max_depth: r = os.path.join(c, "requirements.txt") @@ -183,13 +184,12 @@ def temp_environ(): def escape_cmd(cmd): if any(special_char in cmd for special_char in ["<", ">", "&", ".", "^", "|", "?"]): - cmd = f'\"{cmd}\"' + cmd = f'"{cmd}"' return cmd def safe_expandvars(value): - """Call os.path.expandvars if value is a string, otherwise do nothing. - """ + """Call os.path.expandvars if value is a string, otherwise do nothing.""" if isinstance(value, str): return os.path.expandvars(value) return value @@ -239,8 +239,8 @@ def is_virtual_environment(path): """ if not path.is_dir(): return False - for bindir_name in ('bin', 'Scripts'): - for python in path.joinpath(bindir_name).glob('python*'): + for bindir_name in ("bin", "Scripts"): + for python in path.joinpath(bindir_name).glob("python*"): try: exeness = python.is_file() and os.access(str(python), os.X_OK) except OSError: @@ -252,10 +252,17 @@ def is_virtual_environment(path): def mkdir_p(newdir): """works the way a good mkdir should :) + <<<<<<< HEAD - already exists, silently complete - regular file in the way, raise an exception - parent directory(ies) does not exist, make them as well From: http://code.activestate.com/recipes/82465-a-friendly-mkdir/ + ======= + - already exists, silently complete + - regular file in the way, raise an exception + - parent directory(ies) does not exist, make them as well + From: http://code.activestate.com/recipes/82465-a-friendly-mkdir/ + >>>>>>> main """ if os.path.isdir(newdir): pass @@ -295,19 +302,18 @@ def find_python(finder, line=None): """ if line and not isinstance(line, str): - raise TypeError( - f"Invalid python search type: expected string, received {line!r}" - ) + raise TypeError(f"Invalid python search type: expected string, received {line!r}") if line and os.path.isabs(line): if os.name == "nt": line = make_posix(line) return line if not finder: from pipenv.vendor.pythonfinder import Finder + finder = Finder(global_search=True) if not line: result = next(iter(finder.find_all_python_versions()), None) - elif line and line[0].isdigit() or re.match(r'[\d\.]+', line): + elif line and line[0].isdigit() or re.match(r"[\d\.]+", line): result = finder.find_python_version(line) else: result = finder.find_python_version(name=line) @@ -339,9 +345,13 @@ def is_python_command(line): raise TypeError(f"Not a valid command to check: {line!r}") from pipenv.vendor.pythonfinder.utils import PYTHON_IMPLEMENTATIONS - is_version = re.match(r'\d+(\.\d+)*', line) - if (line.startswith("python") or is_version - or any(line.startswith(v) for v in PYTHON_IMPLEMENTATIONS)): + + is_version = re.match(r"\d+(\.\d+)*", line) + if ( + line.startswith("python") + or is_version + or any(line.startswith(v) for v in PYTHON_IMPLEMENTATIONS) + ): return True # we are less sure about this but we can guess if line.startswith("py"): @@ -405,9 +415,7 @@ def handle_remove_readonly(func, path, exc): Windows source repo folders are read-only by default, so this error handler attempts to set them as writeable and then proceed with deletion.""" # Check for read-only attribute - default_warning_message = ( - "Unable to remove file due to permissions restriction: {!r}" - ) + default_warning_message = "Unable to remove file due to permissions restriction: {!r}" # split the initial exception out into its type, exception, and traceback exc_type, exc_exception, exc_tb = exc if is_readonly_path(path): diff --git a/pipenv/utils/spinner.py b/pipenv/utils/spinner.py index edcf0282..b1c7cda2 100644 --- a/pipenv/utils/spinner.py +++ b/pipenv/utils/spinner.py @@ -5,6 +5,7 @@ import contextlib def create_spinner(text, setting, nospin=None, spinner_name=None): from pipenv.vendor.vistir import spin from pipenv.vendor.vistir.misc import fs_str + if not spinner_name: spinner_name = setting.PIPENV_SPINNER if nospin is None: @@ -12,6 +13,7 @@ def create_spinner(text, setting, nospin=None, spinner_name=None): with spin.create_spinner( spinner_name=spinner_name, start_text=fs_str(text), - nospin=nospin, write_to_stdout=False + nospin=nospin, + write_to_stdout=False, ) as sp: yield sp diff --git a/pipenv/utils/toml.py b/pipenv/utils/toml.py index 21e89dfe..6c3e814f 100644 --- a/pipenv/utils/toml.py +++ b/pipenv/utils/toml.py @@ -27,6 +27,7 @@ def cleanup_toml(tml): def convert_toml_outline_tables(parsed): """Converts all outline tables to inline tables.""" + def convert_tomlkit_table(section): if isinstance(section, tomlkit.items.Table): body = section.value._body @@ -35,14 +36,18 @@ def convert_toml_outline_tables(parsed): for key, value in body: if not key: continue - if hasattr(value, "keys") and not isinstance(value, tomlkit.items.InlineTable): + if hasattr(value, "keys") and not isinstance( + value, tomlkit.items.InlineTable + ): table = tomlkit.inline_table() table.update(value.value) section[key.key] = table def convert_toml_table(section): for package, value in section.items(): - if hasattr(value, "keys") and not isinstance(value, toml.decoder.InlineTableDict): + if hasattr(value, "keys") and not isinstance( + value, toml.decoder.InlineTableDict + ): table = toml.TomlDecoder().get_empty_inline_table() table.update(value) section[package] = table diff --git a/pyproject.toml b/pyproject.toml index 71c94f3d..794472ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,3 +70,8 @@ template = "news/towncrier_template.rst" directory = "removal" name = "Removals and Deprecations" showcontent = true + + [[tool.towncrier.type]] + directory = "process" + name = "Relates to dev process changes" + showcontent = true diff --git a/setup.py b/setup.py index 3d56518c..192c2cbf 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ import os import sys from shutil import rmtree -from setuptools import find_packages, setup, Command +from setuptools import Command, find_packages, setup here = os.path.abspath(os.path.dirname(__file__)) @@ -25,7 +25,7 @@ required = [ "certifi", "setuptools>=36.2.1", "virtualenv-clone>=0.2.5", - "virtualenv" + "virtualenv", ] extras = { "dev": [ @@ -113,7 +113,7 @@ setup( version=about["__version__"], description="Python Development Workflow for Humans.", long_description=long_description, - long_description_content_type='text/markdown', + long_description_content_type="text/markdown", author="Pipenv maintainer team", author_email="distutils-sig@python.org", url="https://github.com/pypa/pipenv", diff --git a/tasks/__init__.py b/tasks/__init__.py index 07a054a9..961ec1ef 100644 --- a/tasks/__init__.py +++ b/tasks/__init__.py @@ -6,7 +6,6 @@ import invoke from . import release, vendoring - ROOT = Path(".").parent.parent.absolute() ns = invoke.Collection(vendoring, release, release.clean_mdchangelog) diff --git a/tasks/release.py b/tasks/release.py index 058c546e..8e796b64 100644 --- a/tasks/release.py +++ b/tasks/release.py @@ -2,11 +2,10 @@ import datetime import os import pathlib import re -import sys import subprocess +import sys import invoke - from parver import Version from pipenv.__version__ import __version__ @@ -14,7 +13,6 @@ from pipenv.vendor.vistir.contextmanagers import temp_environ from .vendoring import _get_git_root, drop_dir - VERSION_FILE = "pipenv/__version__.py" ROOT = pathlib.Path(".").parent.parent.absolute() PACKAGE_NAME = "pipenv" @@ -49,8 +47,7 @@ def get_build_dir(ctx): def _render_log(): - """Totally tap into Towncrier internals to get an in-memory result. - """ + """Totally tap into Towncrier internals to get an in-memory result.""" rendered = subprocess.check_output(["towncrier", "--draft"]).decode("utf-8") return rendered @@ -66,7 +63,9 @@ release_help = { @invoke.task(help=release_help) -def release(ctx, manual=False, local=False, dry_run=False, pre=False, tag=None, month_offset="0"): +def release( + ctx, manual=False, local=False, dry_run=False, pre=False, tag=None, month_offset="0" +): trunc_month = False if pre: trunc_month = True @@ -77,7 +76,7 @@ def release(ctx, manual=False, local=False, dry_run=False, pre=False, tag=None, pre=pre, tag=tag, month_offset=month_offset, - trunc_month=trunc_month + trunc_month=trunc_month, ) tag_content = _render_log() if dry_run: @@ -93,9 +92,7 @@ def release(ctx, manual=False, local=False, dry_run=False, pre=False, tag=None, ctx.run(f"git add {get_version_file(ctx).as_posix()}") else: ctx.run("towncrier") - ctx.run( - f"git add CHANGELOG.rst news/ {get_version_file(ctx).as_posix()}" - ) + ctx.run(f"git add CHANGELOG.rst news/ {get_version_file(ctx).as_posix()}") log("removing changelog draft if present") draft_changelog = pathlib.Path("CHANGELOG.draft.rst") if draft_changelog.exists(): @@ -150,11 +147,9 @@ def build_dists(ctx): log("Building sdist using %s ...." % executable) os.environ["PIPENV_PYTHON"] = py_version ctx.run("pipenv install --dev", env=env) - ctx.run( - "pipenv run pip install -e . --upgrade --upgrade-strategy=eager", env=env - ) + ctx.run("pipenv run pip install -e . --upgrade --upgrade-strategy=eager", env=env) log("Building wheel using python %s ...." % py_version) - ctx.run(f"pipenv run python setup.py sdist bdist_wheel", env=env) + ctx.run("pipenv run python setup.py sdist bdist_wheel", env=env) @invoke.task(build_dists) @@ -266,9 +261,11 @@ def date_offset(dt, month_offset=0, day_offset=0, truncate=False): "month": dt.month + month_offset, "year": dt.year + year_offset, } - log("Getting updated date from date: {} using month offset: {} and year offset {}".format( - dt, new_month, replace_args["year"] - )) + log( + "Getting updated date from date: {} using month offset: {} and year offset {}".format( + dt, new_month, replace_args["year"] + ) + ) if day_offset: dt = dt + datetime.timedelta(days=day_offset) log(f"updated date using day offset: {day_offset} => {dt}") @@ -279,7 +276,16 @@ def date_offset(dt, month_offset=0, day_offset=0, truncate=False): @invoke.task -def bump_version(ctx, dry_run=False, dev=False, pre=False, tag=None, commit=False, month_offset="0", trunc_month=False): +def bump_version( + ctx, + dry_run=False, + dev=False, + pre=False, + tag=None, + commit=False, + month_offset="0", + trunc_month=False, +): current_version = Version.parse(__version__) current_date = datetime.date(*current_version.release) today = datetime.date.today() @@ -295,10 +301,7 @@ def bump_version(ctx, dry_run=False, dev=False, pre=False, tag=None, commit=Fals ): day_offset = 1 target_day = date_offset( - today, - month_offset=month_offset, - day_offset=day_offset, - truncate=trunc_month + today, month_offset=month_offset, day_offset=day_offset, truncate=trunc_month ) log(f"target_day: {target_day}") target_timetuple = target_day.timetuple()[:3] diff --git a/tasks/vendoring/__init__.py b/tasks/vendoring/__init__.py index d5d27417..1861c8b1 100644 --- a/tasks/vendoring/__init__.py +++ b/tasks/vendoring/__init__.py @@ -3,26 +3,20 @@ """"Vendoring script, python 3.5 needed""" import itertools -import os import re import shutil - -# from tempfile import TemporaryDirectory import tarfile import zipfile - from pathlib import Path +from tempfile import TemporaryDirectory import bs4 import invoke import requests - from urllib3.util import parse_url as urllib3_parse -from tempfile import TemporaryDirectory -from pipenv.vendor.vistir.contextmanagers import open_file import pipenv.vendor.parse as parse - +from pipenv.vendor.vistir.contextmanagers import open_file TASK_NAME = "update" @@ -190,10 +184,16 @@ def rewrite_file_imports(item, vendored_libs): for lib, to_lib in vendored_libs.items(): text = re.sub( - r"^(?m)(\s*)import %s((?:\.\S*)?\s+as)" % lib, r"\1import %s\2" % to_lib, text, + r"^(?m)(\s*)import %s((?:\.\S*)?\s+as)" % lib, + r"\1import %s\2" % to_lib, + text, ) text = re.sub(r"^(?m)(\s*)from %s([\s\.]+)" % lib, r"\1from %s\2" % to_lib, text) - text = re.sub(r"^(?m)(\s*)import %s(\s*[,\n#])" % lib, r"\1import %s as %s\2" % (to_lib, lib), text) + text = re.sub( + r"^(?m)(\s*)import %s(\s*[,\n#])" % lib, + r"\1import %s as %s\2" % (to_lib, lib), + text, + ) for pattern, sub in GLOBAL_REPLACEMENT: text = re.sub(pattern, sub, text) item.write_text(text, encoding="utf-8") @@ -209,7 +209,7 @@ def _recursive_write_to_zip(zf, path, root=None): return if root is None: if not path.is_dir(): - raise ValueError('root is required for non-directory path') + raise ValueError("root is required for non-directory path") root = path if not path.is_dir(): zf.write(str(path), str(path.relative_to(root))) @@ -251,7 +251,7 @@ def write_backport_imports(ctx, vendor_dir): def _ensure_package_in_requirements(ctx, requirements_file, package): requirement = None log("using requirements file: %s" % requirements_file) - req_file_lines = [l for l in requirements_file.read_text().splitlines()] + req_file_lines = [line for line in requirements_file.read_text().splitlines()] if package: match = [r for r in req_file_lines if r.strip().lower().startswith(package)] matched_req = None @@ -274,8 +274,10 @@ def _ensure_package_in_requirements(ctx, requirements_file, package): def install_pyyaml(ctx, vendor_dir): with TemporaryDirectory(prefix="pipenv-", suffix="-yaml") as download_dir: - pip_command = "pip download --no-binary=:all: --no-clean --no-deps -d {} pyyaml".format( - download_dir + pip_command = ( + "pip download --no-binary=:all: --no-clean --no-deps -d {} pyyaml".format( + download_dir + ) ) log(f"downloading deps via pip: {pip_command}") ctx.run(pip_command) @@ -291,7 +293,9 @@ def install_pyyaml(ctx, vendor_dir): if yaml_dir.exists(): drop_dir(yaml_dir) path_dict["current_path"].rename(path_dict["destination"]) - path_dict["destination"].joinpath("LICENSE").write_text(extracted.joinpath("LICENSE").read_text()) + path_dict["destination"].joinpath("LICENSE").write_text( + extracted.joinpath("LICENSE").read_text() + ) def install(ctx, vendor_dir, package=None): @@ -305,7 +309,8 @@ def install(ctx, vendor_dir, package=None): # the chain. ctx.run( "pip install -t {} --no-compile --no-deps --upgrade {}".format( - vendor_dir.as_posix(), requirement, + vendor_dir.as_posix(), + requirement, ) ) # read licenses from distinfo files if possible @@ -319,22 +324,16 @@ def install(ctx, vendor_dir, package=None): license_file.read_text() ) elif vendor_dir.joinpath(f"{pkg}.py").exists(): - vendor_dir.joinpath(f"{pkg}.LICENSE").write_text( - license_file.read_text() - ) + vendor_dir.joinpath(f"{pkg}.LICENSE").write_text(license_file.read_text()) else: pkg = pkg.replace("-", "?").replace("_", "?") - matched_path = next( - iter(pth for pth in vendor_dir.glob(f"{pkg}*")), None - ) + matched_path = next(iter(pth for pth in vendor_dir.glob(f"{pkg}*")), None) if matched_path is not None: if matched_path.is_dir(): target = vendor_dir.joinpath(matched_path).joinpath("LICENSE") else: target = vendor_dir.joinpath(f"{matched_path}.LICENSE") - target.write_text( - license_file.read_text() - ) + target.write_text(license_file.read_text()) def post_install_cleanup(ctx, vendor_dir): @@ -460,7 +459,7 @@ def packages_missing_licenses( ".".join(lic) for lic in itertools.product(("LICENSE", "LICENSE-MIT"), LICENSE_EXTS) ] - for i, req in enumerate(requirements): + for _, req in enumerate(requirements): if req.startswith("git+"): pkg = req.strip().split("#egg=")[1] else: @@ -549,9 +548,7 @@ def download_licenses( if "." in backend: backend, _, _ = backend.partition(".") ctx.run(f"pip install {backend}") - ctx.run( - f"{cmd} --no-build-isolation -d {tmp_dir.as_posix()} {req}" - ) + ctx.run(f"{cmd} --no-build-isolation -d {tmp_dir.as_posix()} {req}") for sdist in tmp_dir.iterdir(): extract_license(vendor_dir, sdist) drop_dir(tmp_dir) @@ -720,9 +717,7 @@ def unpin_and_copy_requirements(ctx, requirement_file, name="requirements.txt"): ctx.run("pipenv --rm", env=env, hide=True) result = list(sorted(line.strip() for line in result.splitlines()[1:])) new_requirements = requirement_file.parent.joinpath(name) - requirement_file.rename( - requirement_file.parent.joinpath(f"{name}.bak") - ) + requirement_file.rename(requirement_file.parent.joinpath(f"{name}.bak")) new_requirements.write_text("\n".join(result)) return result