From 53682cffeb4641e1678adf376d3dfad91a4907f4 Mon Sep 17 00:00:00 2001
From: Matt Davis <=>
Date: Sun, 20 Feb 2022 11:28:37 -0500
Subject: [PATCH 01/30] check point progress on only bringing in pip==22.0.3
---
.../notpip/{_vendor/msgpack => }/COPYING | 0
.../notpip/{_vendor/webencodings => }/LICENSE | 0
.../{_vendor/packaging => }/LICENSE.APACHE | 0
.../{_vendor/packaging => }/LICENSE.BSD | 0
.../notpip/{_vendor/idna => }/LICENSE.md | 0
.../notpip/{_vendor/urllib3 => }/LICENSE.txt | 0
pipenv/patched/notpip/__init__.py | 2 +-
pipenv/patched/notpip/_internal/build_env.py | 193 +-
pipenv/patched/notpip/_internal/cache.py | 117 +-
.../notpip/_internal/cli/autocompletion.py | 10 +-
.../notpip/_internal/cli/base_command.py | 125 +-
.../notpip/_internal/cli/cmdoptions.py | 45 +-
.../notpip/_internal/cli/progress_bars.py | 77 +-
.../notpip/_internal/cli/req_command.py | 61 +-
.../notpip/_internal/commands/__init__.py | 163 +-
.../notpip/_internal/commands/cache.py | 105 +-
.../notpip/_internal/commands/check.py | 14 +-
.../notpip/_internal/commands/completion.py | 49 +-
.../_internal/commands/configuration.py | 70 +-
.../notpip/_internal/commands/debug.py | 88 +-
.../notpip/_internal/commands/download.py | 19 +-
.../notpip/_internal/commands/freeze.py | 67 +-
.../patched/notpip/_internal/commands/hash.py | 22 +-
.../patched/notpip/_internal/commands/help.py | 2 +-
.../notpip/_internal/commands/index.py | 24 +-
.../notpip/_internal/commands/install.py | 279 +-
.../patched/notpip/_internal/commands/list.py | 172 +-
.../notpip/_internal/commands/search.py | 72 +-
.../patched/notpip/_internal/commands/show.py | 106 +-
.../notpip/_internal/commands/uninstall.py | 41 +-
.../notpip/_internal/commands/wheel.py | 42 +-
.../patched/notpip/_internal/configuration.py | 163 +-
.../notpip/_internal/distributions/base.py | 6 +-
.../_internal/distributions/installed.py | 8 +-
.../notpip/_internal/distributions/sdist.py | 96 +-
.../notpip/_internal/distributions/wheel.py | 25 +-
pipenv/patched/notpip/_internal/exceptions.py | 535 +-
.../notpip/_internal/index/collector.py | 211 +-
.../notpip/_internal/index/package_finder.py | 212 +-
.../notpip/_internal/locations/__init__.py | 268 +-
.../notpip/_internal/locations/_distutils.py | 24 +-
.../notpip/_internal/locations/_sysconfig.py | 53 +-
pipenv/patched/notpip/_internal/main.py | 3 +-
.../notpip/_internal/metadata/__init__.py | 20 +-
.../patched/notpip/_internal/metadata/base.py | 338 +-
.../_internal/metadata/pkg_resources.py | 193 +-
.../notpip/_internal/models/candidate.py | 15 +-
.../notpip/_internal/models/direct_url.py | 14 +-
.../notpip/_internal/models/format_control.py | 44 +-
.../patched/notpip/_internal/models/index.py | 16 +-
.../patched/notpip/_internal/models/link.py | 86 +-
.../patched/notpip/_internal/models/scheme.py | 2 +-
.../notpip/_internal/models/search_scope.py | 35 +-
.../_internal/models/selection_prefs.py | 9 +-
.../notpip/_internal/models/target_python.py | 17 +-
.../patched/notpip/_internal/models/wheel.py | 21 +-
.../patched/notpip/_internal/network/auth.py | 19 +-
.../patched/notpip/_internal/network/cache.py | 2 +-
.../notpip/_internal/network/download.py | 5 +-
.../notpip/_internal/network/lazy_wheel.py | 14 +-
.../notpip/_internal/network/session.py | 28 +-
.../_internal/operations/build/metadata.py | 22 +-
.../operations/build/metadata_editable.py | 41 +
.../operations/build/metadata_legacy.py | 62 +-
.../_internal/operations/build/wheel.py | 17 +-
.../operations/build/wheel_editable.py | 46 +
.../operations/build/wheel_legacy.py | 68 +-
.../notpip/_internal/operations/check.py | 52 +-
.../notpip/_internal/operations/freeze.py | 193 +-
.../operations/install/editable_legacy.py | 26 +-
.../_internal/operations/install/legacy.py | 66 +-
.../_internal/operations/install/wheel.py | 373 +-
.../notpip/_internal/operations/prepare.py | 291 +-
pipenv/patched/notpip/_internal/pyproject.py | 79 +-
.../patched/notpip/_internal/req/__init__.py | 16 +-
.../notpip/_internal/req/constructors.py | 172 +-
.../patched/notpip/_internal/req/req_file.py | 78 +-
.../notpip/_internal/req/req_install.py | 371 +-
.../patched/notpip/_internal/req/req_set.py | 69 +-
.../notpip/_internal/req/req_tracker.py | 24 +-
.../notpip/_internal/req/req_uninstall.py | 384 +-
.../notpip/_internal/resolution/base.py | 6 +-
.../_internal/resolution/legacy/resolver.py | 54 +-
.../_internal/resolution/resolvelib/base.py | 5 +-
.../resolution/resolvelib/candidates.py | 99 +-
.../resolution/resolvelib/factory.py | 85 +-
.../resolution/resolvelib/found_candidates.py | 25 +-
.../resolution/resolvelib/provider.py | 97 +-
.../resolution/resolvelib/reporter.py | 5 +-
.../resolution/resolvelib/requirements.py | 4 +-
.../resolution/resolvelib/resolver.py | 100 +-
.../notpip/_internal/self_outdated_check.py | 42 +-
.../patched/notpip/_internal/utils/appdirs.py | 39 +-
.../_internal/utils/compatibility_tags.py | 11 +-
.../notpip/_internal/utils/deprecation.py | 56 +-
.../_internal/utils/direct_url_helpers.py | 8 +
.../notpip/_internal/utils/egg_link.py | 75 +
.../notpip/_internal/utils/filetypes.py | 13 +-
.../patched/notpip/_internal/utils/glibc.py | 12 +-
.../patched/notpip/_internal/utils/hashes.py | 47 +-
.../_internal/utils/inject_securetransport.py | 3 +-
.../patched/notpip/_internal/utils/logging.py | 234 +-
pipenv/patched/notpip/_internal/utils/misc.py | 317 +-
.../patched/notpip/_internal/utils/models.py | 24 +-
.../notpip/_internal/utils/packaging.py | 80 +-
.../notpip/_internal/utils/parallel.py | 101 -
.../notpip/_internal/utils/pkg_resources.py | 40 -
.../_internal/utils/setuptools_build.py | 136 +-
.../notpip/_internal/utils/subprocess.py | 141 +-
.../notpip/_internal/utils/temp_dir.py | 56 +-
.../notpip/_internal/utils/unpacking.py | 33 +-
pipenv/patched/notpip/_internal/utils/urls.py | 9 +-
.../notpip/_internal/utils/virtualenv.py | 21 +-
.../patched/notpip/_internal/utils/wheel.py | 67 +-
pipenv/patched/notpip/_internal/vcs/bazaar.py | 73 +-
pipenv/patched/notpip/_internal/vcs/git.py | 230 +-
.../patched/notpip/_internal/vcs/mercurial.py | 97 +-
.../notpip/_internal/vcs/subversion.py | 151 +-
.../notpip/_internal/vcs/versioncontrol.py | 337 +-
.../patched/notpip/_internal/wheel_builder.py | 193 +-
pipenv/patched/notpip/_vendor/__init__.py | 2 +-
.../notpip/_vendor/appdirs.LICENSE.txt | 23 -
pipenv/patched/notpip/_vendor/appdirs.py | 633 --
.../notpip/_vendor/cachecontrol/LICENSE.txt | 15 -
.../notpip/_vendor/cachecontrol/__init__.py | 9 +-
.../notpip/_vendor/cachecontrol/_cmd.py | 4 +
.../notpip/_vendor/cachecontrol/adapter.py | 10 +-
.../notpip/_vendor/cachecontrol/cache.py | 8 +-
.../_vendor/cachecontrol/caches/__init__.py | 4 +
.../_vendor/cachecontrol/caches/file_cache.py | 6 +-
.../cachecontrol/caches/redis_cache.py | 4 +
.../notpip/_vendor/cachecontrol/compat.py | 5 +-
.../notpip/_vendor/cachecontrol/controller.py | 67 +-
.../_vendor/cachecontrol/filewrapper.py | 39 +-
.../notpip/_vendor/cachecontrol/heuristics.py | 4 +
.../notpip/_vendor/cachecontrol/serialize.py | 22 +-
.../notpip/_vendor/cachecontrol/wrapper.py | 4 +
pipenv/patched/notpip/_vendor/certifi/LICENSE | 21 -
.../notpip/_vendor/certifi/__init__.py | 2 +-
.../patched/notpip/_vendor/certifi/cacert.pem | 105 +
pipenv/patched/notpip/_vendor/chardet/LICENSE | 504 --
.../notpip/_vendor/colorama/LICENSE.txt | 27 -
.../notpip/_vendor/distlib/__init__.py | 2 +-
.../_vendor/distlib/_backport/__init__.py | 6 -
.../notpip/_vendor/distlib/_backport/misc.py | 41 -
.../_vendor/distlib/_backport/shutil.py | 764 --
.../_vendor/distlib/_backport/sysconfig.cfg | 84 -
.../_vendor/distlib/_backport/sysconfig.py | 786 --
.../_vendor/distlib/_backport/tarfile.py | 2607 ------
.../patched/notpip/_vendor/distlib/compat.py | 52 +-
.../notpip/_vendor/distlib/database.py | 50 +-
.../notpip/_vendor/distlib/locators.py | 2 +-
.../patched/notpip/_vendor/distlib/markers.py | 26 +-
.../patched/notpip/_vendor/distlib/scripts.py | 10 +-
pipenv/patched/notpip/_vendor/distlib/t32.exe | Bin 96768 -> 96768 bytes
.../notpip/_vendor/distlib/t64-arm.exe | Bin 0 -> 180736 bytes
pipenv/patched/notpip/_vendor/distlib/t64.exe | Bin 105984 -> 106496 bytes
pipenv/patched/notpip/_vendor/distlib/util.py | 85 +-
.../patched/notpip/_vendor/distlib/version.py | 2 +-
pipenv/patched/notpip/_vendor/distlib/w32.exe | Bin 90112 -> 90112 bytes
.../notpip/_vendor/distlib/w64-arm.exe | Bin 0 -> 166400 bytes
pipenv/patched/notpip/_vendor/distlib/w64.exe | Bin 99840 -> 100864 bytes
.../patched/notpip/_vendor/distlib/wheel.py | 5 +-
pipenv/patched/notpip/_vendor/distro.py | 504 +-
.../patched/notpip/_vendor/html5lib/LICENSE | 20 -
pipenv/patched/notpip/_vendor/idna/codec.py | 15 +-
pipenv/patched/notpip/_vendor/idna/compat.py | 9 +-
pipenv/patched/notpip/_vendor/idna/core.py | 66 +-
.../patched/notpip/_vendor/idna/idnadata.py | 137 +-
.../patched/notpip/_vendor/idna/intranges.py | 12 +-
.../notpip/_vendor/idna/package_data.py | 2 +-
.../patched/notpip/_vendor/idna/uts46data.py | 882 +-
.../notpip/_vendor/msgpack/_version.py | 2 +-
.../notpip/_vendor/msgpack/fallback.py | 271 +-
.../patched/notpip/_vendor/packaging/LICENSE | 3 -
.../notpip/_vendor/packaging/__about__.py | 2 +-
.../notpip/_vendor/packaging/_musllinux.py | 2 +-
.../notpip/_vendor/packaging/_structures.py | 6 -
.../notpip/_vendor/packaging/specifiers.py | 30 +-
.../patched/notpip/_vendor/packaging/tags.py | 23 +-
pipenv/patched/notpip/_vendor/pep517/LICENSE | 21 -
.../patched/notpip/_vendor/pep517/__init__.py | 2 +-
pipenv/patched/notpip/_vendor/pep517/build.py | 2 +-
pipenv/patched/notpip/_vendor/pep517/check.py | 2 +-
.../patched/notpip/_vendor/pep517/compat.py | 11 +-
.../patched/notpip/_vendor/pep517/envbuild.py | 2 +-
.../_vendor/pep517/in_process/_in_process.py | 14 +
.../patched/notpip/_vendor/pep517/wrappers.py | 4 +
.../notpip/_vendor/pkg_resources/LICENSE | 19 -
.../notpip/_vendor/pkg_resources/__init__.py | 4 +-
.../notpip/_vendor/platformdirs/__init__.py | 331 +
.../notpip/_vendor/platformdirs/__main__.py | 46 +
.../notpip/_vendor/platformdirs/android.py | 119 +
.../notpip/_vendor/platformdirs/api.py | 156 +
.../notpip/_vendor/platformdirs/macos.py | 64 +
.../notpip/_vendor/platformdirs/unix.py | 181 +
.../notpip/_vendor/platformdirs/version.py | 4 +
.../notpip/_vendor/platformdirs/windows.py | 182 +
.../patched/notpip/_vendor/progress/LICENSE | 13 -
.../notpip/_vendor/progress/__init__.py | 52 +-
pipenv/patched/notpip/_vendor/progress/bar.py | 8 +-
.../patched/notpip/_vendor/progress/colors.py | 79 +
.../notpip/_vendor/progress/counter.py | 14 +-
.../notpip/_vendor/progress/spinner.py | 6 +-
.../notpip/_vendor/pygments/__init__.py | 83 +
.../notpip/_vendor/pygments/__main__.py | 17 +
.../notpip/_vendor/pygments/cmdline.py | 663 ++
.../notpip/_vendor/pygments/console.py | 70 +
.../patched/notpip/_vendor/pygments/filter.py | 71 +
.../_vendor/pygments/filters/__init__.py | 937 +++
.../notpip/_vendor/pygments/formatter.py | 94 +
.../_vendor/pygments/formatters/__init__.py | 153 +
.../_vendor/pygments/formatters/_mapping.py | 84 +
.../_vendor/pygments/formatters/bbcode.py | 108 +
.../_vendor/pygments/formatters/groff.py | 168 +
.../_vendor/pygments/formatters/html.py | 983 +++
.../notpip/_vendor/pygments/formatters/img.py | 641 ++
.../notpip/_vendor/pygments/formatters/irc.py | 179 +
.../_vendor/pygments/formatters/latex.py | 511 ++
.../_vendor/pygments/formatters/other.py | 161 +
.../pygments/formatters/pangomarkup.py | 83 +
.../notpip/_vendor/pygments/formatters/rtf.py | 146 +
.../notpip/_vendor/pygments/formatters/svg.py | 188 +
.../_vendor/pygments/formatters/terminal.py | 127 +
.../pygments/formatters/terminal256.py | 338 +
.../patched/notpip/_vendor/pygments/lexer.py | 879 ++
.../_vendor/pygments/lexers/__init__.py | 341 +
.../_vendor/pygments/lexers/_mapping.py | 580 ++
.../notpip/_vendor/pygments/lexers/python.py | 1188 +++
.../notpip/_vendor/pygments/modeline.py | 43 +
.../patched/notpip/_vendor/pygments/plugin.py | 69 +
.../notpip/_vendor/pygments/regexopt.py | 91 +
.../notpip/_vendor/pygments/scanner.py | 104 +
.../notpip/_vendor/pygments/sphinxext.py | 155 +
.../patched/notpip/_vendor/pygments/style.py | 197 +
.../_vendor/pygments/styles/__init__.py | 93 +
.../patched/notpip/_vendor/pygments/token.py | 212 +
.../notpip/_vendor/pygments/unistring.py | 153 +
.../patched/notpip/_vendor/pygments/util.py | 308 +
.../patched/notpip/_vendor/pyparsing.LICENSE | 18 -
pipenv/patched/notpip/_vendor/pyparsing.py | 7107 -----------------
.../notpip/_vendor/pyparsing/__init__.py | 328 +
.../notpip/_vendor/pyparsing/actions.py | 207 +
.../notpip/_vendor/pyparsing/common.py | 424 +
.../patched/notpip/_vendor/pyparsing/core.py | 5789 ++++++++++++++
.../_vendor/pyparsing/diagram/__init__.py | 593 ++
.../notpip/_vendor/pyparsing/exceptions.py | 267 +
.../notpip/_vendor/pyparsing/helpers.py | 1069 +++
.../notpip/_vendor/pyparsing/results.py | 760 ++
.../notpip/_vendor/pyparsing/testing.py | 331 +
.../notpip/_vendor/pyparsing/unicode.py | 332 +
.../patched/notpip/_vendor/pyparsing/util.py | 235 +
.../patched/notpip/_vendor/requests/LICENSE | 175 -
.../notpip/_vendor/requests/__init__.py | 2 +-
.../notpip/_vendor/requests/__version__.py | 6 +-
.../notpip/_vendor/requests/adapters.py | 9 +-
.../patched/notpip/_vendor/requests/compat.py | 3 +-
.../notpip/_vendor/requests/exceptions.py | 10 +-
.../patched/notpip/_vendor/requests/models.py | 33 +-
.../notpip/_vendor/requests/sessions.py | 24 +-
.../patched/notpip/_vendor/requests/utils.py | 63 +-
.../patched/notpip/_vendor/resolvelib/LICENSE | 13 -
.../notpip/_vendor/resolvelib/__init__.py | 4 +-
.../notpip/_vendor/resolvelib/providers.py | 11 +-
.../notpip/_vendor/resolvelib/reporters.py | 6 +
.../notpip/_vendor/resolvelib/resolvers.py | 19 +-
.../patched/notpip/_vendor/rich/__init__.py | 172 +
.../patched/notpip/_vendor/rich/__main__.py | 280 +
.../notpip/_vendor/rich/_cell_widths.py | 451 ++
.../notpip/_vendor/rich/_emoji_codes.py | 3610 +++++++++
.../notpip/_vendor/rich/_emoji_replace.py | 32 +
.../patched/notpip/_vendor/rich/_extension.py | 10 +
.../patched/notpip/_vendor/rich/_inspect.py | 210 +
.../notpip/_vendor/rich/_log_render.py | 94 +
pipenv/patched/notpip/_vendor/rich/_loop.py | 43 +
.../patched/notpip/_vendor/rich/_lru_cache.py | 34 +
.../patched/notpip/_vendor/rich/_palettes.py | 309 +
pipenv/patched/notpip/_vendor/rich/_pick.py | 17 +
pipenv/patched/notpip/_vendor/rich/_ratio.py | 160 +
.../patched/notpip/_vendor/rich/_spinners.py | 848 ++
pipenv/patched/notpip/_vendor/rich/_stack.py | 16 +
pipenv/patched/notpip/_vendor/rich/_timer.py | 19 +
.../patched/notpip/_vendor/rich/_windows.py | 72 +
pipenv/patched/notpip/_vendor/rich/_wrap.py | 55 +
pipenv/patched/notpip/_vendor/rich/abc.py | 33 +
pipenv/patched/notpip/_vendor/rich/align.py | 312 +
pipenv/patched/notpip/_vendor/rich/ansi.py | 228 +
pipenv/patched/notpip/_vendor/rich/bar.py | 94 +
pipenv/patched/notpip/_vendor/rich/box.py | 483 ++
pipenv/patched/notpip/_vendor/rich/cells.py | 147 +
pipenv/patched/notpip/_vendor/rich/color.py | 581 ++
.../notpip/_vendor/rich/color_triplet.py | 38 +
pipenv/patched/notpip/_vendor/rich/columns.py | 187 +
pipenv/patched/notpip/_vendor/rich/console.py | 2211 +++++
.../patched/notpip/_vendor/rich/constrain.py | 37 +
.../patched/notpip/_vendor/rich/containers.py | 167 +
pipenv/patched/notpip/_vendor/rich/control.py | 175 +
.../notpip/_vendor/rich/default_styles.py | 183 +
.../patched/notpip/_vendor/rich/diagnose.py | 6 +
pipenv/patched/notpip/_vendor/rich/emoji.py | 96 +
pipenv/patched/notpip/_vendor/rich/errors.py | 34 +
.../patched/notpip/_vendor/rich/file_proxy.py | 54 +
.../patched/notpip/_vendor/rich/filesize.py | 89 +
.../notpip/_vendor/rich/highlighter.py | 147 +
pipenv/patched/notpip/_vendor/rich/json.py | 140 +
pipenv/patched/notpip/_vendor/rich/jupyter.py | 92 +
pipenv/patched/notpip/_vendor/rich/layout.py | 444 +
pipenv/patched/notpip/_vendor/rich/live.py | 365 +
.../notpip/_vendor/rich/live_render.py | 113 +
pipenv/patched/notpip/_vendor/rich/logging.py | 268 +
pipenv/patched/notpip/_vendor/rich/markup.py | 244 +
pipenv/patched/notpip/_vendor/rich/measure.py | 149 +
pipenv/patched/notpip/_vendor/rich/padding.py | 141 +
pipenv/patched/notpip/_vendor/rich/pager.py | 34 +
pipenv/patched/notpip/_vendor/rich/palette.py | 100 +
pipenv/patched/notpip/_vendor/rich/panel.py | 250 +
pipenv/patched/notpip/_vendor/rich/pretty.py | 903 +++
.../patched/notpip/_vendor/rich/progress.py | 1036 +++
.../notpip/_vendor/rich/progress_bar.py | 216 +
pipenv/patched/notpip/_vendor/rich/prompt.py | 376 +
.../patched/notpip/_vendor/rich/protocol.py | 42 +
pipenv/patched/notpip/_vendor/rich/region.py | 10 +
pipenv/patched/notpip/_vendor/rich/repr.py | 151 +
pipenv/patched/notpip/_vendor/rich/rule.py | 115 +
pipenv/patched/notpip/_vendor/rich/scope.py | 86 +
pipenv/patched/notpip/_vendor/rich/screen.py | 54 +
pipenv/patched/notpip/_vendor/rich/segment.py | 720 ++
pipenv/patched/notpip/_vendor/rich/spinner.py | 134 +
pipenv/patched/notpip/_vendor/rich/status.py | 132 +
pipenv/patched/notpip/_vendor/rich/style.py | 785 ++
pipenv/patched/notpip/_vendor/rich/styled.py | 42 +
pipenv/patched/notpip/_vendor/rich/syntax.py | 735 ++
pipenv/patched/notpip/_vendor/rich/table.py | 968 +++
.../patched/notpip/_vendor/rich/tabulate.py | 51 +
.../notpip/_vendor/rich/terminal_theme.py | 55 +
pipenv/patched/notpip/_vendor/rich/text.py | 1282 +++
pipenv/patched/notpip/_vendor/rich/theme.py | 112 +
pipenv/patched/notpip/_vendor/rich/themes.py | 5 +
.../patched/notpip/_vendor/rich/traceback.py | 678 ++
pipenv/patched/notpip/_vendor/rich/tree.py | 249 +
pipenv/patched/notpip/_vendor/tomli/LICENSE | 21 -
.../notpip/_vendor/typing_extensions.py | 2296 ++++++
.../notpip/_vendor/urllib3/_version.py | 2 +-
.../notpip/_vendor/urllib3/connection.py | 42 +-
.../notpip/_vendor/urllib3/connectionpool.py | 49 +-
.../contrib/_securetransport/bindings.py | 2 +-
.../contrib/_securetransport/low_level.py | 1 +
.../_vendor/urllib3/contrib/pyopenssl.py | 4 +-
.../urllib3/contrib/securetransport.py | 4 +-
.../_vendor/urllib3/packages/__init__.py | 5 -
.../packages/ssl_match_hostname/__init__.py | 24 -
.../notpip/_vendor/urllib3/util/connection.py | 3 +-
.../notpip/_vendor/urllib3/util/proxy.py | 1 +
.../notpip/_vendor/urllib3/util/retry.py | 24 +-
.../ssl_match_hostname.py} | 5 +-
.../_vendor/urllib3/util/ssltransport.py | 4 +-
pipenv/patched/notpip/_vendor/vendor.txt | 31 +-
.../notpip/{_vendor => }/distro.LICENSE | 0
.../patched/notpip/{_vendor => }/six.LICENSE | 0
.../LICENSE.txt => typing_extensions.LICENSE} | 38 +-
pipenv/patched/patched.txt | 2 +-
.../patches/patched/_post_pip_import.patch | 2 +-
362 files changed, 54307 insertions(+), 18705 deletions(-)
rename pipenv/patched/notpip/{_vendor/msgpack => }/COPYING (100%)
rename pipenv/patched/notpip/{_vendor/webencodings => }/LICENSE (100%)
rename pipenv/patched/notpip/{_vendor/packaging => }/LICENSE.APACHE (100%)
rename pipenv/patched/notpip/{_vendor/packaging => }/LICENSE.BSD (100%)
rename pipenv/patched/notpip/{_vendor/idna => }/LICENSE.md (100%)
rename pipenv/patched/notpip/{_vendor/urllib3 => }/LICENSE.txt (100%)
create mode 100644 pipenv/patched/notpip/_internal/operations/build/metadata_editable.py
create mode 100644 pipenv/patched/notpip/_internal/operations/build/wheel_editable.py
create mode 100644 pipenv/patched/notpip/_internal/utils/egg_link.py
delete mode 100644 pipenv/patched/notpip/_internal/utils/parallel.py
delete mode 100644 pipenv/patched/notpip/_internal/utils/pkg_resources.py
delete mode 100644 pipenv/patched/notpip/_vendor/appdirs.LICENSE.txt
delete mode 100644 pipenv/patched/notpip/_vendor/appdirs.py
delete mode 100644 pipenv/patched/notpip/_vendor/cachecontrol/LICENSE.txt
delete mode 100644 pipenv/patched/notpip/_vendor/certifi/LICENSE
delete mode 100644 pipenv/patched/notpip/_vendor/chardet/LICENSE
delete mode 100644 pipenv/patched/notpip/_vendor/colorama/LICENSE.txt
delete mode 100644 pipenv/patched/notpip/_vendor/distlib/_backport/__init__.py
delete mode 100644 pipenv/patched/notpip/_vendor/distlib/_backport/misc.py
delete mode 100644 pipenv/patched/notpip/_vendor/distlib/_backport/shutil.py
delete mode 100644 pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.cfg
delete mode 100644 pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.py
delete mode 100644 pipenv/patched/notpip/_vendor/distlib/_backport/tarfile.py
create mode 100644 pipenv/patched/notpip/_vendor/distlib/t64-arm.exe
create mode 100644 pipenv/patched/notpip/_vendor/distlib/w64-arm.exe
delete mode 100644 pipenv/patched/notpip/_vendor/html5lib/LICENSE
delete mode 100644 pipenv/patched/notpip/_vendor/packaging/LICENSE
delete mode 100644 pipenv/patched/notpip/_vendor/pep517/LICENSE
delete mode 100644 pipenv/patched/notpip/_vendor/pkg_resources/LICENSE
create mode 100644 pipenv/patched/notpip/_vendor/platformdirs/__init__.py
create mode 100644 pipenv/patched/notpip/_vendor/platformdirs/__main__.py
create mode 100644 pipenv/patched/notpip/_vendor/platformdirs/android.py
create mode 100644 pipenv/patched/notpip/_vendor/platformdirs/api.py
create mode 100644 pipenv/patched/notpip/_vendor/platformdirs/macos.py
create mode 100644 pipenv/patched/notpip/_vendor/platformdirs/unix.py
create mode 100644 pipenv/patched/notpip/_vendor/platformdirs/version.py
create mode 100644 pipenv/patched/notpip/_vendor/platformdirs/windows.py
delete mode 100644 pipenv/patched/notpip/_vendor/progress/LICENSE
create mode 100644 pipenv/patched/notpip/_vendor/progress/colors.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/__init__.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/__main__.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/cmdline.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/console.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/filter.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/filters/__init__.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatter.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/__init__.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/_mapping.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/bbcode.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/groff.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/html.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/img.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/irc.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/latex.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/other.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/pangomarkup.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/rtf.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/svg.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/terminal.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/formatters/terminal256.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/lexer.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/lexers/__init__.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/lexers/_mapping.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/lexers/python.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/modeline.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/plugin.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/regexopt.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/scanner.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/sphinxext.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/style.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/styles/__init__.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/token.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/unistring.py
create mode 100644 pipenv/patched/notpip/_vendor/pygments/util.py
delete mode 100644 pipenv/patched/notpip/_vendor/pyparsing.LICENSE
delete mode 100644 pipenv/patched/notpip/_vendor/pyparsing.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/__init__.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/actions.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/common.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/core.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/diagram/__init__.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/exceptions.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/helpers.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/results.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/testing.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/unicode.py
create mode 100644 pipenv/patched/notpip/_vendor/pyparsing/util.py
delete mode 100644 pipenv/patched/notpip/_vendor/requests/LICENSE
delete mode 100644 pipenv/patched/notpip/_vendor/resolvelib/LICENSE
create mode 100644 pipenv/patched/notpip/_vendor/rich/__init__.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/__main__.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_cell_widths.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_emoji_codes.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_emoji_replace.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_extension.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_inspect.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_log_render.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_loop.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_lru_cache.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_palettes.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_pick.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_ratio.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_spinners.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_stack.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_timer.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_windows.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/_wrap.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/abc.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/align.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/ansi.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/bar.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/box.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/cells.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/color.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/color_triplet.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/columns.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/console.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/constrain.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/containers.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/control.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/default_styles.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/diagnose.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/emoji.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/errors.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/file_proxy.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/filesize.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/highlighter.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/json.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/jupyter.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/layout.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/live.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/live_render.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/logging.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/markup.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/measure.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/padding.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/pager.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/palette.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/panel.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/pretty.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/progress.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/progress_bar.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/prompt.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/protocol.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/region.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/repr.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/rule.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/scope.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/screen.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/segment.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/spinner.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/status.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/style.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/styled.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/syntax.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/table.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/tabulate.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/terminal_theme.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/text.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/theme.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/themes.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/traceback.py
create mode 100644 pipenv/patched/notpip/_vendor/rich/tree.py
delete mode 100644 pipenv/patched/notpip/_vendor/tomli/LICENSE
create mode 100644 pipenv/patched/notpip/_vendor/typing_extensions.py
delete mode 100644 pipenv/patched/notpip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py
rename pipenv/patched/notpip/_vendor/urllib3/{packages/ssl_match_hostname/_implementation.py => util/ssl_match_hostname.py} (96%)
rename pipenv/patched/notpip/{_vendor => }/distro.LICENSE (100%)
rename pipenv/patched/notpip/{_vendor => }/six.LICENSE (100%)
rename pipenv/patched/notpip/{_vendor/distlib/LICENSE.txt => typing_extensions.LICENSE} (86%)
diff --git a/pipenv/patched/notpip/_vendor/msgpack/COPYING b/pipenv/patched/notpip/COPYING
similarity index 100%
rename from pipenv/patched/notpip/_vendor/msgpack/COPYING
rename to pipenv/patched/notpip/COPYING
diff --git a/pipenv/patched/notpip/_vendor/webencodings/LICENSE b/pipenv/patched/notpip/LICENSE
similarity index 100%
rename from pipenv/patched/notpip/_vendor/webencodings/LICENSE
rename to pipenv/patched/notpip/LICENSE
diff --git a/pipenv/patched/notpip/_vendor/packaging/LICENSE.APACHE b/pipenv/patched/notpip/LICENSE.APACHE
similarity index 100%
rename from pipenv/patched/notpip/_vendor/packaging/LICENSE.APACHE
rename to pipenv/patched/notpip/LICENSE.APACHE
diff --git a/pipenv/patched/notpip/_vendor/packaging/LICENSE.BSD b/pipenv/patched/notpip/LICENSE.BSD
similarity index 100%
rename from pipenv/patched/notpip/_vendor/packaging/LICENSE.BSD
rename to pipenv/patched/notpip/LICENSE.BSD
diff --git a/pipenv/patched/notpip/_vendor/idna/LICENSE.md b/pipenv/patched/notpip/LICENSE.md
similarity index 100%
rename from pipenv/patched/notpip/_vendor/idna/LICENSE.md
rename to pipenv/patched/notpip/LICENSE.md
diff --git a/pipenv/patched/notpip/_vendor/urllib3/LICENSE.txt b/pipenv/patched/notpip/LICENSE.txt
similarity index 100%
rename from pipenv/patched/notpip/_vendor/urllib3/LICENSE.txt
rename to pipenv/patched/notpip/LICENSE.txt
diff --git a/pipenv/patched/notpip/__init__.py b/pipenv/patched/notpip/__init__.py
index 9b0b9d2995..04489dc8da 100644
--- a/pipenv/patched/notpip/__init__.py
+++ b/pipenv/patched/notpip/__init__.py
@@ -1,6 +1,6 @@
from typing import List, Optional
-__version__ = "21.2.2"
+__version__ = "22.0.3"
def main(args: Optional[List[str]] = None) -> int:
diff --git a/pipenv/patched/notpip/_internal/build_env.py b/pipenv/patched/notpip/_internal/build_env.py
index d8c66b3ff5..948c721e10 100644
--- a/pipenv/patched/notpip/_internal/build_env.py
+++ b/pipenv/patched/notpip/_internal/build_env.py
@@ -17,7 +17,7 @@
from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
from pipenv.patched.notpip._vendor.packaging.version import Version
-from pip import __file__ as pip_location
+from pipenv.patched.notpip import __file__ as pip_location
from pipenv.patched.notpip._internal.cli.spinners import open_spinner
from pipenv.patched.notpip._internal.locations import get_platlib, get_prefixed_libs, get_purelib
from pipenv.patched.notpip._internal.metadata import get_environment
@@ -31,15 +31,13 @@
class _Prefix:
-
- def __init__(self, path):
- # type: (str) -> None
+ def __init__(self, path: str) -> None:
self.path = path
self.setup = False
self.bin_dir = get_paths(
- 'nt' if os.name == 'nt' else 'posix_prefix',
- vars={'base': path, 'platbase': path}
- )['scripts']
+ "nt" if os.name == "nt" else "posix_prefix",
+ vars={"base": path, "platbase": path},
+ )["scripts"]
self.lib_dirs = get_prefixed_libs(path)
@@ -70,22 +68,18 @@ def _create_standalone_pip() -> Iterator[str]:
class BuildEnvironment:
- """Creates and manages an isolated environment to install build deps
- """
+ """Creates and manages an isolated environment to install build deps"""
- def __init__(self):
- # type: () -> None
- temp_dir = TempDirectory(
- kind=tempdir_kinds.BUILD_ENV, globally_managed=True
- )
+ def __init__(self) -> None:
+ temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
self._prefixes = OrderedDict(
(name, _Prefix(os.path.join(temp_dir.path, name)))
- for name in ('normal', 'overlay')
+ for name in ("normal", "overlay")
)
- self._bin_dirs = [] # type: List[str]
- self._lib_dirs = [] # type: List[str]
+ self._bin_dirs: List[str] = []
+ self._lib_dirs: List[str] = []
for prefix in reversed(list(self._prefixes.values())):
self._bin_dirs.append(prefix.bin_dir)
self._lib_dirs.extend(prefix.lib_dirs)
@@ -96,12 +90,15 @@ def __init__(self):
system_sites = {
os.path.normcase(site) for site in (get_purelib(), get_platlib())
}
- self._site_dir = os.path.join(temp_dir.path, 'site')
+ self._site_dir = os.path.join(temp_dir.path, "site")
if not os.path.exists(self._site_dir):
os.mkdir(self._site_dir)
- with open(os.path.join(self._site_dir, 'sitecustomize.py'), 'w') as fp:
- fp.write(textwrap.dedent(
- '''
+ with open(
+ os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8"
+ ) as fp:
+ fp.write(
+ textwrap.dedent(
+ """
import os, site, sys
# First, drop system-sites related paths.
@@ -124,47 +121,49 @@ def __init__(self):
for path in {lib_dirs!r}:
assert not path in sys.path
site.addsitedir(path)
- '''
- ).format(system_sites=system_sites, lib_dirs=self._lib_dirs))
+ """
+ ).format(system_sites=system_sites, lib_dirs=self._lib_dirs)
+ )
- def __enter__(self):
- # type: () -> None
+ def __enter__(self) -> None:
self._save_env = {
name: os.environ.get(name, None)
- for name in ('PATH', 'PYTHONNOUSERSITE', 'PYTHONPATH')
+ for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH")
}
path = self._bin_dirs[:]
- old_path = self._save_env['PATH']
+ old_path = self._save_env["PATH"]
if old_path:
path.extend(old_path.split(os.pathsep))
pythonpath = [self._site_dir]
- os.environ.update({
- 'PATH': os.pathsep.join(path),
- 'PYTHONNOUSERSITE': '1',
- 'PYTHONPATH': os.pathsep.join(pythonpath),
- })
+ os.environ.update(
+ {
+ "PATH": os.pathsep.join(path),
+ "PYTHONNOUSERSITE": "1",
+ "PYTHONPATH": os.pathsep.join(pythonpath),
+ }
+ )
def __exit__(
self,
- exc_type, # type: Optional[Type[BaseException]]
- exc_val, # type: Optional[BaseException]
- exc_tb # type: Optional[TracebackType]
- ):
- # type: (...) -> None
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
for varname, old_value in self._save_env.items():
if old_value is None:
os.environ.pop(varname, None)
else:
os.environ[varname] = old_value
- def check_requirements(self, reqs):
- # type: (Iterable[str]) -> Tuple[Set[Tuple[str, str]], Set[str]]
+ def check_requirements(
+ self, reqs: Iterable[str]
+ ) -> Tuple[Set[Tuple[str, str]], Set[str]]:
"""Return 2 sets:
- - conflicting requirements: set of (installed, wanted) reqs tuples
- - missing requirements: set of reqs
+ - conflicting requirements: set of (installed, wanted) reqs tuples
+ - missing requirements: set of reqs
"""
missing = set()
conflicting = set()
@@ -187,32 +186,25 @@ def check_requirements(self, reqs):
def install_requirements(
self,
- finder, # type: PackageFinder
- requirements, # type: Iterable[str]
- prefix_as_string, # type: str
- message # type: str
- ):
- # type: (...) -> None
+ finder: "PackageFinder",
+ requirements: Iterable[str],
+ prefix_as_string: str,
+ *,
+ kind: str,
+ ) -> None:
prefix = self._prefixes[prefix_as_string]
assert not prefix.setup
prefix.setup = True
if not requirements:
return
with contextlib.ExitStack() as ctx:
- # TODO: Remove this block when dropping 3.6 support. Python 3.6
- # lacks importlib.resources and pep517 has issues loading files in
- # a zip, so we fallback to the "old" method by adding the current
- # pip directory to the child process's sys.path.
- if sys.version_info < (3, 7):
- pip_runnable = os.path.dirname(pip_location)
- else:
- pip_runnable = ctx.enter_context(_create_standalone_pip())
+ pip_runnable = ctx.enter_context(_create_standalone_pip())
self._install_requirements(
pip_runnable,
finder,
requirements,
prefix,
- message,
+ kind=kind,
)
@staticmethod
@@ -221,75 +213,84 @@ def _install_requirements(
finder: "PackageFinder",
requirements: Iterable[str],
prefix: _Prefix,
- message: str,
+ *,
+ kind: str,
) -> None:
- sys_executable = os.environ.get('PIP_PYTHON_PATH', sys.executable)
- args = [
- sys_executable, pip_runnable, 'install',
- '--ignore-installed', '--no-user', '--prefix', prefix.path,
- '--no-warn-script-location',
- ] # type: List[str]
+ args: List[str] = [
+ sys.executable,
+ pip_runnable,
+ "install",
+ "--ignore-installed",
+ "--no-user",
+ "--prefix",
+ prefix.path,
+ "--no-warn-script-location",
+ ]
if logger.getEffectiveLevel() <= logging.DEBUG:
- args.append('-v')
- for format_control in ('no_binary', 'only_binary'):
+ args.append("-v")
+ 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:"})),
+ )
+ )
index_urls = finder.index_urls
if index_urls:
- args.extend(['-i', index_urls[0]])
+ args.extend(["-i", index_urls[0]])
for extra_index in 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.trusted_hosts:
- args.extend(['--trusted-host', host])
+ args.extend(["--trusted-host", host])
if finder.allow_all_prereleases:
- args.append('--pre')
+ args.append("--pre")
if finder.prefer_binary:
- args.append('--prefer-binary')
- args.append('--')
+ args.append("--prefer-binary")
+ args.append("--")
args.extend(requirements)
extra_environ = {"_PIP_STANDALONE_CERT": where()}
- with open_spinner(message) as spinner:
- call_subprocess(args, spinner=spinner, extra_environ=extra_environ)
+ with open_spinner(f"Installing {kind}") as spinner:
+ call_subprocess(
+ args,
+ command_desc=f"pip subprocess to install {kind}",
+ spinner=spinner,
+ extra_environ=extra_environ,
+ )
class NoOpBuildEnvironment(BuildEnvironment):
- """A no-op drop-in replacement for BuildEnvironment
- """
+ """A no-op drop-in replacement for BuildEnvironment"""
- def __init__(self):
- # type: () -> None
+ def __init__(self) -> None:
pass
- def __enter__(self):
- # type: () -> None
+ def __enter__(self) -> None:
pass
def __exit__(
self,
- exc_type, # type: Optional[Type[BaseException]]
- exc_val, # type: Optional[BaseException]
- exc_tb # type: Optional[TracebackType]
- ):
- # type: (...) -> None
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
pass
- def cleanup(self):
- # type: () -> None
+ def cleanup(self) -> None:
pass
def install_requirements(
self,
- finder, # type: PackageFinder
- requirements, # type: Iterable[str]
- prefix_as_string, # type: str
- message # type: str
- ):
- # type: (...) -> None
+ finder: "PackageFinder",
+ requirements: Iterable[str],
+ prefix_as_string: str,
+ *,
+ kind: str,
+ ) -> None:
raise NotImplementedError()
diff --git a/pipenv/patched/notpip/_internal/cache.py b/pipenv/patched/notpip/_internal/cache.py
index 5e53d9865c..d2c0c519e6 100644
--- a/pipenv/patched/notpip/_internal/cache.py
+++ b/pipenv/patched/notpip/_internal/cache.py
@@ -20,8 +20,7 @@
logger = logging.getLogger(__name__)
-def _hash_dict(d):
- # type: (Dict[str, str]) -> str
+def _hash_dict(d: Dict[str, str]) -> str:
"""Return a stable sha224 of a dictionary."""
s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
return hashlib.sha224(s.encode("ascii")).hexdigest()
@@ -31,15 +30,16 @@ class Cache:
"""An abstract class - provides cache directories for data from links
- :param cache_dir: The root of the cache.
- :param format_control: An object of FormatControl class to limit
- binaries being read from the cache.
- :param allowed_formats: which formats of files the cache should store.
- ('binary' and 'source' are the only allowed values)
+ :param cache_dir: The root of the cache.
+ :param format_control: An object of FormatControl class to limit
+ binaries being read from the cache.
+ :param allowed_formats: which formats of files the cache should store.
+ ('binary' and 'source' are the only allowed values)
"""
- def __init__(self, cache_dir, format_control, allowed_formats):
- # type: (str, FormatControl, Set[str]) -> None
+ def __init__(
+ self, cache_dir: str, format_control: FormatControl, allowed_formats: Set[str]
+ ) -> None:
super().__init__()
assert not cache_dir or os.path.isabs(cache_dir)
self.cache_dir = cache_dir or None
@@ -49,10 +49,8 @@ def __init__(self, cache_dir, format_control, allowed_formats):
_valid_formats = {"source", "binary"}
assert self.allowed_formats.union(_valid_formats) == _valid_formats
- def _get_cache_path_parts(self, link):
- # type: (Link) -> List[str]
- """Get parts of part that must be os.path.joined with cache_dir
- """
+ def _get_cache_path_parts(self, link: Link) -> List[str]:
+ """Get parts of part that must be os.path.joined with cache_dir"""
# We want to generate an url to use as our cache key, we don't want to
# just re-use the URL because it might have other items in the fragment
@@ -84,19 +82,12 @@ def _get_cache_path_parts(self, link):
return parts
- def _get_candidates(self, link, canonical_package_name):
- # type: (Link, str) -> List[Any]
- can_not_cache = (
- not self.cache_dir or
- not canonical_package_name or
- not link
- )
+ def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]:
+ can_not_cache = not self.cache_dir or not canonical_package_name or not link
if can_not_cache:
return []
- formats = self.format_control.get_allowed_formats(
- canonical_package_name
- )
+ formats = self.format_control.get_allowed_formats(canonical_package_name)
if not self.allowed_formats.intersection(formats):
return []
@@ -107,19 +98,16 @@ def _get_candidates(self, link, canonical_package_name):
candidates.append((candidate, path))
return candidates
- def get_path_for_link(self, link):
- # type: (Link) -> str
- """Return a directory to store cached items in for link.
- """
+ def get_path_for_link(self, link: Link) -> str:
+ """Return a directory to store cached items in for link."""
raise NotImplementedError()
def get(
self,
- link, # type: Link
- package_name, # type: Optional[str]
- supported_tags, # type: List[Tag]
- ):
- # type: (...) -> Link
+ link: Link,
+ package_name: Optional[str],
+ supported_tags: List[Tag],
+ ) -> Link:
"""Returns a link to a cached item if it exists, otherwise returns the
passed link.
"""
@@ -127,15 +115,12 @@ def get(
class SimpleWheelCache(Cache):
- """A cache of wheels for future installs.
- """
+ """A cache of wheels for future installs."""
- def __init__(self, cache_dir, format_control):
- # type: (str, FormatControl) -> None
+ def __init__(self, cache_dir: str, format_control: FormatControl) -> None:
super().__init__(cache_dir, format_control, {"binary"})
- def get_path_for_link(self, link):
- # type: (Link) -> str
+ def get_path_for_link(self, link: Link) -> str:
"""Return a directory to store cached wheels for link
Because there are M wheels for any one sdist, we provide a directory
@@ -157,20 +142,17 @@ def get_path_for_link(self, link):
def get(
self,
- link, # type: Link
- package_name, # type: Optional[str]
- supported_tags, # type: List[Tag]
- ):
- # type: (...) -> Link
+ link: Link,
+ package_name: Optional[str],
+ supported_tags: List[Tag],
+ ) -> Link:
candidates = []
if not package_name:
return link
canonical_package_name = canonicalize_name(package_name)
- for wheel_name, wheel_dir in self._get_candidates(
- link, canonical_package_name
- ):
+ for wheel_name, wheel_dir in self._get_candidates(link, canonical_package_name):
try:
wheel = Wheel(wheel_name)
except InvalidWheelFilename:
@@ -179,7 +161,9 @@ def get(
logger.debug(
"Ignoring cached wheel %s for %s as it "
"does not match the expected distribution name %s.",
- wheel_name, link, package_name,
+ wheel_name,
+ link,
+ package_name,
)
continue
if not wheel.supported(supported_tags):
@@ -201,11 +185,9 @@ def get(
class EphemWheelCache(SimpleWheelCache):
- """A SimpleWheelCache that creates it's own temporary cache directory
- """
+ """A SimpleWheelCache that creates it's own temporary cache directory"""
- def __init__(self, format_control):
- # type: (FormatControl) -> None
+ def __init__(self, format_control: FormatControl) -> None:
self._temp_dir = TempDirectory(
kind=tempdir_kinds.EPHEM_WHEEL_CACHE,
globally_managed=True,
@@ -217,8 +199,8 @@ def __init__(self, format_control):
class CacheEntry:
def __init__(
self,
- link, # type: Link
- persistent, # type: bool
+ link: Link,
+ persistent: bool,
):
self.link = link
self.persistent = persistent
@@ -231,27 +213,23 @@ class WheelCache(Cache):
when a certain link is not found in the simple wheel cache first.
"""
- def __init__(self, cache_dir, format_control):
- # type: (str, FormatControl) -> None
- super().__init__(cache_dir, format_control, {'binary'})
+ def __init__(self, cache_dir: str, format_control: FormatControl) -> None:
+ super().__init__(cache_dir, format_control, {"binary"})
self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
self._ephem_cache = EphemWheelCache(format_control)
- def get_path_for_link(self, link):
- # type: (Link) -> str
+ def get_path_for_link(self, link: Link) -> str:
return self._wheel_cache.get_path_for_link(link)
- def get_ephem_path_for_link(self, link):
- # type: (Link) -> str
+ def get_ephem_path_for_link(self, link: Link) -> str:
return self._ephem_cache.get_path_for_link(link)
def get(
self,
- link, # type: Link
- package_name, # type: Optional[str]
- supported_tags, # type: List[Tag]
- ):
- # type: (...) -> Link
+ link: Link,
+ package_name: Optional[str],
+ supported_tags: List[Tag],
+ ) -> Link:
cache_entry = self.get_cache_entry(link, package_name, supported_tags)
if cache_entry is None:
return link
@@ -259,11 +237,10 @@ def get(
def get_cache_entry(
self,
- link, # type: Link
- package_name, # type: Optional[str]
- supported_tags, # type: List[Tag]
- ):
- # type: (...) -> Optional[CacheEntry]
+ link: Link,
+ package_name: Optional[str],
+ supported_tags: List[Tag],
+ ) -> Optional[CacheEntry]:
"""Returns a CacheEntry with a link to a cached item if it exists or
None. The cache entry indicates if the item was found in the persistent
or ephemeral cache.
diff --git a/pipenv/patched/notpip/_internal/cli/autocompletion.py b/pipenv/patched/notpip/_internal/cli/autocompletion.py
index 2648f29fec..82c95652f1 100644
--- a/pipenv/patched/notpip/_internal/cli/autocompletion.py
+++ b/pipenv/patched/notpip/_internal/cli/autocompletion.py
@@ -59,6 +59,14 @@ def autocomplete() -> None:
print(dist)
sys.exit(1)
+ should_list_installables = (
+ not current.startswith("-") and subcommand_name == "install"
+ )
+ if should_list_installables:
+ for path in auto_complete_paths(current, "path"):
+ print(path)
+ sys.exit(1)
+
subcommand = create_command(subcommand_name)
for opt in subcommand.parser.option_list_all:
@@ -138,7 +146,7 @@ def auto_complete_paths(current: str, completion_type: str) -> Iterable[str]:
starting with ``current``.
:param current: The word to be completed
- :param completion_type: path completion type(`file`, `path` or `dir`)i
+ :param completion_type: path completion type(``file``, ``path`` or ``dir``)
:return: A generator of regular files and/or directories
"""
directory, filename = os.path.split(current)
diff --git a/pipenv/patched/notpip/_internal/cli/base_command.py b/pipenv/patched/notpip/_internal/cli/base_command.py
index b4b034321b..6a71bc2125 100644
--- a/pipenv/patched/notpip/_internal/cli/base_command.py
+++ b/pipenv/patched/notpip/_internal/cli/base_command.py
@@ -1,5 +1,6 @@
"""Base Command class, and related routines"""
+import functools
import logging
import logging.config
import optparse
@@ -7,7 +8,9 @@
import sys
import traceback
from optparse import Values
-from typing import Any, List, Optional, Tuple
+from typing import Any, Callable, List, Optional, Tuple
+
+from pipenv.patched.notpip._vendor.rich import traceback as rich_traceback
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.cli.command_context import CommandContextMixIn
@@ -21,12 +24,12 @@
from pipenv.patched.notpip._internal.exceptions import (
BadCommand,
CommandError,
+ DiagnosticPipError,
InstallationError,
NetworkConnectionError,
PreviousBuildDirError,
UninstallationError,
)
-from pipenv.patched.notpip._internal.utils.deprecation import deprecated
from pipenv.patched.notpip._internal.utils.filesystem import check_path_owner
from pipenv.patched.notpip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
from pipenv.patched.notpip._internal.utils.misc import get_prog, normalize_path
@@ -85,10 +88,10 @@ def handle_pip_version_check(self, options: Values) -> None:
# are present.
assert not hasattr(options, "no_index")
- def run(self, options: Values, args: List[Any]) -> int:
+ def run(self, options: Values, args: List[str]) -> int:
raise NotImplementedError
- def parse_args(self, args: List[str]) -> Tuple[Any, Any]:
+ def parse_args(self, args: List[str]) -> Tuple[Values, List[str]]:
# factored out for testability
return self.parser.parse_args(args)
@@ -148,20 +151,6 @@ def _main(self, args: List[str]) -> int:
)
options.cache_dir = None
- if getattr(options, "build_dir", None):
- deprecated(
- reason=(
- "The -b/--build/--build-dir/--build-directory "
- "option is deprecated and has no effect anymore."
- ),
- replacement=(
- "use the TMPDIR/TEMP/TMP environment variable, "
- "possibly combined with --no-clean"
- ),
- gone_in="21.3",
- issue=8333,
- )
-
if "2020-resolver" in options.features_enabled:
logger.warning(
"--use-feature=2020-resolver no longer has any effect, "
@@ -169,46 +158,66 @@ def _main(self, args: List[str]) -> int:
"This will become an error in pip 21.0."
)
+ def intercepts_unhandled_exc(
+ run_func: Callable[..., int]
+ ) -> Callable[..., int]:
+ @functools.wraps(run_func)
+ def exc_logging_wrapper(*args: Any) -> int:
+ try:
+ status = run_func(*args)
+ assert isinstance(status, int)
+ return status
+ except DiagnosticPipError as exc:
+ logger.error("[present-diagnostic] %s", exc)
+ logger.debug("Exception information:", exc_info=True)
+
+ return ERROR
+ except PreviousBuildDirError as exc:
+ logger.critical(str(exc))
+ logger.debug("Exception information:", exc_info=True)
+
+ return PREVIOUS_BUILD_DIR_ERROR
+ except (
+ InstallationError,
+ UninstallationError,
+ BadCommand,
+ NetworkConnectionError,
+ ) as exc:
+ logger.critical(str(exc))
+ logger.debug("Exception information:", exc_info=True)
+
+ return ERROR
+ except CommandError as exc:
+ logger.critical("%s", exc)
+ logger.debug("Exception information:", exc_info=True)
+
+ return ERROR
+ except BrokenStdoutLoggingError:
+ # Bypass our logger and write any remaining messages to
+ # stderr because stdout no longer works.
+ print("ERROR: Pipe to stdout was broken", file=sys.stderr)
+ if level_number <= logging.DEBUG:
+ traceback.print_exc(file=sys.stderr)
+
+ return ERROR
+ except KeyboardInterrupt:
+ logger.critical("Operation cancelled by user")
+ logger.debug("Exception information:", exc_info=True)
+
+ return ERROR
+ except BaseException:
+ logger.critical("Exception:", exc_info=True)
+
+ return UNKNOWN_ERROR
+
+ return exc_logging_wrapper
+
try:
- status = self.run(options, args)
- assert isinstance(status, int)
- return status
- except PreviousBuildDirError as exc:
- logger.critical(str(exc))
- logger.debug("Exception information:", exc_info=True)
-
- return PREVIOUS_BUILD_DIR_ERROR
- except (
- InstallationError,
- UninstallationError,
- BadCommand,
- NetworkConnectionError,
- ) as exc:
- logger.critical(str(exc))
- logger.debug("Exception information:", exc_info=True)
-
- return ERROR
- except CommandError as exc:
- logger.critical("%s", exc)
- logger.debug("Exception information:", exc_info=True)
-
- return ERROR
- except BrokenStdoutLoggingError:
- # Bypass our logger and write any remaining messages to stderr
- # because stdout no longer works.
- print("ERROR: Pipe to stdout was broken", file=sys.stderr)
- if level_number <= logging.DEBUG:
- traceback.print_exc(file=sys.stderr)
-
- return ERROR
- except KeyboardInterrupt:
- logger.critical("Operation cancelled by user")
- logger.debug("Exception information:", exc_info=True)
-
- return ERROR
- except BaseException:
- logger.critical("Exception:", exc_info=True)
-
- return UNKNOWN_ERROR
+ if not options.debug_mode:
+ run = intercepts_unhandled_exc(self.run)
+ else:
+ run = self.run
+ rich_traceback.install(show_locals=True)
+ return run(options, args)
finally:
self.handle_pip_version_check(options)
diff --git a/pipenv/patched/notpip/_internal/cli/cmdoptions.py b/pipenv/patched/notpip/_internal/cli/cmdoptions.py
index 1b72229caa..603f14aa2d 100644
--- a/pipenv/patched/notpip/_internal/cli/cmdoptions.py
+++ b/pipenv/patched/notpip/_internal/cli/cmdoptions.py
@@ -10,9 +10,9 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
+import logging
import os
import textwrap
-import warnings
from functools import partial
from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values
from textwrap import dedent
@@ -30,6 +30,8 @@
from pipenv.patched.notpip._internal.utils.hashes import STRONG_HASHES
from pipenv.patched.notpip._internal.utils.misc import strtobool
+logger = logging.getLogger(__name__)
+
def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None:
"""
@@ -76,10 +78,9 @@ def getname(n: str) -> Optional[Any]:
if any(map(getname, names)):
control = options.format_control
control.disallow_binaries()
- warnings.warn(
+ logger.warning(
"Disabling all use of wheels due to the use of --build-option "
"/ --global-option / --install-option.",
- stacklevel=2,
)
@@ -151,6 +152,18 @@ class PipOption(Option):
help="Show help.",
)
+debug_mode: Callable[..., Option] = partial(
+ Option,
+ "--debug",
+ dest="debug_mode",
+ action="store_true",
+ default=False,
+ help=(
+ "Let unhandled exceptions propagate outside the main subroutine, "
+ "instead of logging them to stderr."
+ ),
+)
+
isolated_mode: Callable[..., Option] = partial(
Option,
"--isolated",
@@ -165,13 +178,15 @@ class PipOption(Option):
require_virtualenv: Callable[..., Option] = partial(
Option,
- # Run only if inside a virtualenv, bail if not.
"--require-virtualenv",
"--require-venv",
dest="require_venv",
action="store_true",
default=False,
- help=SUPPRESS_HELP,
+ help=(
+ "Allow pip to only run in a virtual environment; "
+ "exit with an error otherwise."
+ ),
)
verbose: Callable[..., Option] = partial(
@@ -719,18 +734,6 @@ def _handle_no_cache_dir(
help="Don't install package dependencies.",
)
-build_dir: Callable[..., Option] = partial(
- PipOption,
- "-b",
- "--build",
- "--build-dir",
- "--build-directory",
- dest="build_dir",
- type="path",
- metavar="dir",
- help=SUPPRESS_HELP,
-)
-
ignore_requires_python: Callable[..., Option] = partial(
Option,
"--ignore-requires-python",
@@ -961,7 +964,12 @@ def check_list_path_option(options: Values) -> None:
metavar="feature",
action="append",
default=[],
- choices=["legacy-resolver"],
+ choices=[
+ "legacy-resolver",
+ "out-of-tree-build",
+ "backtrack-on-build-failures",
+ "html5lib",
+ ],
help=("Enable deprecated functionality, that will be removed in the future."),
)
@@ -974,6 +982,7 @@ def check_list_path_option(options: Values) -> None:
"name": "General Options",
"options": [
help_,
+ debug_mode,
isolated_mode,
require_virtualenv,
verbose,
diff --git a/pipenv/patched/notpip/_internal/cli/progress_bars.py b/pipenv/patched/notpip/_internal/cli/progress_bars.py
index 582747ed8f..692a1b4c5c 100644
--- a/pipenv/patched/notpip/_internal/cli/progress_bars.py
+++ b/pipenv/patched/notpip/_internal/cli/progress_bars.py
@@ -1,10 +1,23 @@
+import functools
import itertools
import sys
from signal import SIGINT, default_int_handler, signal
-from typing import Any
+from typing import Any, Callable, Iterator, Optional, Tuple
from pipenv.patched.notpip._vendor.progress.bar import Bar, FillingCirclesBar, IncrementalBar
from pipenv.patched.notpip._vendor.progress.spinner import Spinner
+from pipenv.patched.notpip._vendor.rich.progress import (
+ BarColumn,
+ DownloadColumn,
+ FileSizeColumn,
+ Progress,
+ ProgressColumn,
+ SpinnerColumn,
+ TextColumn,
+ TimeElapsedColumn,
+ TimeRemainingColumn,
+ TransferSpeedColumn,
+)
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
from pipenv.patched.notpip._internal.utils.logging import get_indentation
@@ -17,6 +30,8 @@
except Exception:
colorama = None
+DownloadProgressRenderer = Callable[[Iterator[bytes]], Iterator[bytes]]
+
def _select_progress_class(preferred: Bar, fallback: Bar) -> Bar:
encoding = getattr(preferred.file, "encoding", None)
@@ -243,8 +258,64 @@ def update(self) -> None:
}
-def DownloadProgressProvider(progress_bar, max=None): # type: ignore
+def _legacy_progress_bar(
+ progress_bar: str, max: Optional[int]
+) -> DownloadProgressRenderer:
if max is None or max == 0:
- return BAR_TYPES[progress_bar][1]().iter
+ return BAR_TYPES[progress_bar][1]().iter # type: ignore
else:
return BAR_TYPES[progress_bar][0](max=max).iter
+
+
+#
+# Modern replacement, for our legacy progress bars.
+#
+def _rich_progress_bar(
+ iterable: Iterator[bytes],
+ *,
+ bar_type: str,
+ size: int,
+) -> Iterator[bytes]:
+ assert bar_type == "on", "This should only be used in the default mode."
+
+ if not size:
+ total = float("inf")
+ columns: Tuple[ProgressColumn, ...] = (
+ TextColumn("[progress.description]{task.description}"),
+ SpinnerColumn("line", speed=1.5),
+ FileSizeColumn(),
+ TransferSpeedColumn(),
+ TimeElapsedColumn(),
+ )
+ else:
+ total = size
+ columns = (
+ TextColumn("[progress.description]{task.description}"),
+ BarColumn(),
+ DownloadColumn(),
+ TransferSpeedColumn(),
+ TextColumn("eta"),
+ TimeRemainingColumn(),
+ )
+
+ progress = Progress(*columns, refresh_per_second=30)
+ task_id = progress.add_task(" " * (get_indentation() + 2), total=total)
+ with progress:
+ for chunk in iterable:
+ yield chunk
+ progress.update(task_id, advance=len(chunk))
+
+
+def get_download_progress_renderer(
+ *, bar_type: str, size: Optional[int] = None
+) -> DownloadProgressRenderer:
+ """Get an object that can be used to render the download progress.
+
+ Returns a callable, that takes an iterable to "wrap".
+ """
+ if bar_type == "on":
+ return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size)
+ elif bar_type == "off":
+ return iter # no-op, when passed an iterator
+ else:
+ return _legacy_progress_bar(bar_type, size)
diff --git a/pipenv/patched/notpip/_internal/cli/req_command.py b/pipenv/patched/notpip/_internal/cli/req_command.py
index 17c2209ff6..91664b3f15 100644
--- a/pipenv/patched/notpip/_internal/cli/req_command.py
+++ b/pipenv/patched/notpip/_internal/cli/req_command.py
@@ -34,6 +34,7 @@
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
from pipenv.patched.notpip._internal.resolution.base import BaseResolver
from pipenv.patched.notpip._internal.self_outdated_check import pip_self_version_check
+from pipenv.patched.notpip._internal.utils.deprecation import deprecated
from pipenv.patched.notpip._internal.utils.temp_dir import (
TempDirectory,
TempDirectoryTypeRegistry,
@@ -172,9 +173,10 @@ def warn_if_run_as_root() -> None:
# checks: https://mypy.readthedocs.io/en/stable/common_issues.html
if sys.platform == "win32" or sys.platform == "cygwin":
return
- if sys.platform == "darwin" or sys.platform == "linux":
- if os.getuid() != 0:
- return
+
+ if os.getuid() != 0:
+ return
+
logger.warning(
"Running pip as the 'root' user can result in broken permissions and "
"conflicting behaviour with the system package manager. "
@@ -225,6 +227,31 @@ def determine_resolver_variant(options: Values) -> str:
return "2020-resolver"
+ @staticmethod
+ def determine_build_failure_suppression(options: Values) -> bool:
+ """Determines whether build failures should be suppressed and backtracked on."""
+ if "backtrack-on-build-failures" not in options.deprecated_features_enabled:
+ return False
+
+ if "legacy-resolver" in options.deprecated_features_enabled:
+ raise CommandError("Cannot backtrack with legacy resolver.")
+
+ deprecated(
+ reason=(
+ "Backtracking on build failures can mask issues related to how "
+ "a package generates metadata or builds a wheel. This flag will "
+ "be removed in pip 22.2."
+ ),
+ gone_in=None,
+ replacement=(
+ "avoiding known-bad versions by explicitly telling pip to ignore them "
+ "(either directly as requirements, or via a constraints file)"
+ ),
+ feature_flag=None,
+ issue=10655,
+ )
+ return True
+
@classmethod
def make_requirement_preparer(
cls,
@@ -235,6 +262,7 @@ def make_requirement_preparer(
finder: PackageFinder,
use_user_site: bool,
download_dir: Optional[str] = None,
+ verbosity: int = 0,
) -> RequirementPreparer:
"""
Create a RequirementPreparer instance for the given parameters.
@@ -260,6 +288,27 @@ def make_requirement_preparer(
"fast-deps has no effect when used with the legacy resolver."
)
+ in_tree_build = "out-of-tree-build" not in options.deprecated_features_enabled
+ if "in-tree-build" in options.features_enabled:
+ deprecated(
+ reason="In-tree builds are now the default.",
+ replacement="to remove the --use-feature=in-tree-build flag",
+ gone_in="22.1",
+ )
+ if "out-of-tree-build" in options.deprecated_features_enabled:
+ deprecated(
+ reason="Out-of-tree builds are deprecated.",
+ replacement=None,
+ gone_in="22.1",
+ )
+
+ if options.progress_bar not in {"on", "off"}:
+ deprecated(
+ reason="Custom progress bar styles are deprecated",
+ replacement="to use the default progress bar style.",
+ gone_in="22.1",
+ )
+
return RequirementPreparer(
build_dir=temp_build_dir_path,
src_dir=options.src_dir,
@@ -272,7 +321,8 @@ def make_requirement_preparer(
require_hashes=options.require_hashes,
use_user_site=use_user_site,
lazy_wheel=lazy_wheel,
- in_tree_build="in-tree-build" in options.features_enabled,
+ verbosity=verbosity,
+ in_tree_build=in_tree_build,
)
@classmethod
@@ -298,6 +348,7 @@ def make_resolver(
isolated=options.isolated_mode,
use_pep517=use_pep517,
)
+ suppress_build_failures = cls.determine_build_failure_suppression(options)
resolver_variant = cls.determine_resolver_variant(options)
# The long import name and duplicated invocation is needed to convince
# Mypy into correctly typechecking. Otherwise it would complain the
@@ -317,6 +368,7 @@ def make_resolver(
force_reinstall=force_reinstall,
upgrade_strategy=upgrade_strategy,
py_version_info=py_version_info,
+ suppress_build_failures=suppress_build_failures,
)
import pipenv.patched.notpip._internal.resolution.legacy.resolver
@@ -450,4 +502,5 @@ def _build_package_finder(
link_collector=link_collector,
selection_prefs=selection_prefs,
target_python=target_python,
+ use_deprecated_html5lib="html5lib" in options.deprecated_features_enabled,
)
diff --git a/pipenv/patched/notpip/_internal/commands/__init__.py b/pipenv/patched/notpip/_internal/commands/__init__.py
index 24ad0b49b8..c1b22ee4b1 100644
--- a/pipenv/patched/notpip/_internal/commands/__init__.py
+++ b/pipenv/patched/notpip/_internal/commands/__init__.py
@@ -3,87 +3,102 @@
"""
import importlib
-from collections import OrderedDict, namedtuple
+from collections import namedtuple
from typing import Any, Dict, Optional
from pipenv.patched.notpip._internal.cli.base_command import Command
-CommandInfo = namedtuple('CommandInfo', 'module_path, class_name, summary')
+CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
-# The ordering matters for help display.
-# Also, even though the module path starts with the same
-# "pipenv.patched.notpip._internal.commands" prefix in each case, we include the full path
-# because it makes testing easier (specifically when modifying commands_dict
-# in test setup / teardown by adding info for a FakeCommand class defined
-# in a test-related module).
-# Finally, we need to pass an iterable of pairs here rather than a dict
-# so that the ordering won't be lost when using Python 2.7.
-commands_dict: Dict[str, CommandInfo] = OrderedDict([
- ('install', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.install', 'InstallCommand',
- 'Install packages.',
- )),
- ('download', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.download', 'DownloadCommand',
- 'Download packages.',
- )),
- ('uninstall', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.uninstall', 'UninstallCommand',
- 'Uninstall packages.',
- )),
- ('freeze', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.freeze', 'FreezeCommand',
- 'Output installed packages in requirements format.',
- )),
- ('list', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.list', 'ListCommand',
- 'List installed packages.',
- )),
- ('show', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.show', 'ShowCommand',
- 'Show information about installed packages.',
- )),
- ('check', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.check', 'CheckCommand',
- 'Verify installed packages have compatible dependencies.',
- )),
- ('config', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.configuration', 'ConfigurationCommand',
- 'Manage local and global configuration.',
- )),
- ('search', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.search', 'SearchCommand',
- 'Search PyPI for packages.',
- )),
- ('cache', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.cache', 'CacheCommand',
+# This dictionary does a bunch of heavy lifting for help output:
+# - Enables avoiding additional (costly) imports for presenting `--help`.
+# - The ordering matters for help display.
+#
+# Even though the module path starts with the same "pipenv.patched.notpip._internal.commands"
+# prefix, the full path makes testing easier (specifically when modifying
+# `commands_dict` in test setup / teardown).
+commands_dict: Dict[str, CommandInfo] = {
+ "install": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.install",
+ "InstallCommand",
+ "Install packages.",
+ ),
+ "download": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.download",
+ "DownloadCommand",
+ "Download packages.",
+ ),
+ "uninstall": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.uninstall",
+ "UninstallCommand",
+ "Uninstall packages.",
+ ),
+ "freeze": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.freeze",
+ "FreezeCommand",
+ "Output installed packages in requirements format.",
+ ),
+ "list": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.list",
+ "ListCommand",
+ "List installed packages.",
+ ),
+ "show": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.show",
+ "ShowCommand",
+ "Show information about installed packages.",
+ ),
+ "check": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.check",
+ "CheckCommand",
+ "Verify installed packages have compatible dependencies.",
+ ),
+ "config": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.configuration",
+ "ConfigurationCommand",
+ "Manage local and global configuration.",
+ ),
+ "search": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.search",
+ "SearchCommand",
+ "Search PyPI for packages.",
+ ),
+ "cache": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.cache",
+ "CacheCommand",
"Inspect and manage pip's wheel cache.",
- )),
- ('index', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.index', 'IndexCommand',
+ ),
+ "index": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.index",
+ "IndexCommand",
"Inspect information available from package indexes.",
- )),
- ('wheel', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.wheel', 'WheelCommand',
- 'Build wheels from your requirements.',
- )),
- ('hash', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.hash', 'HashCommand',
- 'Compute hashes of package archives.',
- )),
- ('completion', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.completion', 'CompletionCommand',
- 'A helper command used for command completion.',
- )),
- ('debug', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.debug', 'DebugCommand',
- 'Show information useful for debugging.',
- )),
- ('help', CommandInfo(
- 'pipenv.patched.notpip._internal.commands.help', 'HelpCommand',
- 'Show help for commands.',
- )),
-])
+ ),
+ "wheel": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.wheel",
+ "WheelCommand",
+ "Build wheels from your requirements.",
+ ),
+ "hash": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.hash",
+ "HashCommand",
+ "Compute hashes of package archives.",
+ ),
+ "completion": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.completion",
+ "CompletionCommand",
+ "A helper command used for command completion.",
+ ),
+ "debug": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.debug",
+ "DebugCommand",
+ "Show information useful for debugging.",
+ ),
+ "help": CommandInfo(
+ "pipenv.patched.notpip._internal.commands.help",
+ "HelpCommand",
+ "Show help for commands.",
+ ),
+}
def create_command(name: str, **kwargs: Any) -> Command:
diff --git a/pipenv/patched/notpip/_internal/commands/cache.py b/pipenv/patched/notpip/_internal/commands/cache.py
index 2668996a84..9ce71763b4 100644
--- a/pipenv/patched/notpip/_internal/commands/cache.py
+++ b/pipenv/patched/notpip/_internal/commands/cache.py
@@ -39,17 +39,17 @@ class CacheCommand(Command):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '--format',
- action='store',
- dest='list_format',
+ "--format",
+ action="store",
+ dest="list_format",
default="human",
- choices=('human', 'abspath'),
- help="Select the output format among: human (default) or abspath"
+ choices=("human", "abspath"),
+ help="Select the output format among: human (default) or abspath",
)
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[Any]) -> int:
+ def run(self, options: Values, args: List[str]) -> int:
handlers = {
"dir": self.get_cache_dir,
"info": self.get_cache_info,
@@ -59,8 +59,7 @@ def run(self, options: Values, args: List[Any]) -> int:
}
if not options.cache_dir:
- logger.error("pip cache commands can not "
- "function since cache is disabled.")
+ logger.error("pip cache commands can not function since cache is disabled.")
return ERROR
# Determine action
@@ -84,69 +83,73 @@ def run(self, options: Values, args: List[Any]) -> int:
def get_cache_dir(self, options: Values, args: List[Any]) -> None:
if args:
- raise CommandError('Too many arguments')
+ raise CommandError("Too many arguments")
logger.info(options.cache_dir)
def get_cache_info(self, options: Values, args: List[Any]) -> None:
if args:
- raise CommandError('Too many arguments')
+ raise CommandError("Too many arguments")
num_http_files = len(self._find_http_files(options))
- num_packages = len(self._find_wheels(options, '*'))
+ num_packages = len(self._find_wheels(options, "*"))
- http_cache_location = self._cache_dir(options, 'http')
- wheels_cache_location = self._cache_dir(options, 'wheels')
+ http_cache_location = self._cache_dir(options, "http")
+ wheels_cache_location = self._cache_dir(options, "wheels")
http_cache_size = filesystem.format_directory_size(http_cache_location)
- wheels_cache_size = filesystem.format_directory_size(
- wheels_cache_location
+ wheels_cache_size = filesystem.format_directory_size(wheels_cache_location)
+
+ message = (
+ textwrap.dedent(
+ """
+ Package index page cache location: {http_cache_location}
+ Package index page cache size: {http_cache_size}
+ Number of HTTP files: {num_http_files}
+ Wheels location: {wheels_cache_location}
+ Wheels size: {wheels_cache_size}
+ Number of wheels: {package_count}
+ """
+ )
+ .format(
+ http_cache_location=http_cache_location,
+ http_cache_size=http_cache_size,
+ num_http_files=num_http_files,
+ wheels_cache_location=wheels_cache_location,
+ package_count=num_packages,
+ wheels_cache_size=wheels_cache_size,
+ )
+ .strip()
)
- message = textwrap.dedent("""
- Package index page cache location: {http_cache_location}
- Package index page cache size: {http_cache_size}
- Number of HTTP files: {num_http_files}
- Wheels location: {wheels_cache_location}
- Wheels size: {wheels_cache_size}
- Number of wheels: {package_count}
- """).format(
- http_cache_location=http_cache_location,
- http_cache_size=http_cache_size,
- num_http_files=num_http_files,
- wheels_cache_location=wheels_cache_location,
- package_count=num_packages,
- wheels_cache_size=wheels_cache_size,
- ).strip()
-
logger.info(message)
def list_cache_items(self, options: Values, args: List[Any]) -> None:
if len(args) > 1:
- raise CommandError('Too many arguments')
+ raise CommandError("Too many arguments")
if args:
pattern = args[0]
else:
- pattern = '*'
+ pattern = "*"
files = self._find_wheels(options, pattern)
- if options.list_format == 'human':
+ if options.list_format == "human":
self.format_for_human(files)
else:
self.format_for_abspath(files)
def format_for_human(self, files: List[str]) -> None:
if not files:
- logger.info('Nothing cached.')
+ logger.info("Nothing cached.")
return
results = []
for filename in files:
wheel = os.path.basename(filename)
size = filesystem.format_file_size(filename)
- results.append(f' - {wheel} ({size})')
- logger.info('Cache contents:\n')
- logger.info('\n'.join(sorted(results)))
+ results.append(f" - {wheel} ({size})")
+ logger.info("Cache contents:\n")
+ logger.info("\n".join(sorted(results)))
def format_for_abspath(self, files: List[str]) -> None:
if not files:
@@ -156,23 +159,27 @@ def format_for_abspath(self, files: List[str]) -> None:
for filename in files:
results.append(filename)
- logger.info('\n'.join(sorted(results)))
+ logger.info("\n".join(sorted(results)))
def remove_cache_items(self, options: Values, args: List[Any]) -> None:
if len(args) > 1:
- raise CommandError('Too many arguments')
+ raise CommandError("Too many arguments")
if not args:
- raise CommandError('Please provide a pattern')
+ raise CommandError("Please provide a pattern")
files = self._find_wheels(options, args[0])
- # Only fetch http files if no specific pattern given
- if args[0] == '*':
+ no_matching_msg = "No matching packages"
+ if args[0] == "*":
+ # Only fetch http files if no specific pattern given
files += self._find_http_files(options)
+ else:
+ # Add the pattern to the log message
+ no_matching_msg += ' for pattern "{}"'.format(args[0])
if not files:
- raise CommandError('No matching packages')
+ logger.warning(no_matching_msg)
for filename in files:
os.unlink(filename)
@@ -181,19 +188,19 @@ def remove_cache_items(self, options: Values, args: List[Any]) -> None:
def purge_cache(self, options: Values, args: List[Any]) -> None:
if args:
- raise CommandError('Too many arguments')
+ raise CommandError("Too many arguments")
- return self.remove_cache_items(options, ['*'])
+ return self.remove_cache_items(options, ["*"])
def _cache_dir(self, options: Values, subdir: str) -> str:
return os.path.join(options.cache_dir, subdir)
def _find_http_files(self, options: Values) -> List[str]:
- http_dir = self._cache_dir(options, 'http')
- return filesystem.find_files(http_dir, '*')
+ http_dir = self._cache_dir(options, "http")
+ return filesystem.find_files(http_dir, "*")
def _find_wheels(self, options: Values, pattern: str) -> List[str]:
- wheel_dir = self._cache_dir(options, 'wheels')
+ wheel_dir = self._cache_dir(options, "wheels")
# The wheel filename format, as specified in PEP 427, is:
# {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl
diff --git a/pipenv/patched/notpip/_internal/commands/check.py b/pipenv/patched/notpip/_internal/commands/check.py
index 219a02c5c9..ae57ac22d7 100644
--- a/pipenv/patched/notpip/_internal/commands/check.py
+++ b/pipenv/patched/notpip/_internal/commands/check.py
@@ -1,6 +1,6 @@
import logging
from optparse import Values
-from typing import Any, List
+from typing import List
from pipenv.patched.notpip._internal.cli.base_command import Command
from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS
@@ -19,7 +19,7 @@ class CheckCommand(Command):
usage = """
%prog [options]"""
- def run(self, options: Values, args: List[Any]) -> int:
+ def run(self, options: Values, args: List[str]) -> int:
package_set, parsing_probs = create_package_set_from_installed()
missing, conflicting = check_package_set(package_set)
@@ -29,7 +29,9 @@ def run(self, options: Values, args: List[Any]) -> int:
for dependency in missing[project_name]:
write_output(
"%s %s requires %s, which is not installed.",
- project_name, version, dependency[0],
+ project_name,
+ version,
+ dependency[0],
)
for project_name in conflicting:
@@ -37,7 +39,11 @@ def run(self, options: Values, args: List[Any]) -> int:
for dep_name, dep_version, req in conflicting[project_name]:
write_output(
"%s %s has requirement %s, but you have %s %s.",
- project_name, version, req, dep_name, dep_version,
+ project_name,
+ version,
+ req,
+ dep_name,
+ dep_version,
)
if missing or conflicting or parsing_probs:
diff --git a/pipenv/patched/notpip/_internal/commands/completion.py b/pipenv/patched/notpip/_internal/commands/completion.py
index 7a63054638..dac731e530 100644
--- a/pipenv/patched/notpip/_internal/commands/completion.py
+++ b/pipenv/patched/notpip/_internal/commands/completion.py
@@ -12,7 +12,7 @@
"""
COMPLETION_SCRIPTS = {
- 'bash': """
+ "bash": """
_pip_completion()
{{
COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\
@@ -21,7 +21,7 @@
}}
complete -o default -F _pip_completion {prog}
""",
- 'zsh': """
+ "zsh": """
function _pip_completion {{
local words cword
read -Ac words
@@ -32,7 +32,7 @@
}}
compctl -K _pip_completion {prog}
""",
- 'fish': """
+ "fish": """
function __fish_complete_pip
set -lx COMP_WORDS (commandline -o) ""
set -lx COMP_CWORD ( \\
@@ -53,39 +53,44 @@ class CompletionCommand(Command):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '--bash', '-b',
- action='store_const',
- const='bash',
- dest='shell',
- help='Emit completion code for bash')
+ "--bash",
+ "-b",
+ action="store_const",
+ const="bash",
+ dest="shell",
+ help="Emit completion code for bash",
+ )
self.cmd_opts.add_option(
- '--zsh', '-z',
- action='store_const',
- const='zsh',
- dest='shell',
- help='Emit completion code for zsh')
+ "--zsh",
+ "-z",
+ action="store_const",
+ const="zsh",
+ dest="shell",
+ help="Emit completion code for zsh",
+ )
self.cmd_opts.add_option(
- '--fish', '-f',
- action='store_const',
- const='fish',
- dest='shell',
- help='Emit completion code for fish')
+ "--fish",
+ "-f",
+ action="store_const",
+ const="fish",
+ dest="shell",
+ help="Emit completion code for fish",
+ )
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: List[str]) -> int:
"""Prints the completion code of the given shell"""
shells = COMPLETION_SCRIPTS.keys()
- shell_options = ['--' + shell for shell in sorted(shells)]
+ shell_options = ["--" + shell for shell in sorted(shells)]
if options.shell in shells:
script = textwrap.dedent(
- COMPLETION_SCRIPTS.get(options.shell, '').format(
- prog=get_prog())
+ COMPLETION_SCRIPTS.get(options.shell, "").format(prog=get_prog())
)
print(BASE_COMPLETION.format(script=script, shell=options.shell))
return SUCCESS
else:
sys.stderr.write(
- 'ERROR: You must pass {}\n' .format(' or '.join(shell_options))
+ "ERROR: You must pass {}\n".format(" or ".join(shell_options))
)
return SUCCESS
diff --git a/pipenv/patched/notpip/_internal/commands/configuration.py b/pipenv/patched/notpip/_internal/commands/configuration.py
index 658074f2c2..0f15e54073 100644
--- a/pipenv/patched/notpip/_internal/commands/configuration.py
+++ b/pipenv/patched/notpip/_internal/commands/configuration.py
@@ -34,7 +34,7 @@ class ConfigurationCommand(Command):
If none of --user, --global and --site are passed, a virtual
environment configuration file is used if one is active and the file
- exists. Otherwise, all modifications happen on the to the user file by
+ exists. Otherwise, all modifications happen to the user file by
default.
"""
@@ -51,38 +51,38 @@ class ConfigurationCommand(Command):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '--editor',
- dest='editor',
- action='store',
+ "--editor",
+ dest="editor",
+ action="store",
default=None,
help=(
- 'Editor to use to edit the file. Uses VISUAL or EDITOR '
- 'environment variables if not provided.'
- )
+ "Editor to use to edit the file. Uses VISUAL or EDITOR "
+ "environment variables if not provided."
+ ),
)
self.cmd_opts.add_option(
- '--global',
- dest='global_file',
- action='store_true',
+ "--global",
+ dest="global_file",
+ action="store_true",
default=False,
- help='Use the system-wide configuration file only'
+ help="Use the system-wide configuration file only",
)
self.cmd_opts.add_option(
- '--user',
- dest='user_file',
- action='store_true',
+ "--user",
+ dest="user_file",
+ action="store_true",
default=False,
- help='Use the user configuration file only'
+ help="Use the user configuration file only",
)
self.cmd_opts.add_option(
- '--site',
- dest='site_file',
- action='store_true',
+ "--site",
+ dest="site_file",
+ action="store_true",
default=False,
- help='Use the current environment configuration file only'
+ help="Use the current environment configuration file only",
)
self.parser.insert_option_group(0, self.cmd_opts)
@@ -133,11 +133,15 @@ def run(self, options: Values, args: List[str]) -> int:
return SUCCESS
def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]:
- file_options = [key for key, value in (
- (kinds.USER, options.user_file),
- (kinds.GLOBAL, options.global_file),
- (kinds.SITE, options.site_file),
- ) if value]
+ file_options = [
+ key
+ for key, value in (
+ (kinds.USER, options.user_file),
+ (kinds.GLOBAL, options.global_file),
+ (kinds.SITE, options.site_file),
+ )
+ if value
+ ]
if not file_options:
if not need_value:
@@ -194,24 +198,22 @@ def list_config_values(self, options: Values, args: List[str]) -> None:
for fname in files:
with indent_log():
file_exists = os.path.exists(fname)
- write_output("%s, exists: %r",
- fname, file_exists)
+ write_output("%s, exists: %r", fname, file_exists)
if file_exists:
self.print_config_file_values(variant)
def print_config_file_values(self, variant: Kind) -> None:
"""Get key-value pairs from the file of a variant"""
- for name, value in self.configuration.\
- get_values_in_config(variant).items():
+ for name, value in self.configuration.get_values_in_config(variant).items():
with indent_log():
write_output("%s: %s", name, value)
def print_env_var_values(self) -> None:
"""Get key-values pairs present as environment variables"""
- write_output("%s:", 'env_var')
+ write_output("%s:", "env_var")
with indent_log():
for key, value in sorted(self.configuration.get_environ_vars()):
- env_var = f'PIP_{key.upper()}'
+ env_var = f"PIP_{key.upper()}"
write_output("%s=%r", env_var, value)
def open_in_editor(self, options: Values, args: List[str]) -> None:
@@ -225,16 +227,14 @@ def open_in_editor(self, options: Values, args: List[str]) -> None:
subprocess.check_call([editor, fname])
except subprocess.CalledProcessError as e:
raise PipError(
- "Editor Subprocess exited with exit code {}"
- .format(e.returncode)
+ "Editor Subprocess exited with exit code {}".format(e.returncode)
)
def _get_n_args(self, args: List[str], example: str, n: int) -> Any:
- """Helper to make sure the command got the right number of arguments
- """
+ """Helper to make sure the command got the right number of arguments"""
if len(args) != n:
msg = (
- 'Got unexpected number of arguments, expected {}. '
+ "Got unexpected number of arguments, expected {}. "
'(example: "{} config {}")'
).format(n, get_prog(), example)
raise PipError(msg)
diff --git a/pipenv/patched/notpip/_internal/commands/debug.py b/pipenv/patched/notpip/_internal/commands/debug.py
index 97035956d4..20e9e1fd5e 100644
--- a/pipenv/patched/notpip/_internal/commands/debug.py
+++ b/pipenv/patched/notpip/_internal/commands/debug.py
@@ -24,52 +24,46 @@
def show_value(name: str, value: Any) -> None:
- logger.info('%s: %s', name, value)
+ logger.info("%s: %s", name, value)
def show_sys_implementation() -> None:
- logger.info('sys.implementation:')
+ logger.info("sys.implementation:")
implementation_name = sys.implementation.name
with indent_log():
- show_value('name', implementation_name)
+ show_value("name", implementation_name)
def create_vendor_txt_map() -> Dict[str, str]:
vendor_txt_path = os.path.join(
- os.path.dirname(pip_location),
- '_vendor',
- 'vendor.txt'
+ os.path.dirname(pip_location), "_vendor", "vendor.txt"
)
with open(vendor_txt_path) as f:
# Purge non version specifying lines.
# Also, remove any space prefix or suffixes (including comments).
- lines = [line.strip().split(' ', 1)[0]
- for line in f.readlines() if '==' in line]
+ lines = [
+ line.strip().split(" ", 1)[0] for line in f.readlines() if "==" in line
+ ]
# Transform into "module" -> version dict.
- return dict(line.split('==', 1) for line in lines) # type: ignore
+ return dict(line.split("==", 1) for line in lines) # type: ignore
def get_module_from_module_name(module_name: str) -> ModuleType:
# Module name can be uppercase in vendor.txt for some reason...
module_name = module_name.lower()
# PATCH: setuptools is actually only pkg_resources.
- if module_name == 'setuptools':
- module_name = 'pkg_resources'
-
- __import__(
- f'pipenv.patched.notpip._vendor.{module_name}',
- globals(),
- locals(),
- level=0
- )
+ if module_name == "setuptools":
+ module_name = "pkg_resources"
+
+ __import__(f"pipenv.patched.notpip._vendor.{module_name}", globals(), locals(), level=0)
return getattr(pipenv.patched.notpip._vendor, module_name)
def get_vendor_version_from_module(module_name: str) -> Optional[str]:
module = get_module_from_module_name(module_name)
- version = getattr(module, '__version__', None)
+ version = getattr(module, "__version__", None)
if not version:
# Try to find version in debundled module info.
@@ -86,20 +80,24 @@ def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None:
a conflict or if the actual version could not be imported.
"""
for module_name, expected_version in vendor_txt_versions.items():
- extra_message = ''
+ extra_message = ""
actual_version = get_vendor_version_from_module(module_name)
if not actual_version:
- extra_message = ' (Unable to locate actual module version, using'\
- ' vendor.txt specified version)'
+ extra_message = (
+ " (Unable to locate actual module version, using"
+ " vendor.txt specified version)"
+ )
actual_version = expected_version
elif parse_version(actual_version) != parse_version(expected_version):
- extra_message = ' (CONFLICT: vendor.txt suggests version should'\
- ' be {})'.format(expected_version)
- logger.info('%s==%s%s', module_name, actual_version, extra_message)
+ extra_message = (
+ " (CONFLICT: vendor.txt suggests version should"
+ " be {})".format(expected_version)
+ )
+ logger.info("%s==%s%s", module_name, actual_version, extra_message)
def show_vendor_versions() -> None:
- logger.info('vendored library versions:')
+ logger.info("vendored library versions:")
vendor_txt_versions = create_vendor_txt_map()
with indent_log():
@@ -114,11 +112,11 @@ def show_tags(options: Values) -> None:
# Display the target options that were explicitly provided.
formatted_target = target_python.format_given()
- suffix = ''
+ suffix = ""
if formatted_target:
- suffix = f' (target: {formatted_target})'
+ suffix = f" (target: {formatted_target})"
- msg = 'Compatible tags: {}{}'.format(len(tags), suffix)
+ msg = "Compatible tags: {}{}".format(len(tags), suffix)
logger.info(msg)
if options.verbose < 1 and len(tags) > tag_limit:
@@ -133,8 +131,7 @@ def show_tags(options: Values) -> None:
if tags_limited:
msg = (
- '...\n'
- '[First {tag_limit} tags shown. Pass --verbose to show all.]'
+ "...\n[First {tag_limit} tags shown. Pass --verbose to show all.]"
).format(tag_limit=tag_limit)
logger.info(msg)
@@ -142,20 +139,20 @@ def show_tags(options: Values) -> None:
def ca_bundle_info(config: Configuration) -> str:
levels = set()
for key, _ in config.items():
- levels.add(key.split('.')[0])
+ levels.add(key.split(".")[0])
if not levels:
return "Not specified"
- levels_that_override_global = ['install', 'wheel', 'download']
+ levels_that_override_global = ["install", "wheel", "download"]
global_overriding_level = [
level for level in levels if level in levels_that_override_global
]
if not global_overriding_level:
- return 'global'
+ return "global"
- if 'global' in levels:
- levels.remove('global')
+ if "global" in levels:
+ levels.remove("global")
return ", ".join(levels)
@@ -180,20 +177,21 @@ def run(self, options: Values, args: List[str]) -> int:
"details, since the output and options of this command may "
"change without notice."
)
- show_value('pip version', get_pip_version())
- show_value('sys.version', sys.version)
- show_value('sys.executable', sys.executable)
- show_value('sys.getdefaultencoding', sys.getdefaultencoding())
- show_value('sys.getfilesystemencoding', sys.getfilesystemencoding())
+ show_value("pip version", get_pip_version())
+ show_value("sys.version", sys.version)
+ show_value("sys.executable", sys.executable)
+ show_value("sys.getdefaultencoding", sys.getdefaultencoding())
+ show_value("sys.getfilesystemencoding", sys.getfilesystemencoding())
show_value(
- 'locale.getpreferredencoding', locale.getpreferredencoding(),
+ "locale.getpreferredencoding",
+ locale.getpreferredencoding(),
)
- show_value('sys.platform', sys.platform)
+ show_value("sys.platform", sys.platform)
show_sys_implementation()
show_value("'cert' config value", ca_bundle_info(self.parser.config))
- show_value("REQUESTS_CA_BUNDLE", os.environ.get('REQUESTS_CA_BUNDLE'))
- show_value("CURL_CA_BUNDLE", os.environ.get('CURL_CA_BUNDLE'))
+ show_value("REQUESTS_CA_BUNDLE", os.environ.get("REQUESTS_CA_BUNDLE"))
+ show_value("CURL_CA_BUNDLE", os.environ.get("CURL_CA_BUNDLE"))
show_value("pipenv.patched.notpip._vendor.certifi.where()", where())
show_value("pipenv.patched.notpip._vendor.DEBUNDLED", pipenv.patched.notpip._vendor.DEBUNDLED)
diff --git a/pipenv/patched/notpip/_internal/commands/download.py b/pipenv/patched/notpip/_internal/commands/download.py
index 1b3c18a04c..2025bc784f 100644
--- a/pipenv/patched/notpip/_internal/commands/download.py
+++ b/pipenv/patched/notpip/_internal/commands/download.py
@@ -37,7 +37,6 @@ class DownloadCommand(RequirementCommand):
def add_options(self) -> None:
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.requirements())
- self.cmd_opts.add_option(cmdoptions.build_dir())
self.cmd_opts.add_option(cmdoptions.no_deps())
self.cmd_opts.add_option(cmdoptions.global_options())
self.cmd_opts.add_option(cmdoptions.no_binary())
@@ -53,11 +52,14 @@ def add_options(self) -> None:
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
self.cmd_opts.add_option(
- '-d', '--dest', '--destination-dir', '--destination-directory',
- dest='download_dir',
- metavar='dir',
+ "-d",
+ "--dest",
+ "--destination-dir",
+ "--destination-directory",
+ dest="download_dir",
+ metavar="dir",
default=os.curdir,
- help=("Download packages into
."),
+ help="Download packages into .",
)
cmdoptions.add_target_python_options(self.cmd_opts)
@@ -111,6 +113,7 @@ def run(self, options: Values, args: List[str]) -> int:
finder=finder,
download_dir=options.download_dir,
use_user_site=False,
+ verbosity=self.verbosity,
)
resolver = self.make_resolver(
@@ -123,9 +126,7 @@ def run(self, options: Values, args: List[str]) -> int:
self.trace_basic_info(finder)
- requirement_set = resolver.resolve(
- reqs, check_supported_wheels=True
- )
+ requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
downloaded: List[str] = []
for req in requirement_set.requirements.values():
@@ -134,6 +135,6 @@ def run(self, options: Values, args: List[str]) -> int:
preparer.save_linked_requirement(req)
downloaded.append(req.name)
if downloaded:
- write_output('Successfully downloaded %s', ' '.join(downloaded))
+ write_output("Successfully downloaded %s", " ".join(downloaded))
return SUCCESS
diff --git a/pipenv/patched/notpip/_internal/commands/freeze.py b/pipenv/patched/notpip/_internal/commands/freeze.py
index 2344639ff2..90e0afdd2c 100644
--- a/pipenv/patched/notpip/_internal/commands/freeze.py
+++ b/pipenv/patched/notpip/_internal/commands/freeze.py
@@ -8,7 +8,7 @@
from pipenv.patched.notpip._internal.operations.freeze import freeze
from pipenv.patched.notpip._internal.utils.compat import stdlib_pkgs
-DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'}
+DEV_PKGS = {"pip", "setuptools", "distribute", "wheel"}
class FreezeCommand(Command):
@@ -24,39 +24,52 @@ class FreezeCommand(Command):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-r', '--requirement',
- dest='requirements',
- action='append',
+ "-r",
+ "--requirement",
+ dest="requirements",
+ action="append",
default=[],
- metavar='file',
- help="Use the order in the given requirements file and its "
- "comments when generating output. This option can be "
- "used multiple times.")
+ metavar="file",
+ help=(
+ "Use the order in the given requirements file and its "
+ "comments when generating output. This option can be "
+ "used multiple times."
+ ),
+ )
self.cmd_opts.add_option(
- '-l', '--local',
- dest='local',
- action='store_true',
+ "-l",
+ "--local",
+ dest="local",
+ action="store_true",
default=False,
- help='If in a virtualenv that has global access, do not output '
- 'globally-installed packages.')
+ help=(
+ "If in a virtualenv that has global access, do not output "
+ "globally-installed packages."
+ ),
+ )
self.cmd_opts.add_option(
- '--user',
- dest='user',
- action='store_true',
+ "--user",
+ dest="user",
+ action="store_true",
default=False,
- help='Only output packages installed in user-site.')
+ help="Only output packages installed in user-site.",
+ )
self.cmd_opts.add_option(cmdoptions.list_path())
self.cmd_opts.add_option(
- '--all',
- dest='freeze_all',
- action='store_true',
- help='Do not skip these packages in the output:'
- ' {}'.format(', '.join(DEV_PKGS)))
+ "--all",
+ dest="freeze_all",
+ action="store_true",
+ help=(
+ "Do not skip these packages in the output:"
+ " {}".format(", ".join(DEV_PKGS))
+ ),
+ )
self.cmd_opts.add_option(
- '--exclude-editable',
- dest='exclude_editable',
- action='store_true',
- help='Exclude editable package from output.')
+ "--exclude-editable",
+ dest="exclude_editable",
+ action="store_true",
+ help="Exclude editable package from output.",
+ )
self.cmd_opts.add_option(cmdoptions.list_exclude())
self.parser.insert_option_group(0, self.cmd_opts)
@@ -80,5 +93,5 @@ def run(self, options: Values, args: List[str]) -> int:
skip=skip,
exclude_editable=options.exclude_editable,
):
- sys.stdout.write(line + '\n')
+ sys.stdout.write(line + "\n")
return SUCCESS
diff --git a/pipenv/patched/notpip/_internal/commands/hash.py b/pipenv/patched/notpip/_internal/commands/hash.py
index 3955eb3e81..e763e65543 100644
--- a/pipenv/patched/notpip/_internal/commands/hash.py
+++ b/pipenv/patched/notpip/_internal/commands/hash.py
@@ -20,18 +20,21 @@ class HashCommand(Command):
installs.
"""
- usage = '%prog [options] ...'
+ usage = "%prog [options] ..."
ignore_require_venv = True
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-a', '--algorithm',
- dest='algorithm',
+ "-a",
+ "--algorithm",
+ dest="algorithm",
choices=STRONG_HASHES,
- action='store',
+ action="store",
default=FAVORITE_HASH,
- help='The hash algorithm to use: one of {}'.format(
- ', '.join(STRONG_HASHES)))
+ help="The hash algorithm to use: one of {}".format(
+ ", ".join(STRONG_HASHES)
+ ),
+ )
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: List[str]) -> int:
@@ -41,14 +44,15 @@ def run(self, options: Values, args: List[str]) -> int:
algorithm = options.algorithm
for path in args:
- write_output('%s:\n--hash=%s:%s',
- path, algorithm, _hash_of_file(path, algorithm))
+ write_output(
+ "%s:\n--hash=%s:%s", path, algorithm, _hash_of_file(path, algorithm)
+ )
return SUCCESS
def _hash_of_file(path: str, algorithm: str) -> str:
"""Return the hash digest of a file."""
- with open(path, 'rb') as archive:
+ with open(path, "rb") as archive:
hash = hashlib.new(algorithm)
for chunk in read_chunks(archive):
hash.update(chunk)
diff --git a/pipenv/patched/notpip/_internal/commands/help.py b/pipenv/patched/notpip/_internal/commands/help.py
index 5b56dc9a02..184126dac5 100644
--- a/pipenv/patched/notpip/_internal/commands/help.py
+++ b/pipenv/patched/notpip/_internal/commands/help.py
@@ -33,7 +33,7 @@ def run(self, options: Values, args: List[str]) -> int:
if guess:
msg.append(f'maybe you meant "{guess}"')
- raise CommandError(' - '.join(msg))
+ raise CommandError(" - ".join(msg))
command = create_command(cmd_name)
command.parser.print_help()
diff --git a/pipenv/patched/notpip/_internal/commands/index.py b/pipenv/patched/notpip/_internal/commands/index.py
index 1674177299..78982fd03e 100644
--- a/pipenv/patched/notpip/_internal/commands/index.py
+++ b/pipenv/patched/notpip/_internal/commands/index.py
@@ -44,7 +44,7 @@ def add_options(self) -> None:
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, self.cmd_opts)
- def run(self, options: Values, args: List[Any]) -> int:
+ def run(self, options: Values, args: List[str]) -> int:
handlers = {
"versions": self.get_available_package_versions,
}
@@ -97,11 +97,12 @@ def _build_package_finder(
link_collector=link_collector,
selection_prefs=selection_prefs,
target_python=target_python,
+ use_deprecated_html5lib="html5lib" in options.deprecated_features_enabled,
)
def get_available_package_versions(self, options: Values, args: List[Any]) -> None:
if len(args) != 1:
- raise CommandError('You need to specify exactly one argument')
+ raise CommandError("You need to specify exactly one argument")
target_python = cmdoptions.make_target_python(options)
query = args[0]
@@ -115,25 +116,24 @@ def get_available_package_versions(self, options: Values, args: List[Any]) -> No
)
versions: Iterable[Union[LegacyVersion, Version]] = (
- candidate.version
- for candidate in finder.find_all_candidates(query)
+ candidate.version for candidate in finder.find_all_candidates(query)
)
if not options.pre:
# Remove prereleases
- versions = (version for version in versions
- if not version.is_prerelease)
+ versions = (
+ version for version in versions if not version.is_prerelease
+ )
versions = set(versions)
if not versions:
raise DistributionNotFound(
- 'No matching distribution found for {}'.format(query))
+ "No matching distribution found for {}".format(query)
+ )
- formatted_versions = [str(ver) for ver in sorted(
- versions, reverse=True)]
+ formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)]
latest = formatted_versions[0]
- write_output('{} ({})'.format(query, latest))
- write_output('Available versions: {}'.format(
- ', '.join(formatted_versions)))
+ write_output("{} ({})".format(query, latest))
+ write_output("Available versions: {}".format(", ".join(formatted_versions)))
print_dist_installation_info(query, latest)
diff --git a/pipenv/patched/notpip/_internal/commands/install.py b/pipenv/patched/notpip/_internal/commands/install.py
index d4e2296af2..ec8a695a80 100644
--- a/pipenv/patched/notpip/_internal/commands/install.py
+++ b/pipenv/patched/notpip/_internal/commands/install.py
@@ -86,87 +86,103 @@ def add_options(self) -> None:
self.cmd_opts.add_option(cmdoptions.editable())
self.cmd_opts.add_option(
- '-t', '--target',
- dest='target_dir',
- metavar='dir',
+ "-t",
+ "--target",
+ dest="target_dir",
+ metavar="dir",
default=None,
- help='Install packages into . '
- 'By default this will not replace existing files/folders in '
- '. Use --upgrade to replace existing packages in '
- 'with new versions.'
+ help=(
+ "Install packages into . "
+ "By default this will not replace existing files/folders in "
+ ". Use --upgrade to replace existing packages in "
+ "with new versions."
+ ),
)
cmdoptions.add_target_python_options(self.cmd_opts)
self.cmd_opts.add_option(
- '--user',
- dest='use_user_site',
- action='store_true',
- help="Install to the Python user install directory for your "
- "platform. Typically ~/.local/, or %APPDATA%\\Python on "
- "Windows. (See the Python documentation for site.USER_BASE "
- "for full details.)")
+ "--user",
+ dest="use_user_site",
+ action="store_true",
+ help=(
+ "Install to the Python user install directory for your "
+ "platform. Typically ~/.local/, or %APPDATA%\\Python on "
+ "Windows. (See the Python documentation for site.USER_BASE "
+ "for full details.)"
+ ),
+ )
self.cmd_opts.add_option(
- '--no-user',
- dest='use_user_site',
- action='store_false',
- help=SUPPRESS_HELP)
+ "--no-user",
+ dest="use_user_site",
+ action="store_false",
+ help=SUPPRESS_HELP,
+ )
self.cmd_opts.add_option(
- '--root',
- dest='root_path',
- metavar='dir',
+ "--root",
+ dest="root_path",
+ metavar="dir",
default=None,
- help="Install everything relative to this alternate root "
- "directory.")
+ help="Install everything relative to this alternate root directory.",
+ )
self.cmd_opts.add_option(
- '--prefix',
- dest='prefix_path',
- metavar='dir',
+ "--prefix",
+ dest="prefix_path",
+ metavar="dir",
default=None,
- help="Installation prefix where lib, bin and other top-level "
- "folders are placed")
-
- self.cmd_opts.add_option(cmdoptions.build_dir())
+ help=(
+ "Installation prefix where lib, bin and other top-level "
+ "folders are placed"
+ ),
+ )
self.cmd_opts.add_option(cmdoptions.src())
self.cmd_opts.add_option(
- '-U', '--upgrade',
- dest='upgrade',
- action='store_true',
- help='Upgrade all specified packages to the newest available '
- 'version. The handling of dependencies depends on the '
- 'upgrade-strategy used.'
+ "-U",
+ "--upgrade",
+ dest="upgrade",
+ action="store_true",
+ help=(
+ "Upgrade all specified packages to the newest available "
+ "version. The handling of dependencies depends on the "
+ "upgrade-strategy used."
+ ),
)
self.cmd_opts.add_option(
- '--upgrade-strategy',
- dest='upgrade_strategy',
- default='only-if-needed',
- choices=['only-if-needed', 'eager'],
- help='Determines how dependency upgrading should be handled '
- '[default: %default]. '
- '"eager" - dependencies are upgraded regardless of '
- 'whether the currently installed version satisfies the '
- 'requirements of the upgraded package(s). '
- '"only-if-needed" - are upgraded only when they do not '
- 'satisfy the requirements of the upgraded package(s).'
+ "--upgrade-strategy",
+ dest="upgrade_strategy",
+ default="only-if-needed",
+ choices=["only-if-needed", "eager"],
+ help=(
+ "Determines how dependency upgrading should be handled "
+ "[default: %default]. "
+ '"eager" - dependencies are upgraded regardless of '
+ "whether the currently installed version satisfies the "
+ "requirements of the upgraded package(s). "
+ '"only-if-needed" - are upgraded only when they do not '
+ "satisfy the requirements of the upgraded package(s)."
+ ),
)
self.cmd_opts.add_option(
- '--force-reinstall',
- dest='force_reinstall',
- action='store_true',
- help='Reinstall all packages even if they are already '
- 'up-to-date.')
+ "--force-reinstall",
+ dest="force_reinstall",
+ action="store_true",
+ help="Reinstall all packages even if they are already up-to-date.",
+ )
self.cmd_opts.add_option(
- '-I', '--ignore-installed',
- dest='ignore_installed',
- action='store_true',
- help='Ignore the installed packages, overwriting them. '
- 'This can break your system if the existing package '
- 'is of a different version or was installed '
- 'with a different package manager!'
+ "-I",
+ "--ignore-installed",
+ dest="ignore_installed",
+ action="store_true",
+ help=(
+ "Ignore the installed packages, overwriting them. "
+ "This can break your system if the existing package "
+ "is of a different version or was installed "
+ "with a different package manager!"
+ ),
)
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
@@ -249,11 +265,14 @@ def run(self, options: Values, args: List[str]) -> int:
if options.target_dir:
options.ignore_installed = True
options.target_dir = os.path.abspath(options.target_dir)
- if (os.path.exists(options.target_dir) and not
- os.path.isdir(options.target_dir)):
+ if (
+ # fmt: off
+ os.path.exists(options.target_dir) and
+ not os.path.isdir(options.target_dir)
+ # fmt: on
+ ):
raise CommandError(
- "Target path exists but is not a directory, will not "
- "continue."
+ "Target path exists but is not a directory, will not continue."
)
# Create a target directory for using with the target option
@@ -285,9 +304,13 @@ def run(self, options: Values, args: List[str]) -> int:
try:
reqs = self.get_requirements(args, options, finder, session)
- reject_location_related_install_options(
- reqs, options.install_options
- )
+ # Only when installing is it permitted to use PEP 660.
+ # In other circumstances (pip wheel, pip download) we generate
+ # regular (i.e. non editable) metadata and wheels.
+ for req in reqs:
+ req.permit_editable_wheels = True
+
+ reject_location_related_install_options(reqs, options.install_options)
preparer = self.make_requirement_preparer(
temp_build_dir=directory,
@@ -296,6 +319,7 @@ def run(self, options: Values, args: List[str]) -> int:
session=session,
finder=finder,
use_user_site=options.use_user_site,
+ verbosity=self.verbosity,
)
resolver = self.make_resolver(
preparer=preparer,
@@ -324,19 +348,14 @@ def run(self, options: Values, args: List[str]) -> int:
# If we're not replacing an already installed pip,
# we're not modifying it.
modifying_pip = pip_req.satisfied_by is None
- protect_pip_from_modification_on_windows(
- modifying_pip=modifying_pip
- )
+ protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
- check_binary_allowed = get_check_binary_allowed(
- finder.format_control
- )
+ check_binary_allowed = get_check_binary_allowed(finder.format_control)
reqs_to_build = [
- r for r in requirement_set.requirements.values()
- if should_build_for_install_command(
- r, check_binary_allowed
- )
+ r
+ for r in requirement_set.requirements.values()
+ if should_build_for_install_command(r, check_binary_allowed)
]
_, build_failures = build(
@@ -347,36 +366,32 @@ def run(self, options: Values, args: List[str]) -> int:
global_options=[],
)
- # If we're using PEP 517, we cannot do a direct install
+ # If we're using PEP 517, we cannot do a legacy setup.py install
# so we fail here.
pep517_build_failure_names: List[str] = [
- r.name # type: ignore
- for r in build_failures if r.use_pep517
+ r.name for r in build_failures if r.use_pep517 # type: ignore
]
if pep517_build_failure_names:
raise InstallationError(
- "Could not build wheels for {} which use"
- " PEP 517 and cannot be installed directly".format(
+ "Could not build wheels for {}, which is required to "
+ "install pyproject.toml-based projects".format(
", ".join(pep517_build_failure_names)
)
)
# For now, we just warn about failures building legacy
- # requirements, as we'll fall through to a direct
- # install for those.
+ # requirements, as we'll fall through to a setup.py install for
+ # those.
for r in build_failures:
if not r.use_pep517:
r.legacy_install_reason = 8368
- to_install = resolver.get_installation_order(
- requirement_set
- )
+ to_install = resolver.get_installation_order(requirement_set)
# Check for conflicts in the package set we're installing.
conflicts: Optional[ConflictDetails] = None
should_warn_about_conflicts = (
- not options.ignore_dependencies and
- options.warn_about_conflicts
+ not options.ignore_dependencies and options.warn_about_conflicts
)
if should_warn_about_conflicts:
conflicts = self._determine_conflicts(to_install)
@@ -408,7 +423,7 @@ def run(self, options: Values, args: List[str]) -> int:
)
env = get_environment(lib_locations)
- installed.sort(key=operator.attrgetter('name'))
+ installed.sort(key=operator.attrgetter("name"))
items = []
for result in installed:
item = result.name
@@ -426,16 +441,19 @@ def run(self, options: Values, args: List[str]) -> int:
resolver_variant=self.determine_resolver_variant(options),
)
- installed_desc = ' '.join(items)
+ installed_desc = " ".join(items)
if installed_desc:
write_output(
- 'Successfully installed %s', installed_desc,
+ "Successfully installed %s",
+ installed_desc,
)
except OSError as error:
- show_traceback = (self.verbosity >= 1)
+ show_traceback = self.verbosity >= 1
message = create_os_error_message(
- error, show_traceback, options.use_user_site,
+ error,
+ show_traceback,
+ options.use_user_site,
)
logger.error(message, exc_info=show_traceback) # noqa
@@ -461,7 +479,7 @@ def _handle_target_dir(
# Checking both purelib and platlib directories for installed
# packages to be moved to target directory
- scheme = get_scheme('', home=target_temp_dir.path)
+ scheme = get_scheme("", home=target_temp_dir.path)
purelib_dir = scheme.purelib
platlib_dir = scheme.platlib
data_dir = scheme.data
@@ -483,18 +501,18 @@ def _handle_target_dir(
if os.path.exists(target_item_dir):
if not upgrade:
logger.warning(
- 'Target directory %s already exists. Specify '
- '--upgrade to force replacement.',
- target_item_dir
+ "Target directory %s already exists. Specify "
+ "--upgrade to force replacement.",
+ target_item_dir,
)
continue
if os.path.islink(target_item_dir):
logger.warning(
- 'Target directory %s already exists and is '
- 'a link. pip will not automatically replace '
- 'links, please remove if replacement is '
- 'desired.',
- target_item_dir
+ "Target directory %s already exists and is "
+ "a link. pip will not automatically replace "
+ "links, please remove if replacement is "
+ "desired.",
+ target_item_dir,
)
continue
if os.path.isdir(target_item_dir):
@@ -502,10 +520,7 @@ def _handle_target_dir(
else:
os.remove(target_item_dir)
- shutil.move(
- os.path.join(lib_dir, item),
- target_item_dir
- )
+ shutil.move(os.path.join(lib_dir, item), target_item_dir)
def _determine_conflicts(
self, to_install: List[InstallRequirement]
@@ -567,7 +582,7 @@ def _warn_about_conflicts(
requirement=req,
dep_name=dep_name,
dep_version=dep_version,
- you=("you" if resolver_variant == "2020-resolver" else "you'll")
+ you=("you" if resolver_variant == "2020-resolver" else "you'll"),
)
parts.append(message)
@@ -575,14 +590,14 @@ def _warn_about_conflicts(
def get_lib_location_guesses(
- user: bool = False,
- home: Optional[str] = None,
- root: Optional[str] = None,
- isolated: bool = False,
- prefix: Optional[str] = None
+ user: bool = False,
+ home: Optional[str] = None,
+ root: Optional[str] = None,
+ isolated: bool = False,
+ prefix: Optional[str] = None,
) -> List[str]:
scheme = get_scheme(
- '',
+ "",
user=user,
home=home,
root=root,
@@ -594,8 +609,8 @@ def get_lib_location_guesses(
def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
return all(
- test_writable_dir(d) for d in set(
- get_lib_location_guesses(root=root, isolated=isolated))
+ test_writable_dir(d)
+ for d in set(get_lib_location_guesses(root=root, isolated=isolated))
)
@@ -653,8 +668,10 @@ def decide_user_install(
logger.debug("Non-user install because site-packages writeable")
return False
- logger.info("Defaulting to user installation because normal site-packages "
- "is not writeable")
+ logger.info(
+ "Defaulting to user installation because normal site-packages "
+ "is not writeable"
+ )
return True
@@ -664,6 +681,7 @@ def reject_location_related_install_options(
"""If any location-changing --install-option arguments were passed for
requirements or on the command-line, then show a deprecation warning.
"""
+
def format_options(option_names: Iterable[str]) -> List[str]:
return ["--{}".format(name.replace("_", "-")) for name in option_names]
@@ -683,9 +701,7 @@ def format_options(option_names: Iterable[str]) -> List[str]:
location_options = parse_distutils_args(options)
if location_options:
offenders.append(
- "{!r} from command line".format(
- format_options(location_options.keys())
- )
+ "{!r} from command line".format(format_options(location_options.keys()))
)
if not offenders:
@@ -694,9 +710,7 @@ def format_options(option_names: Iterable[str]) -> List[str]:
raise CommandError(
"Location-changing options found in --install-option: {}."
" This is unsupported, use pip-level options like --user,"
- " --prefix, --root, and --target instead.".format(
- "; ".join(offenders)
- )
+ " --prefix, --root, and --target instead.".format("; ".join(offenders))
)
@@ -727,18 +741,25 @@ def create_os_error_message(
permissions_part = "Check the permissions"
if not running_under_virtualenv() and not using_user_site:
- parts.extend([
- user_option_part, " or ",
- permissions_part.lower(),
- ])
+ parts.extend(
+ [
+ user_option_part,
+ " or ",
+ permissions_part.lower(),
+ ]
+ )
else:
parts.append(permissions_part)
parts.append(".\n")
# Suggest the user to enable Long Paths if path length is
# more than 260
- if (WINDOWS and error.errno == errno.ENOENT and error.filename and
- len(error.filename) > 260):
+ if (
+ WINDOWS
+ and error.errno == errno.ENOENT
+ and error.filename
+ and len(error.filename) > 260
+ ):
parts.append(
"HINT: This error might have occurred since "
"this system does not have Windows Long Path "
diff --git a/pipenv/patched/notpip/_internal/commands/list.py b/pipenv/patched/notpip/_internal/commands/list.py
index 61d5ad6d72..6ec466bd19 100644
--- a/pipenv/patched/notpip/_internal/commands/list.py
+++ b/pipenv/patched/notpip/_internal/commands/list.py
@@ -14,8 +14,8 @@
from pipenv.patched.notpip._internal.metadata import BaseDistribution, get_environment
from pipenv.patched.notpip._internal.models.selection_prefs import SelectionPreferences
from pipenv.patched.notpip._internal.network.session import PipSession
-from pipenv.patched.notpip._internal.utils.misc import stdlib_pkgs, tabulate, write_output
-from pipenv.patched.notpip._internal.utils.parallel import map_multithread
+from pipenv.patched.notpip._internal.utils.compat import stdlib_pkgs
+from pipenv.patched.notpip._internal.utils.misc import tabulate, write_output
if TYPE_CHECKING:
from pipenv.patched.notpip._internal.metadata.base import DistributionVersion
@@ -26,6 +26,7 @@ class _DistWithLatestInfo(BaseDistribution):
These will be populated during ``get_outdated()``. This is dirty but
makes the rest of the code much cleaner.
"""
+
latest_version: DistributionVersion
latest_filetype: str
@@ -48,77 +49,85 @@ class ListCommand(IndexGroupCommand):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-o', '--outdated',
- action='store_true',
+ "-o",
+ "--outdated",
+ action="store_true",
default=False,
- help='List outdated packages')
+ help="List outdated packages",
+ )
self.cmd_opts.add_option(
- '-u', '--uptodate',
- action='store_true',
+ "-u",
+ "--uptodate",
+ action="store_true",
default=False,
- help='List uptodate packages')
+ help="List uptodate packages",
+ )
self.cmd_opts.add_option(
- '-e', '--editable',
- action='store_true',
+ "-e",
+ "--editable",
+ action="store_true",
default=False,
- help='List editable projects.')
+ help="List editable projects.",
+ )
self.cmd_opts.add_option(
- '-l', '--local',
- action='store_true',
+ "-l",
+ "--local",
+ action="store_true",
default=False,
- help=('If in a virtualenv that has global access, do not list '
- 'globally-installed packages.'),
+ help=(
+ "If in a virtualenv that has global access, do not list "
+ "globally-installed packages."
+ ),
)
self.cmd_opts.add_option(
- '--user',
- dest='user',
- action='store_true',
+ "--user",
+ dest="user",
+ action="store_true",
default=False,
- help='Only output packages installed in user-site.')
+ help="Only output packages installed in user-site.",
+ )
self.cmd_opts.add_option(cmdoptions.list_path())
self.cmd_opts.add_option(
- '--pre',
- action='store_true',
+ "--pre",
+ action="store_true",
default=False,
- help=("Include pre-release and development versions. By default, "
- "pip only finds stable versions."),
+ help=(
+ "Include pre-release and development versions. By default, "
+ "pip only finds stable versions."
+ ),
)
self.cmd_opts.add_option(
- '--format',
- action='store',
- dest='list_format',
+ "--format",
+ action="store",
+ dest="list_format",
default="columns",
- choices=('columns', 'freeze', 'json'),
- help="Select the output format among: columns (default), freeze, "
- "or json",
+ choices=("columns", "freeze", "json"),
+ help="Select the output format among: columns (default), freeze, or json",
)
self.cmd_opts.add_option(
- '--not-required',
- action='store_true',
- dest='not_required',
- help="List packages that are not dependencies of "
- "installed packages.",
+ "--not-required",
+ action="store_true",
+ dest="not_required",
+ help="List packages that are not dependencies of installed packages.",
)
self.cmd_opts.add_option(
- '--exclude-editable',
- action='store_false',
- dest='include_editable',
- help='Exclude editable package from output.',
+ "--exclude-editable",
+ action="store_false",
+ dest="include_editable",
+ help="Exclude editable package from output.",
)
self.cmd_opts.add_option(
- '--include-editable',
- action='store_true',
- dest='include_editable',
- help='Include editable package from output.',
+ "--include-editable",
+ action="store_true",
+ dest="include_editable",
+ help="Include editable package from output.",
default=True,
)
self.cmd_opts.add_option(cmdoptions.list_exclude())
- index_opts = cmdoptions.make_option_group(
- cmdoptions.index_group, self.parser
- )
+ index_opts = cmdoptions.make_option_group(cmdoptions.index_group, self.parser)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, self.cmd_opts)
@@ -140,12 +149,12 @@ def _build_package_finder(
return PackageFinder.create(
link_collector=link_collector,
selection_prefs=selection_prefs,
+ use_deprecated_html5lib="html5lib" in options.deprecated_features_enabled,
)
def run(self, options: Values, args: List[str]) -> int:
if options.outdated and options.uptodate:
- raise CommandError(
- "Options --outdated and --uptodate cannot be combined.")
+ raise CommandError("Options --outdated and --uptodate cannot be combined.")
cmdoptions.check_list_path_option(options)
@@ -183,7 +192,8 @@ def get_outdated(
self, packages: "_ProcessedDists", options: Values
) -> "_ProcessedDists":
return [
- dist for dist in self.iter_packages_latest_infos(packages, options)
+ dist
+ for dist in self.iter_packages_latest_infos(packages, options)
if dist.latest_version > dist.version
]
@@ -191,7 +201,8 @@ def get_uptodate(
self, packages: "_ProcessedDists", options: Values
) -> "_ProcessedDists":
return [
- dist for dist in self.iter_packages_latest_infos(packages, options)
+ dist
+ for dist in self.iter_packages_latest_infos(packages, options)
if dist.latest_version == dist.version
]
@@ -216,13 +227,16 @@ def iter_packages_latest_infos(
finder = self._build_package_finder(options, session)
def latest_info(
- dist: "_DistWithLatestInfo"
+ dist: "_DistWithLatestInfo",
) -> Optional["_DistWithLatestInfo"]:
all_candidates = finder.find_all_candidates(dist.canonical_name)
if not options.pre:
# Remove prereleases
- all_candidates = [candidate for candidate in all_candidates
- if not candidate.version.is_prerelease]
+ all_candidates = [
+ candidate
+ for candidate in all_candidates
+ if not candidate.version.is_prerelease
+ ]
evaluator = finder.make_candidate_evaluator(
project_name=dist.canonical_name,
@@ -233,14 +247,14 @@ def latest_info(
remote_version = best_candidate.version
if best_candidate.link.is_wheel:
- typ = 'wheel'
+ typ = "wheel"
else:
- typ = 'sdist'
+ typ = "sdist"
dist.latest_version = remote_version
dist.latest_filetype = typ
return dist
- for dist in map_multithread(latest_info, packages):
+ for dist in map(latest_info, packages):
if dist is not None:
yield dist
@@ -251,17 +265,18 @@ def output_package_listing(
packages,
key=lambda dist: dist.canonical_name,
)
- if options.list_format == 'columns' and packages:
+ if options.list_format == "columns" and packages:
data, header = format_for_columns(packages, options)
self.output_package_listing_columns(data, header)
- elif options.list_format == 'freeze':
+ elif options.list_format == "freeze":
for dist in packages:
if options.verbose >= 1:
- write_output("%s==%s (%s)", dist.raw_name,
- dist.version, dist.location)
+ write_output(
+ "%s==%s (%s)", dist.raw_name, dist.version, dist.location
+ )
else:
write_output("%s==%s", dist.raw_name, dist.version)
- elif options.list_format == 'json':
+ elif options.list_format == "json":
write_output(format_for_json(packages, options))
def output_package_listing_columns(
@@ -275,7 +290,7 @@ def output_package_listing_columns(
# Create and add a separator.
if len(data) > 0:
- pkg_strings.insert(1, " ".join(map(lambda x: '-' * x, sizes)))
+ pkg_strings.insert(1, " ".join(map(lambda x: "-" * x, sizes)))
for val in pkg_strings:
write_output(val)
@@ -288,19 +303,22 @@ def format_for_columns(
Convert the package data into something usable
by output_package_listing_columns.
"""
+ header = ["Package", "Version"]
+
running_outdated = options.outdated
- # Adjust the header for the `pip list --outdated` case.
if running_outdated:
- header = ["Package", "Version", "Latest", "Type"]
- else:
- header = ["Package", "Version"]
+ header.extend(["Latest", "Type"])
- data = []
- if options.verbose >= 1 or any(x.editable for x in pkgs):
+ has_editables = any(x.editable for x in pkgs)
+ if has_editables:
+ header.append("Editable project location")
+
+ if options.verbose >= 1:
header.append("Location")
if options.verbose >= 1:
header.append("Installer")
+ data = []
for proj in pkgs:
# if we're working on the 'outdated' list, separate out the
# latest_version and type
@@ -310,7 +328,10 @@ def format_for_columns(
row.append(str(proj.latest_version))
row.append(proj.latest_filetype)
- if options.verbose >= 1 or proj.editable:
+ if has_editables:
+ row.append(proj.editable_project_location or "")
+
+ if options.verbose >= 1:
row.append(proj.location or "")
if options.verbose >= 1:
row.append(proj.installer)
@@ -324,14 +345,17 @@ def format_for_json(packages: "_ProcessedDists", options: Values) -> str:
data = []
for dist in packages:
info = {
- 'name': dist.raw_name,
- 'version': str(dist.version),
+ "name": dist.raw_name,
+ "version": str(dist.version),
}
if options.verbose >= 1:
- info['location'] = dist.location or ""
- info['installer'] = dist.installer
+ info["location"] = dist.location or ""
+ info["installer"] = dist.installer
if options.outdated:
- info['latest_version'] = str(dist.latest_version)
- info['latest_filetype'] = dist.latest_filetype
+ info["latest_version"] = str(dist.latest_version)
+ info["latest_filetype"] = dist.latest_filetype
+ editable_project_location = dist.editable_project_location
+ if editable_project_location:
+ info["editable_project_location"] = editable_project_location
data.append(info)
return json.dumps(data)
diff --git a/pipenv/patched/notpip/_internal/commands/search.py b/pipenv/patched/notpip/_internal/commands/search.py
index 29a0c57ec5..0b5ed0d3fd 100644
--- a/pipenv/patched/notpip/_internal/commands/search.py
+++ b/pipenv/patched/notpip/_internal/commands/search.py
@@ -27,6 +27,7 @@ class TransformedHit(TypedDict):
summary: str
versions: List[str]
+
logger = logging.getLogger(__name__)
@@ -39,17 +40,19 @@ class SearchCommand(Command, SessionCommandMixin):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-i', '--index',
- dest='index',
- metavar='URL',
+ "-i",
+ "--index",
+ dest="index",
+ metavar="URL",
default=PyPI.pypi_url,
- help='Base URL of Python Package Index (default %default)')
+ help="Base URL of Python Package Index (default %default)",
+ )
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: List[str]) -> int:
if not args:
- raise CommandError('Missing required argument (search query).')
+ raise CommandError("Missing required argument (search query).")
query = args
pypi_hits = self.search(query, options)
hits = transform_hits(pypi_hits)
@@ -71,7 +74,7 @@ def search(self, query: List[str], options: Values) -> List[Dict[str, str]]:
transport = PipXmlrpcTransport(index_url, session)
pypi = xmlrpc.client.ServerProxy(index_url, transport)
try:
- hits = pypi.search({'name': query, 'summary': query}, 'or')
+ hits = pypi.search({"name": query, "summary": query}, "or")
except xmlrpc.client.Fault as fault:
message = "XMLRPC request failed [code: {code}]\n{string}".format(
code=fault.faultCode,
@@ -90,22 +93,22 @@ def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]:
"""
packages: Dict[str, "TransformedHit"] = OrderedDict()
for hit in hits:
- name = hit['name']
- summary = hit['summary']
- version = hit['version']
+ name = hit["name"]
+ summary = hit["summary"]
+ version = hit["version"]
if name not in packages.keys():
packages[name] = {
- 'name': name,
- 'summary': summary,
- 'versions': [version],
+ "name": name,
+ "summary": summary,
+ "versions": [version],
}
else:
- packages[name]['versions'].append(version)
+ packages[name]["versions"].append(version)
# if this is the highest version, replace summary and score
- if version == highest_version(packages[name]['versions']):
- packages[name]['summary'] = summary
+ if version == highest_version(packages[name]["versions"]):
+ packages[name]["summary"] = summary
return list(packages.values())
@@ -116,14 +119,17 @@ def print_dist_installation_info(name: str, latest: str) -> None:
if dist is not None:
with indent_log():
if dist.version == latest:
- write_output('INSTALLED: %s (latest)', dist.version)
+ write_output("INSTALLED: %s (latest)", dist.version)
else:
- write_output('INSTALLED: %s', dist.version)
+ write_output("INSTALLED: %s", dist.version)
if parse_version(latest).pre:
- write_output('LATEST: %s (pre-release; install'
- ' with "pip install --pre")', latest)
+ write_output(
+ "LATEST: %s (pre-release; install"
+ " with `pip install --pre`)",
+ latest,
+ )
else:
- write_output('LATEST: %s', latest)
+ write_output("LATEST: %s", latest)
def print_results(
@@ -134,25 +140,29 @@ def print_results(
if not hits:
return
if name_column_width is None:
- name_column_width = max([
- len(hit['name']) + len(highest_version(hit.get('versions', ['-'])))
- for hit in hits
- ]) + 4
+ name_column_width = (
+ max(
+ [
+ len(hit["name"]) + len(highest_version(hit.get("versions", ["-"])))
+ for hit in hits
+ ]
+ )
+ + 4
+ )
for hit in hits:
- name = hit['name']
- summary = hit['summary'] or ''
- latest = highest_version(hit.get('versions', ['-']))
+ name = hit["name"]
+ summary = hit["summary"] or ""
+ latest = highest_version(hit.get("versions", ["-"]))
if terminal_width is not None:
target_width = terminal_width - name_column_width - 5
if target_width > 10:
# wrap and indent summary to fit terminal
summary_lines = textwrap.wrap(summary, target_width)
- summary = ('\n' + ' ' * (name_column_width + 3)).join(
- summary_lines)
+ summary = ("\n" + " " * (name_column_width + 3)).join(summary_lines)
- name_latest = f'{name} ({latest})'
- line = f'{name_latest:{name_column_width}} - {summary}'
+ name_latest = f"{name} ({latest})"
+ line = f"{name_latest:{name_column_width}} - {summary}"
try:
write_output(line)
print_dist_installation_info(name, latest)
diff --git a/pipenv/patched/notpip/_internal/commands/show.py b/pipenv/patched/notpip/_internal/commands/show.py
index e80677ba6b..ed99966e2f 100644
--- a/pipenv/patched/notpip/_internal/commands/show.py
+++ b/pipenv/patched/notpip/_internal/commands/show.py
@@ -1,8 +1,6 @@
-import csv
import logging
-import pathlib
from optparse import Values
-from typing import Iterator, List, NamedTuple, Optional, Tuple
+from typing import Iterator, List, NamedTuple, Optional
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
@@ -27,23 +25,26 @@ class ShowCommand(Command):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-f', '--files',
- dest='files',
- action='store_true',
+ "-f",
+ "--files",
+ dest="files",
+ action="store_true",
default=False,
- help='Show the full list of installed files for each package.')
+ help="Show the full list of installed files for each package.",
+ )
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: List[str]) -> int:
if not args:
- logger.warning('ERROR: Please provide a package name or names.')
+ logger.warning("ERROR: Please provide a package name or names.")
return ERROR
query = args
results = search_packages_info(query)
if not print_results(
- results, list_files=options.files, verbose=options.verbose):
+ results, list_files=options.files, verbose=options.verbose
+ ):
return ERROR
return SUCCESS
@@ -66,33 +67,6 @@ class _PackageInfo(NamedTuple):
files: Optional[List[str]]
-def _covert_legacy_entry(entry: Tuple[str, ...], info: Tuple[str, ...]) -> str:
- """Convert a legacy installed-files.txt path into modern RECORD path.
-
- The legacy format stores paths relative to the info directory, while the
- modern format stores paths relative to the package root, e.g. the
- site-packages directory.
-
- :param entry: Path parts of the installed-files.txt entry.
- :param info: Path parts of the egg-info directory relative to package root.
- :returns: The converted entry.
-
- For best compatibility with symlinks, this does not use ``abspath()`` or
- ``Path.resolve()``, but tries to work with path parts:
-
- 1. While ``entry`` starts with ``..``, remove the equal amounts of parts
- from ``info``; if ``info`` is empty, start appending ``..`` instead.
- 2. Join the two directly.
- """
- while entry and entry[0] == "..":
- if not info or info[-1] == "..":
- info += ("..",)
- else:
- info = info[:-1]
- entry = entry[1:]
- return str(pathlib.Path(*info, *entry))
-
-
def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
"""
Gather details from installed distributions. Print distribution name,
@@ -102,53 +76,20 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
"""
env = get_default_environment()
- installed = {
- dist.canonical_name: dist
- for dist in env.iter_distributions()
- }
+ installed = {dist.canonical_name: dist for dist in env.iter_distributions()}
query_names = [canonicalize_name(name) for name in query]
missing = sorted(
[name for name, pkg in zip(query, query_names) if pkg not in installed]
)
if missing:
- logger.warning('Package(s) not found: %s', ', '.join(missing))
+ logger.warning("Package(s) not found: %s", ", ".join(missing))
- def _get_requiring_packages(current_dist: BaseDistribution) -> List[str]:
- return [
+ def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]:
+ return (
dist.metadata["Name"] or "UNKNOWN"
for dist in installed.values()
- if current_dist.canonical_name in {
- canonicalize_name(d.name) for d in dist.iter_dependencies()
- }
- ]
-
- def _files_from_record(dist: BaseDistribution) -> Optional[Iterator[str]]:
- try:
- text = dist.read_text('RECORD')
- except FileNotFoundError:
- return None
- # This extra Path-str cast normalizes entries.
- return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
-
- def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]:
- try:
- text = dist.read_text('installed-files.txt')
- except FileNotFoundError:
- return None
- paths = (p for p in text.splitlines(keepends=False) if p)
- root = dist.location
- info = dist.info_directory
- if root is None or info is None:
- return paths
- try:
- info_rel = pathlib.Path(info).relative_to(root)
- except ValueError: # info is not relative to root.
- return paths
- if not info_rel.parts: # info *is* root.
- return paths
- return (
- _covert_legacy_entry(pathlib.Path(p).parts, info_rel.parts)
- for p in paths
+ if current_dist.canonical_name
+ in {canonicalize_name(d.name) for d in dist.iter_dependencies()}
)
for query_name in query_names:
@@ -157,13 +98,16 @@ def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]:
except KeyError:
continue
+ requires = sorted((req.name for req in dist.iter_dependencies()), key=str.lower)
+ required_by = sorted(_get_requiring_packages(dist), key=str.lower)
+
try:
- entry_points_text = dist.read_text('entry_points.txt')
+ entry_points_text = dist.read_text("entry_points.txt")
entry_points = entry_points_text.splitlines(keepends=False)
except FileNotFoundError:
entry_points = []
- files_iter = _files_from_record(dist) or _files_from_legacy(dist)
+ files_iter = dist.iter_declared_entries()
if files_iter is None:
files: Optional[List[str]] = None
else:
@@ -175,8 +119,8 @@ def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]:
name=dist.raw_name,
version=str(dist.version),
location=dist.location or "",
- requires=[req.name for req in dist.iter_dependencies()],
- required_by=_get_requiring_packages(dist),
+ requires=requires,
+ required_by=required_by,
installer=dist.installer,
metadata_version=dist.metadata_version or "",
classifiers=metadata.get_all("Classifier", []),
@@ -212,8 +156,8 @@ def print_results(
write_output("Author-email: %s", dist.author_email)
write_output("License: %s", dist.license)
write_output("Location: %s", dist.location)
- write_output("Requires: %s", ', '.join(dist.requires))
- write_output("Required-by: %s", ', '.join(dist.required_by))
+ write_output("Requires: %s", ", ".join(dist.requires))
+ write_output("Required-by: %s", ", ".join(dist.required_by))
if verbose:
write_output("Metadata-Version: %s", dist.metadata_version)
diff --git a/pipenv/patched/notpip/_internal/commands/uninstall.py b/pipenv/patched/notpip/_internal/commands/uninstall.py
index ebc380c31a..2b67216fcd 100644
--- a/pipenv/patched/notpip/_internal/commands/uninstall.py
+++ b/pipenv/patched/notpip/_internal/commands/uninstall.py
@@ -35,19 +35,24 @@ class UninstallCommand(Command, SessionCommandMixin):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-r', '--requirement',
- dest='requirements',
- action='append',
+ "-r",
+ "--requirement",
+ dest="requirements",
+ action="append",
default=[],
- metavar='file',
- help='Uninstall all the packages listed in the given requirements '
- 'file. This option can be used multiple times.',
+ metavar="file",
+ help=(
+ "Uninstall all the packages listed in the given requirements "
+ "file. This option can be used multiple times."
+ ),
)
self.cmd_opts.add_option(
- '-y', '--yes',
- dest='yes',
- action='store_true',
- help="Don't ask for confirmation of uninstall deletions.")
+ "-y",
+ "--yes",
+ dest="yes",
+ action="store_true",
+ help="Don't ask for confirmation of uninstall deletions.",
+ )
self.parser.insert_option_group(0, self.cmd_opts)
@@ -57,7 +62,8 @@ def run(self, options: Values, args: List[str]) -> int:
reqs_to_uninstall = {}
for name in args:
req = install_req_from_line(
- name, isolated=options.isolated_mode,
+ name,
+ isolated=options.isolated_mode,
)
if req.name:
reqs_to_uninstall[canonicalize_name(req.name)] = req
@@ -70,18 +76,16 @@ def run(self, options: Values, args: List[str]) -> int:
)
for filename in options.requirements:
for parsed_req in parse_requirements(
- filename,
- options=options,
- session=session):
+ filename, options=options, session=session
+ ):
req = install_req_from_parsed_requirement(
- parsed_req,
- isolated=options.isolated_mode
+ parsed_req, isolated=options.isolated_mode
)
if req.name:
reqs_to_uninstall[canonicalize_name(req.name)] = req
if not reqs_to_uninstall:
raise InstallationError(
- f'You must give at least one requirement to {self.name} (see '
+ f"You must give at least one requirement to {self.name} (see "
f'"pip help {self.name}")'
)
@@ -91,7 +95,8 @@ def run(self, options: Values, args: List[str]) -> int:
for req in reqs_to_uninstall.values():
uninstall_pathset = req.uninstall(
- auto_confirm=options.yes, verbose=self.verbosity > 0,
+ auto_confirm=options.yes,
+ verbose=self.verbosity > 0,
)
if uninstall_pathset:
uninstall_pathset.commit()
diff --git a/pipenv/patched/notpip/_internal/commands/wheel.py b/pipenv/patched/notpip/_internal/commands/wheel.py
index ffb3d700c9..3962a371ce 100644
--- a/pipenv/patched/notpip/_internal/commands/wheel.py
+++ b/pipenv/patched/notpip/_internal/commands/wheel.py
@@ -43,12 +43,15 @@ class WheelCommand(RequirementCommand):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-w', '--wheel-dir',
- dest='wheel_dir',
- metavar='dir',
+ "-w",
+ "--wheel-dir",
+ dest="wheel_dir",
+ metavar="dir",
default=os.curdir,
- help=("Build wheels into , where the default is the "
- "current working directory."),
+ help=(
+ "Build wheels into , where the default is the "
+ "current working directory."
+ ),
)
self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())
@@ -62,13 +65,12 @@ def add_options(self) -> None:
self.cmd_opts.add_option(cmdoptions.src())
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
self.cmd_opts.add_option(cmdoptions.no_deps())
- self.cmd_opts.add_option(cmdoptions.build_dir())
self.cmd_opts.add_option(cmdoptions.progress_bar())
self.cmd_opts.add_option(
- '--no-verify',
- dest='no_verify',
- action='store_true',
+ "--no-verify",
+ dest="no_verify",
+ action="store_true",
default=False,
help="Don't verify if built wheel is valid.",
)
@@ -77,11 +79,13 @@ def add_options(self) -> None:
self.cmd_opts.add_option(cmdoptions.global_options())
self.cmd_opts.add_option(
- '--pre',
- action='store_true',
+ "--pre",
+ action="store_true",
default=False,
- help=("Include pre-release and development versions. By default, "
- "pip only finds stable versions."),
+ help=(
+ "Include pre-release and development versions. By default, "
+ "pip only finds stable versions."
+ ),
)
self.cmd_opts.add_option(cmdoptions.require_hashes())
@@ -124,6 +128,7 @@ def run(self, options: Values, args: List[str]) -> int:
finder=finder,
download_dir=options.wheel_dir,
use_user_site=False,
+ verbosity=self.verbosity,
)
resolver = self.make_resolver(
@@ -137,9 +142,7 @@ def run(self, options: Values, args: List[str]) -> int:
self.trace_basic_info(finder)
- requirement_set = resolver.resolve(
- reqs, check_supported_wheels=True
- )
+ requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
reqs_to_build: List[InstallRequirement] = []
for req in requirement_set.requirements.values():
@@ -165,12 +168,11 @@ def run(self, options: Values, args: List[str]) -> int:
except OSError as e:
logger.warning(
"Building wheel for %s failed: %s",
- req.name, e,
+ req.name,
+ e,
)
build_failures.append(req)
if len(build_failures) != 0:
- raise CommandError(
- "Failed to build one or more wheels"
- )
+ raise CommandError("Failed to build one or more wheels")
return SUCCESS
diff --git a/pipenv/patched/notpip/_internal/configuration.py b/pipenv/patched/notpip/_internal/configuration.py
index fc6e4db7b8..2a40f65415 100644
--- a/pipenv/patched/notpip/_internal/configuration.py
+++ b/pipenv/patched/notpip/_internal/configuration.py
@@ -13,7 +13,6 @@
import configparser
import locale
-import logging
import os
import sys
from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple
@@ -24,41 +23,39 @@
)
from pipenv.patched.notpip._internal.utils import appdirs
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
+from pipenv.patched.notpip._internal.utils.logging import getLogger
from pipenv.patched.notpip._internal.utils.misc import ensure_dir, enum
RawConfigParser = configparser.RawConfigParser # Shorthand
Kind = NewType("Kind", str)
-CONFIG_BASENAME = 'pip.ini' if WINDOWS else 'pip.conf'
+CONFIG_BASENAME = "pip.ini" if WINDOWS else "pip.conf"
ENV_NAMES_IGNORED = "version", "help"
# The kinds of configurations there are.
kinds = enum(
- USER="user", # User Specific
- GLOBAL="global", # System Wide
- SITE="site", # [Virtual] Environment Specific
- ENV="env", # from PIP_CONFIG_FILE
+ USER="user", # User Specific
+ GLOBAL="global", # System Wide
+ SITE="site", # [Virtual] Environment Specific
+ ENV="env", # from PIP_CONFIG_FILE
ENV_VAR="env-var", # from Environment Variables
)
OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR
VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.SITE
-logger = logging.getLogger(__name__)
+logger = getLogger(__name__)
# NOTE: Maybe use the optionx attribute to normalize keynames.
-def _normalize_name(name):
- # type: (str) -> str
- """Make a name consistent regardless of source (environment or file)
- """
- name = name.lower().replace('_', '-')
- if name.startswith('--'):
+def _normalize_name(name: str) -> str:
+ """Make a name consistent regardless of source (environment or file)"""
+ name = name.lower().replace("_", "-")
+ if name.startswith("--"):
name = name[2:] # only prefer long opts
return name
-def _disassemble_key(name):
- # type: (str) -> List[str]
+def _disassemble_key(name: str) -> List[str]:
if "." not in name:
error_message = (
"Key does not contain dot separated section and key. "
@@ -68,22 +65,18 @@ def _disassemble_key(name):
return name.split(".", 1)
-def get_configuration_files():
- # type: () -> Dict[Kind, List[str]]
+def get_configuration_files() -> Dict[Kind, List[str]]:
global_config_files = [
- os.path.join(path, CONFIG_BASENAME)
- for path in appdirs.site_config_dirs('pip')
+ os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip")
]
site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME)
legacy_config_file = os.path.join(
- os.path.expanduser('~'),
- 'pip' if WINDOWS else '.pip',
+ os.path.expanduser("~"),
+ "pip" if WINDOWS else ".pip",
CONFIG_BASENAME,
)
- new_config_file = os.path.join(
- appdirs.user_config_dir("pip"), CONFIG_BASENAME
- )
+ new_config_file = os.path.join(appdirs.user_config_dir("pip"), CONFIG_BASENAME)
return {
kinds.GLOBAL: global_config_files,
kinds.SITE: [site_config_file],
@@ -105,8 +98,7 @@ class Configuration:
and the data stored is also nice.
"""
- def __init__(self, isolated, load_only=None):
- # type: (bool, Optional[Kind]) -> None
+ def __init__(self, isolated: bool, load_only: Optional[Kind] = None) -> None:
super().__init__()
if load_only is not None and load_only not in VALID_LOAD_ONLY:
@@ -119,54 +111,44 @@ def __init__(self, isolated, load_only=None):
self.load_only = load_only
# Because we keep track of where we got the data from
- self._parsers = {
+ self._parsers: Dict[Kind, List[Tuple[str, RawConfigParser]]] = {
variant: [] for variant in OVERRIDE_ORDER
- } # type: Dict[Kind, List[Tuple[str, RawConfigParser]]]
- self._config = {
+ }
+ self._config: Dict[Kind, Dict[str, Any]] = {
variant: {} for variant in OVERRIDE_ORDER
- } # type: Dict[Kind, Dict[str, Any]]
- self._modified_parsers = [] # type: List[Tuple[str, RawConfigParser]]
+ }
+ self._modified_parsers: List[Tuple[str, RawConfigParser]] = []
- def load(self):
- # type: () -> None
- """Loads configuration from configuration files and environment
- """
+ def load(self) -> None:
+ """Loads configuration from configuration files and environment"""
self._load_config_files()
if not self.isolated:
self._load_environment_vars()
- def get_file_to_edit(self):
- # type: () -> Optional[str]
- """Returns the file with highest priority in configuration
- """
- assert self.load_only is not None, \
- "Need to be specified a file to be editing"
+ def get_file_to_edit(self) -> Optional[str]:
+ """Returns the file with highest priority in configuration"""
+ assert self.load_only is not None, "Need to be specified a file to be editing"
try:
return self._get_parser_to_modify()[0]
except IndexError:
return None
- def items(self):
- # type: () -> Iterable[Tuple[str, Any]]
+ def items(self) -> Iterable[Tuple[str, Any]]:
"""Returns key-value pairs like dict.items() representing the loaded
configuration
"""
return self._dictionary.items()
- def get_value(self, key):
- # type: (str) -> Any
- """Get a value from the configuration.
- """
+ def get_value(self, key: str) -> Any:
+ """Get a value from the configuration."""
try:
return self._dictionary[key]
except KeyError:
raise ConfigurationError(f"No such key - {key}")
- def set_value(self, key, value):
- # type: (str, Any) -> None
- """Modify a value in the configuration.
- """
+ def set_value(self, key: str, value: Any) -> None:
+ """Modify a value in the configuration."""
self._ensure_have_load_only()
assert self.load_only
@@ -183,8 +165,7 @@ def set_value(self, key, value):
self._config[self.load_only][key] = value
self._mark_as_modified(fname, parser)
- def unset_value(self, key):
- # type: (str) -> None
+ def unset_value(self, key: str) -> None:
"""Unset a value in the configuration."""
self._ensure_have_load_only()
@@ -196,8 +177,9 @@ def unset_value(self, key):
if parser is not None:
section, name = _disassemble_key(key)
- if not (parser.has_section(section)
- and parser.remove_option(section, name)):
+ if not (
+ parser.has_section(section) and parser.remove_option(section, name)
+ ):
# The option was not removed.
raise ConfigurationError(
"Fatal Internal error [id=1]. Please report as a bug."
@@ -210,10 +192,8 @@ def unset_value(self, key):
del self._config[self.load_only][key]
- def save(self):
- # type: () -> None
- """Save the current in-memory state.
- """
+ def save(self) -> None:
+ """Save the current in-memory state."""
self._ensure_have_load_only()
for fname, parser in self._modified_parsers:
@@ -229,17 +209,14 @@ def save(self):
# Private routines
#
- def _ensure_have_load_only(self):
- # type: () -> None
+ def _ensure_have_load_only(self) -> None:
if self.load_only is None:
raise ConfigurationError("Needed a specific file to be modifying.")
logger.debug("Will be working with %s variant only", self.load_only)
@property
- def _dictionary(self):
- # type: () -> Dict[str, Any]
- """A dictionary representing the loaded configuration.
- """
+ def _dictionary(self) -> Dict[str, Any]:
+ """A dictionary representing the loaded configuration."""
# NOTE: Dictionaries are not populated if not loaded. So, conditionals
# are not needed here.
retval = {}
@@ -249,10 +226,8 @@ def _dictionary(self):
return retval
- def _load_config_files(self):
- # type: () -> None
- """Loads configuration from configuration files
- """
+ def _load_config_files(self) -> None:
+ """Loads configuration from configuration files"""
config_files = dict(self.iter_config_files())
if config_files[kinds.ENV][0:1] == [os.devnull]:
logger.debug(
@@ -266,9 +241,7 @@ def _load_config_files(self):
# If there's specific variant set in `load_only`, load only
# that variant, not the others.
if self.load_only is not None and variant != self.load_only:
- logger.debug(
- "Skipping file '%s' (variant: %s)", fname, variant
- )
+ logger.debug("Skipping file '%s' (variant: %s)", fname, variant)
continue
parser = self._load_file(variant, fname)
@@ -276,9 +249,8 @@ def _load_config_files(self):
# Keeping track of the parsers used
self._parsers[variant].append((fname, parser))
- def _load_file(self, variant, fname):
- # type: (Kind, str) -> RawConfigParser
- logger.debug("For variant '%s', will try loading '%s'", variant, fname)
+ def _load_file(self, variant: Kind, fname: str) -> RawConfigParser:
+ logger.verbose("For variant '%s', will try loading '%s'", variant, fname)
parser = self._construct_parser(fname)
for section in parser.sections():
@@ -287,22 +259,20 @@ def _load_file(self, variant, fname):
return parser
- def _construct_parser(self, fname):
- # type: (str) -> RawConfigParser
+ def _construct_parser(self, fname: str) -> RawConfigParser:
parser = configparser.RawConfigParser()
# If there is no such file, don't bother reading it but create the
# parser anyway, to hold the data.
# Doing this is useful when modifying and saving files, where we don't
# need to construct a parser.
if os.path.exists(fname):
+ locale_encoding = locale.getpreferredencoding(False)
try:
- parser.read(fname)
+ parser.read(fname, encoding=locale_encoding)
except UnicodeDecodeError:
# See https://github.com/pypa/pip/issues/4963
raise ConfigurationFileCouldNotBeLoaded(
- reason="contains invalid {} characters".format(
- locale.getpreferredencoding(False)
- ),
+ reason=f"contains invalid {locale_encoding} characters",
fname=fname,
)
except configparser.Error as error:
@@ -310,16 +280,15 @@ def _construct_parser(self, fname):
raise ConfigurationFileCouldNotBeLoaded(error=error)
return parser
- def _load_environment_vars(self):
- # type: () -> None
- """Loads configuration from environment variables
- """
+ def _load_environment_vars(self) -> None:
+ """Loads configuration from environment variables"""
self._config[kinds.ENV_VAR].update(
self._normalized_keys(":env:", self.get_environ_vars())
)
- def _normalized_keys(self, section, items):
- # type: (str, Iterable[Tuple[str, Any]]) -> Dict[str, Any]
+ def _normalized_keys(
+ self, section: str, items: Iterable[Tuple[str, Any]]
+ ) -> Dict[str, Any]:
"""Normalizes items to construct a dictionary with normalized keys.
This routine is where the names become keys and are made the same
@@ -331,8 +300,7 @@ def _normalized_keys(self, section, items):
normalized[key] = val
return normalized
- def get_environ_vars(self):
- # type: () -> Iterable[Tuple[str, str]]
+ def get_environ_vars(self) -> Iterable[Tuple[str, str]]:
"""Returns a generator with all environmental vars with prefix PIP_"""
for key, val in os.environ.items():
if key.startswith("PIP_"):
@@ -341,8 +309,7 @@ def get_environ_vars(self):
yield name, val
# XXX: This is patched in the tests.
- def iter_config_files(self):
- # type: () -> Iterable[Tuple[Kind, List[str]]]
+ def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]:
"""Yields variant and configuration files associated with it.
This should be treated like items of a dictionary.
@@ -350,7 +317,7 @@ def iter_config_files(self):
# SMELL: Move the conditions out of this function
# environment variables have the lowest priority
- config_file = os.environ.get('PIP_CONFIG_FILE', None)
+ config_file = os.environ.get("PIP_CONFIG_FILE", None)
if config_file is not None:
yield kinds.ENV, [config_file]
else:
@@ -372,13 +339,11 @@ def iter_config_files(self):
# finally virtualenv configuration first trumping others
yield kinds.SITE, config_files[kinds.SITE]
- def get_values_in_config(self, variant):
- # type: (Kind) -> Dict[str, Any]
+ def get_values_in_config(self, variant: Kind) -> Dict[str, Any]:
"""Get values present in a config file"""
return self._config[variant]
- def _get_parser_to_modify(self):
- # type: () -> Tuple[str, RawConfigParser]
+ def _get_parser_to_modify(self) -> Tuple[str, RawConfigParser]:
# Determine which parser to modify
assert self.load_only
parsers = self._parsers[self.load_only]
@@ -392,12 +357,10 @@ def _get_parser_to_modify(self):
return parsers[-1]
# XXX: This is patched in the tests.
- def _mark_as_modified(self, fname, parser):
- # type: (str, RawConfigParser) -> None
+ def _mark_as_modified(self, fname: str, parser: RawConfigParser) -> None:
file_parser_tuple = (fname, parser)
if file_parser_tuple not in self._modified_parsers:
self._modified_parsers.append(file_parser_tuple)
- def __repr__(self):
- # type: () -> str
+ def __repr__(self) -> str:
return f"{self.__class__.__name__}({self._dictionary!r})"
diff --git a/pipenv/patched/notpip/_internal/distributions/base.py b/pipenv/patched/notpip/_internal/distributions/base.py
index fabc9b81a3..3dbc7f2363 100644
--- a/pipenv/patched/notpip/_internal/distributions/base.py
+++ b/pipenv/patched/notpip/_internal/distributions/base.py
@@ -1,9 +1,7 @@
import abc
-from typing import Optional
-
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
from pipenv.patched.notpip._internal.index.package_finder import PackageFinder
+from pipenv.patched.notpip._internal.metadata.base import BaseDistribution
from pipenv.patched.notpip._internal.req import InstallRequirement
@@ -28,7 +26,7 @@ def __init__(self, req: InstallRequirement) -> None:
self.req = req
@abc.abstractmethod
- def get_pkg_resources_distribution(self) -> Optional[Distribution]:
+ def get_metadata_distribution(self) -> BaseDistribution:
raise NotImplementedError()
@abc.abstractmethod
diff --git a/pipenv/patched/notpip/_internal/distributions/installed.py b/pipenv/patched/notpip/_internal/distributions/installed.py
index 127602b7bc..b7184c6368 100644
--- a/pipenv/patched/notpip/_internal/distributions/installed.py
+++ b/pipenv/patched/notpip/_internal/distributions/installed.py
@@ -1,9 +1,6 @@
-from typing import Optional
-
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
-
from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution
from pipenv.patched.notpip._internal.index.package_finder import PackageFinder
+from pipenv.patched.notpip._internal.metadata import BaseDistribution
class InstalledDistribution(AbstractDistribution):
@@ -13,7 +10,8 @@ class InstalledDistribution(AbstractDistribution):
been computed.
"""
- def get_pkg_resources_distribution(self) -> Optional[Distribution]:
+ def get_metadata_distribution(self) -> BaseDistribution:
+ assert self.req.satisfied_by is not None, "not actually installed"
return self.req.satisfied_by
def prepare_distribution_metadata(
diff --git a/pipenv/patched/notpip/_internal/distributions/sdist.py b/pipenv/patched/notpip/_internal/distributions/sdist.py
index 910fe48c75..d2add10ba4 100644
--- a/pipenv/patched/notpip/_internal/distributions/sdist.py
+++ b/pipenv/patched/notpip/_internal/distributions/sdist.py
@@ -1,12 +1,11 @@
import logging
-from typing import Set, Tuple
-
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
+from typing import Iterable, Set, Tuple
from pipenv.patched.notpip._internal.build_env import BuildEnvironment
from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.index.package_finder import PackageFinder
+from pipenv.patched.notpip._internal.metadata import BaseDistribution
from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message
logger = logging.getLogger(__name__)
@@ -19,7 +18,7 @@ class SourceDistribution(AbstractDistribution):
generated, either using PEP 517 or using the legacy `setup.py egg_info`.
"""
- def get_pkg_resources_distribution(self) -> Distribution:
+ def get_metadata_distribution(self) -> BaseDistribution:
return self.req.get_dist()
def prepare_distribution_metadata(
@@ -31,28 +30,23 @@ def prepare_distribution_metadata(
# Set up the build isolation, if this requirement should be isolated
should_isolate = self.req.use_pep517 and build_isolation
if should_isolate:
- self._setup_isolation(finder)
+ # Setup an isolated environment and install the build backend static
+ # requirements in it.
+ self._prepare_build_backend(finder)
+ # Check that if the requirement is editable, it either supports PEP 660 or
+ # has a setup.py or a setup.cfg. This cannot be done earlier because we need
+ # to setup the build backend to verify it supports build_editable, nor can
+ # it be done later, because we want to avoid installing build requirements
+ # needlessly. Doing it here also works around setuptools generating
+ # UNKNOWN.egg-info when running get_requires_for_build_wheel on a directory
+ # without setup.py nor setup.cfg.
+ self.req.isolated_editable_sanity_check()
+ # Install the dynamic build requirements.
+ self._install_build_reqs(finder)
self.req.prepare_metadata()
- def _setup_isolation(self, finder: PackageFinder) -> None:
- def _raise_conflicts(
- conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]]
- ) -> None:
- format_string = (
- "Some build dependencies for {requirement} "
- "conflict with {conflicting_with}: {description}."
- )
- error_message = format_string.format(
- requirement=self.req,
- conflicting_with=conflicting_with,
- description=", ".join(
- f"{installed} is incompatible with {wanted}"
- for installed, wanted in sorted(conflicting)
- ),
- )
- raise InstallationError(error_message)
-
+ def _prepare_build_backend(self, finder: PackageFinder) -> None:
# Isolate in a BuildEnvironment and install the build-time
# requirements.
pyproject_requires = self.req.pyproject_requires
@@ -60,13 +54,13 @@ def _raise_conflicts(
self.req.build_env = BuildEnvironment()
self.req.build_env.install_requirements(
- finder, pyproject_requires, "overlay", "Installing build dependencies"
+ finder, pyproject_requires, "overlay", kind="build dependencies"
)
conflicting, missing = self.req.build_env.check_requirements(
self.req.requirements_to_check
)
if conflicting:
- _raise_conflicts("PEP 517/518 supported requirements", conflicting)
+ self._raise_conflicts("PEP 517/518 supported requirements", conflicting)
if missing:
logger.warning(
"Missing build requirements in pyproject.toml for %s.",
@@ -77,19 +71,57 @@ def _raise_conflicts(
"pip cannot fall back to setuptools without %s.",
" and ".join(map(repr, sorted(missing))),
)
- # Install any extra build dependencies that the backend requests.
- # This must be done in a second pass, as the pyproject.toml
- # dependencies must be installed before we can call the backend.
+
+ def _get_build_requires_wheel(self) -> Iterable[str]:
with self.req.build_env:
runner = runner_with_spinner_message("Getting requirements to build wheel")
backend = self.req.pep517_backend
assert backend is not None
with backend.subprocess_runner(runner):
- reqs = backend.get_requires_for_build_wheel()
+ return backend.get_requires_for_build_wheel()
- conflicting, missing = self.req.build_env.check_requirements(reqs)
+ def _get_build_requires_editable(self) -> Iterable[str]:
+ with self.req.build_env:
+ runner = runner_with_spinner_message(
+ "Getting requirements to build editable"
+ )
+ backend = self.req.pep517_backend
+ assert backend is not None
+ with backend.subprocess_runner(runner):
+ return backend.get_requires_for_build_editable()
+
+ def _install_build_reqs(self, finder: PackageFinder) -> None:
+ # Install any extra build dependencies that the backend requests.
+ # This must be done in a second pass, as the pyproject.toml
+ # dependencies must be installed before we can call the backend.
+ if (
+ self.req.editable
+ and self.req.permit_editable_wheels
+ and self.req.supports_pyproject_editable()
+ ):
+ build_reqs = self._get_build_requires_editable()
+ else:
+ build_reqs = self._get_build_requires_wheel()
+ conflicting, missing = self.req.build_env.check_requirements(build_reqs)
if conflicting:
- _raise_conflicts("the backend dependencies", conflicting)
+ self._raise_conflicts("the backend dependencies", conflicting)
self.req.build_env.install_requirements(
- finder, missing, "normal", "Installing backend dependencies"
+ finder, missing, "normal", kind="backend dependencies"
+ )
+
+ def _raise_conflicts(
+ self, conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]]
+ ) -> None:
+ format_string = (
+ "Some build dependencies for {requirement} "
+ "conflict with {conflicting_with}: {description}."
+ )
+ error_message = format_string.format(
+ requirement=self.req,
+ conflicting_with=conflicting_with,
+ description=", ".join(
+ f"{installed} is incompatible with {wanted}"
+ for installed, wanted in sorted(conflicting_reqs)
+ ),
)
+ raise InstallationError(error_message)
diff --git a/pipenv/patched/notpip/_internal/distributions/wheel.py b/pipenv/patched/notpip/_internal/distributions/wheel.py
index 91542844c7..7b5e17a2a0 100644
--- a/pipenv/patched/notpip/_internal/distributions/wheel.py
+++ b/pipenv/patched/notpip/_internal/distributions/wheel.py
@@ -1,10 +1,12 @@
-from zipfile import ZipFile
-
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
+from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._internal.distributions.base import AbstractDistribution
from pipenv.patched.notpip._internal.index.package_finder import PackageFinder
-from pipenv.patched.notpip._internal.utils.wheel import pkg_resources_distribution_for_wheel
+from pipenv.patched.notpip._internal.metadata import (
+ BaseDistribution,
+ FilesystemWheel,
+ get_wheel_distribution,
+)
class WheelDistribution(AbstractDistribution):
@@ -13,20 +15,15 @@ class WheelDistribution(AbstractDistribution):
This does not need any preparation as wheels can be directly unpacked.
"""
- def get_pkg_resources_distribution(self) -> Distribution:
+ def get_metadata_distribution(self) -> BaseDistribution:
"""Loads the metadata from the wheel file into memory and returns a
Distribution that uses it, not relying on the wheel file or
requirement.
"""
- # Set as part of preparation during download.
- assert self.req.local_file_path
- # Wheels are never unnamed.
- assert self.req.name
-
- with ZipFile(self.req.local_file_path, allowZip64=True) as z:
- return pkg_resources_distribution_for_wheel(
- z, self.req.name, self.req.local_file_path
- )
+ assert self.req.local_file_path, "Set as part of preparation during download"
+ assert self.req.name, "Wheels are never unnamed"
+ wheel = FilesystemWheel(self.req.local_file_path)
+ return get_wheel_distribution(wheel, canonicalize_name(self.req.name))
def prepare_distribution_metadata(
self, finder: PackageFinder, build_isolation: bool
diff --git a/pipenv/patched/notpip/_internal/exceptions.py b/pipenv/patched/notpip/_internal/exceptions.py
index 79fc0edbe3..1c805f776a 100644
--- a/pipenv/patched/notpip/_internal/exceptions.py
+++ b/pipenv/patched/notpip/_internal/exceptions.py
@@ -1,22 +1,174 @@
-"""Exceptions used throughout package"""
+"""Exceptions used throughout package.
+
+This module MUST NOT try to import from anything within `pipenv.patched.notpip._internal` to
+operate. This is expected to be importable from any/all files within the
+subpackage and, thus, should not depend on them.
+"""
import configparser
+import re
from itertools import chain, groupby, repeat
-from typing import TYPE_CHECKING, Dict, List, Optional
+from typing import TYPE_CHECKING, Dict, List, Optional, Union
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
from pipenv.patched.notpip._vendor.requests.models import Request, Response
+from pipenv.patched.notpip._vendor.rich.console import Console, ConsoleOptions, RenderResult
+from pipenv.patched.notpip._vendor.rich.markup import escape
+from pipenv.patched.notpip._vendor.rich.text import Text
if TYPE_CHECKING:
from hashlib import _Hash
+ from typing import Literal
+ from pipenv.patched.notpip._internal.metadata import BaseDistribution
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
+#
+# Scaffolding
+#
+def _is_kebab_case(s: str) -> bool:
+ return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None
+
+
+def _prefix_with_indent(
+ s: Union[Text, str],
+ console: Console,
+ *,
+ prefix: str,
+ indent: str,
+) -> Text:
+ if isinstance(s, Text):
+ text = s
+ else:
+ text = console.render_str(s)
+
+ return console.render_str(prefix, overflow="ignore") + console.render_str(
+ f"\n{indent}", overflow="ignore"
+ ).join(text.split(allow_blank=True))
+
+
class PipError(Exception):
- """Base pip exception"""
+ """The base pip error."""
+
+
+class DiagnosticPipError(PipError):
+ """An error, that presents diagnostic information to the user.
+
+ This contains a bunch of logic, to enable pretty presentation of our error
+ messages. Each error gets a unique reference. Each error can also include
+ additional context, a hint and/or a note -- which are presented with the
+ main error message in a consistent style.
+
+ This is adapted from the error output styling in `sphinx-theme-builder`.
+ """
+
+ reference: str
+
+ def __init__(
+ self,
+ *,
+ kind: 'Literal["error", "warning"]' = "error",
+ reference: Optional[str] = None,
+ message: Union[str, Text],
+ context: Optional[Union[str, Text]],
+ hint_stmt: Optional[Union[str, Text]],
+ note_stmt: Optional[Union[str, Text]] = None,
+ link: Optional[str] = None,
+ ) -> None:
+ # Ensure a proper reference is provided.
+ if reference is None:
+ assert hasattr(self, "reference"), "error reference not provided!"
+ reference = self.reference
+ assert _is_kebab_case(reference), "error reference must be kebab-case!"
+
+ self.kind = kind
+ self.reference = reference
+
+ self.message = message
+ self.context = context
+
+ self.note_stmt = note_stmt
+ self.hint_stmt = hint_stmt
+
+ self.link = link
+
+ super().__init__(f"<{self.__class__.__name__}: {self.reference}>")
+
+ def __repr__(self) -> str:
+ return (
+ f"<{self.__class__.__name__}("
+ f"reference={self.reference!r}, "
+ f"message={self.message!r}, "
+ f"context={self.context!r}, "
+ f"note_stmt={self.note_stmt!r}, "
+ f"hint_stmt={self.hint_stmt!r}"
+ ")>"
+ )
+
+ def __rich_console__(
+ self,
+ console: Console,
+ options: ConsoleOptions,
+ ) -> RenderResult:
+ colour = "red" if self.kind == "error" else "yellow"
+
+ yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"
+ yield ""
+
+ if not options.ascii_only:
+ # Present the main message, with relevant context indented.
+ if self.context is not None:
+ yield _prefix_with_indent(
+ self.message,
+ console,
+ prefix=f"[{colour}]×[/] ",
+ indent=f"[{colour}]│[/] ",
+ )
+ yield _prefix_with_indent(
+ self.context,
+ console,
+ prefix=f"[{colour}]╰─>[/] ",
+ indent=f"[{colour}] [/] ",
+ )
+ else:
+ yield _prefix_with_indent(
+ self.message,
+ console,
+ prefix="[red]×[/] ",
+ indent=" ",
+ )
+ else:
+ yield self.message
+ if self.context is not None:
+ yield ""
+ yield self.context
+
+ if self.note_stmt is not None or self.hint_stmt is not None:
+ yield ""
+
+ if self.note_stmt is not None:
+ yield _prefix_with_indent(
+ self.note_stmt,
+ console,
+ prefix="[magenta bold]note[/]: ",
+ indent=" ",
+ )
+ if self.hint_stmt is not None:
+ yield _prefix_with_indent(
+ self.hint_stmt,
+ console,
+ prefix="[cyan bold]hint[/]: ",
+ indent=" ",
+ )
+ if self.link is not None:
+ yield ""
+ yield f"Link: {self.link}"
+
+#
+# Actual Errors
+#
class ConfigurationError(PipError):
"""General exception in configuration"""
@@ -29,17 +181,78 @@ class UninstallationError(PipError):
"""General exception during uninstallation"""
+class BadHTMLDoctypeDeclaration(DiagnosticPipError):
+ reference = "bad-index-doctype"
+
+ def __init__(self, *, url: str) -> None:
+ super().__init__(
+ kind="warning",
+ message=(
+ "The package index page being used does not have a proper HTML "
+ "doctype declaration."
+ ),
+ context=f"Problematic URL: {escape(url)}",
+ note_stmt="This is an issue with the page at the URL mentioned above.",
+ hint_stmt=(
+ "You might need to reach out to the owner of that package index, "
+ "to get this fixed. "
+ "See https://github.com/pypa/pip/issues/10825 for context."
+ ),
+ )
+
+
+class MissingHTMLDoctypeDeclaration(BadHTMLDoctypeDeclaration):
+ reference = "missing-index-doctype"
+
+
+class MissingPyProjectBuildRequires(DiagnosticPipError):
+ """Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
+
+ reference = "missing-pyproject-build-system-requires"
+
+ def __init__(self, *, package: str) -> None:
+ super().__init__(
+ message=f"Can not process {escape(package)}",
+ context=Text(
+ "This package has an invalid pyproject.toml file.\n"
+ "The [build-system] table is missing the mandatory `requires` key."
+ ),
+ note_stmt="This is an issue with the package mentioned above, not pip.",
+ hint_stmt=Text("See PEP 518 for the detailed specification."),
+ )
+
+
+class InvalidPyProjectBuildRequires(DiagnosticPipError):
+ """Raised when pyproject.toml an invalid `build-system.requires`."""
+
+ reference = "invalid-pyproject-build-system-requires"
+
+ def __init__(self, *, package: str, reason: str) -> None:
+ super().__init__(
+ message=f"Can not process {escape(package)}",
+ context=Text(
+ "This package has an invalid `build-system.requires` key in "
+ f"pyproject.toml.\n{reason}"
+ ),
+ note_stmt="This is an issue with the package mentioned above, not pip.",
+ hint_stmt=Text("See PEP 518 for the detailed specification."),
+ )
+
+
class NoneMetadataError(PipError):
- """
- Raised when accessing "METADATA" or "PKG-INFO" metadata for a
- pipenv.patched.notpip._vendor.pkg_resources.Distribution object and
- `dist.has_metadata('METADATA')` returns True but
- `dist.get_metadata('METADATA')` returns None (and similarly for
- "PKG-INFO").
+ """Raised when accessing a Distribution's "METADATA" or "PKG-INFO".
+
+ This signifies an inconsistency, when the Distribution claims to have
+ the metadata file (if not, raise ``FileNotFoundError`` instead), but is
+ not actually able to produce its content. This may be due to permission
+ errors.
"""
- def __init__(self, dist, metadata_name):
- # type: (Distribution, str) -> None
+ def __init__(
+ self,
+ dist: "BaseDistribution",
+ metadata_name: str,
+ ) -> None:
"""
:param dist: A Distribution object.
:param metadata_name: The name of the metadata being accessed
@@ -48,28 +261,24 @@ def __init__(self, dist, metadata_name):
self.dist = dist
self.metadata_name = metadata_name
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
# Use `dist` in the error message because its stringification
# includes more information, like the version and location.
- return (
- 'None {} metadata found for distribution: {}'.format(
- self.metadata_name, self.dist,
- )
+ return "None {} metadata found for distribution: {}".format(
+ self.metadata_name,
+ self.dist,
)
class UserInstallationInvalid(InstallationError):
"""A --user install is requested on an environment without user site."""
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
return "User base directory is not specified"
class InvalidSchemeCombination(InstallationError):
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
before = ", ".join(str(a) for a in self.args[:-1])
return f"Cannot set {before} and {self.args[-1]} together"
@@ -102,8 +311,9 @@ class PreviousBuildDirError(PipError):
class NetworkConnectionError(PipError):
"""HTTP connection error"""
- def __init__(self, error_msg, response=None, request=None):
- # type: (str, Response, Request) -> None
+ def __init__(
+ self, error_msg: str, response: Response = None, request: Request = None
+ ) -> None:
"""
Initialize NetworkConnectionError with `request` and `response`
objects.
@@ -111,13 +321,15 @@ def __init__(self, error_msg, response=None, request=None):
self.response = response
self.request = request
self.error_msg = error_msg
- if (self.response is not None and not self.request and
- hasattr(response, 'request')):
+ if (
+ self.response is not None
+ and not self.request
+ and hasattr(response, "request")
+ ):
self.request = self.response.request
super().__init__(error_msg, response, request)
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
return str(self.error_msg)
@@ -129,6 +341,17 @@ class UnsupportedWheel(InstallationError):
"""Unsupported wheel."""
+class InvalidWheel(InstallationError):
+ """Invalid (e.g. corrupt) wheel."""
+
+ def __init__(self, location: str, name: str):
+ self.location = location
+ self.name = name
+
+ def __str__(self) -> str:
+ return f"Wheel '{self.name}' located at {self.location} is invalid."
+
+
class MetadataInconsistent(InstallationError):
"""Built metadata contains inconsistent information.
@@ -136,15 +359,16 @@ class MetadataInconsistent(InstallationError):
that do not match the information previously obtained from sdist filename
or user-supplied ``#egg=`` value.
"""
- def __init__(self, ireq, field, f_val, m_val):
- # type: (InstallRequirement, str, str, str) -> None
+
+ def __init__(
+ self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str
+ ) -> None:
self.ireq = ireq
self.field = field
self.f_val = f_val
self.m_val = m_val
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
template = (
"Requested {} has inconsistent {}: "
"filename has {!r}, but metadata has {!r}"
@@ -152,51 +376,102 @@ def __str__(self):
return template.format(self.ireq, self.field, self.f_val, self.m_val)
-class InstallationSubprocessError(InstallationError):
- """A subprocess call failed during installation."""
- def __init__(self, returncode, description):
- # type: (int, str) -> None
- self.returncode = returncode
- self.description = description
+class LegacyInstallFailure(DiagnosticPipError):
+ """Error occurred while executing `setup.py install`"""
- def __str__(self):
- # type: () -> str
- return (
- "Command errored out with exit status {}: {} "
- "Check the logs for full command output."
- ).format(self.returncode, self.description)
+ reference = "legacy-install-failure"
+
+ def __init__(self, package_details: str) -> None:
+ super().__init__(
+ message="Encountered error while trying to install package.",
+ context=package_details,
+ hint_stmt="See above for output from the failure.",
+ note_stmt="This is an issue with the package mentioned above, not pip.",
+ )
+
+
+class InstallationSubprocessError(DiagnosticPipError, InstallationError):
+ """A subprocess call failed."""
+
+ reference = "subprocess-exited-with-error"
+
+ def __init__(
+ self,
+ *,
+ command_description: str,
+ exit_code: int,
+ output_lines: Optional[List[str]],
+ ) -> None:
+ if output_lines is None:
+ output_prompt = Text("See above for output.")
+ else:
+ output_prompt = (
+ Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
+ + Text("".join(output_lines))
+ + Text.from_markup(R"[red]\[end of output][/]")
+ )
+
+ super().__init__(
+ message=(
+ f"[green]{escape(command_description)}[/] did not run successfully.\n"
+ f"exit code: {exit_code}"
+ ),
+ context=output_prompt,
+ hint_stmt=None,
+ note_stmt=(
+ "This error originates from a subprocess, and is likely not a "
+ "problem with pip."
+ ),
+ )
+
+ self.command_description = command_description
+ self.exit_code = exit_code
+
+ def __str__(self) -> str:
+ return f"{self.command_description} exited with {self.exit_code}"
+
+
+class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
+ reference = "metadata-generation-failed"
+
+ def __init__(
+ self,
+ *,
+ package_details: str,
+ ) -> None:
+ super(InstallationSubprocessError, self).__init__(
+ message="Encountered error while generating package metadata.",
+ context=escape(package_details),
+ hint_stmt="See above for details.",
+ note_stmt="This is an issue with the package mentioned above, not pip.",
+ )
+
+ def __str__(self) -> str:
+ return "metadata generation failed"
class HashErrors(InstallationError):
"""Multiple HashError instances rolled into one for reporting"""
- def __init__(self):
- # type: () -> None
- self.errors = [] # type: List[HashError]
+ def __init__(self) -> None:
+ self.errors: List["HashError"] = []
- def append(self, error):
- # type: (HashError) -> None
+ def append(self, error: "HashError") -> None:
self.errors.append(error)
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
lines = []
self.errors.sort(key=lambda e: e.order)
for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):
lines.append(cls.head)
lines.extend(e.body() for e in errors_of_cls)
if lines:
- return '\n'.join(lines)
- return ''
+ return "\n".join(lines)
+ return ""
- def __nonzero__(self):
- # type: () -> bool
+ def __bool__(self) -> bool:
return bool(self.errors)
- def __bool__(self):
- # type: () -> bool
- return self.__nonzero__()
-
class HashError(InstallationError):
"""
@@ -214,12 +489,12 @@ class HashError(InstallationError):
typically available earlier.
"""
- req = None # type: Optional[InstallRequirement]
- head = ''
- order = -1 # type: int
- def body(self):
- # type: () -> str
+ req: Optional["InstallRequirement"] = None
+ head = ""
+ order: int = -1
+
+ def body(self) -> str:
"""Return a summary of me for display under the heading.
This default implementation simply prints a description of the
@@ -229,21 +504,19 @@ def body(self):
its link already populated by the resolver's _populate_link().
"""
- return f' {self._requirement_name()}'
+ return f" {self._requirement_name()}"
- def __str__(self):
- # type: () -> str
- return f'{self.head}\n{self.body()}'
+ def __str__(self) -> str:
+ return f"{self.head}\n{self.body()}"
- def _requirement_name(self):
- # type: () -> str
+ def _requirement_name(self) -> str:
"""Return a description of the requirement that triggered me.
This default implementation returns long description of the req, with
line numbers
"""
- return str(self.req) if self.req else 'unknown package'
+ return str(self.req) if self.req else "unknown package"
class VcsHashUnsupported(HashError):
@@ -251,8 +524,10 @@ class VcsHashUnsupported(HashError):
we don't have a method for hashing those."""
order = 0
- head = ("Can't verify hashes for these requirements because we don't "
- "have a way to hash version control repositories:")
+ head = (
+ "Can't verify hashes for these requirements because we don't "
+ "have a way to hash version control repositories:"
+ )
class DirectoryUrlHashUnsupported(HashError):
@@ -260,32 +535,34 @@ class DirectoryUrlHashUnsupported(HashError):
we don't have a method for hashing those."""
order = 1
- head = ("Can't verify hashes for these file:// requirements because they "
- "point to directories:")
+ head = (
+ "Can't verify hashes for these file:// requirements because they "
+ "point to directories:"
+ )
class HashMissing(HashError):
"""A hash was needed for a requirement but is absent."""
order = 2
- head = ('Hashes are required in --require-hashes mode, but they are '
- 'missing from some requirements. Here is a list of those '
- 'requirements along with the hashes their downloaded archives '
- 'actually had. Add lines like these to your requirements files to '
- 'prevent tampering. (If you did not enable --require-hashes '
- 'manually, note that it turns on automatically when any package '
- 'has a hash.)')
-
- def __init__(self, gotten_hash):
- # type: (str) -> None
+ head = (
+ "Hashes are required in --require-hashes mode, but they are "
+ "missing from some requirements. Here is a list of those "
+ "requirements along with the hashes their downloaded archives "
+ "actually had. Add lines like these to your requirements files to "
+ "prevent tampering. (If you did not enable --require-hashes "
+ "manually, note that it turns on automatically when any package "
+ "has a hash.)"
+ )
+
+ def __init__(self, gotten_hash: str) -> None:
"""
:param gotten_hash: The hash of the (possibly malicious) archive we
just downloaded
"""
self.gotten_hash = gotten_hash
- def body(self):
- # type: () -> str
+ def body(self) -> str:
# Dodge circular import.
from pipenv.patched.notpip._internal.utils.hashes import FAVORITE_HASH
@@ -294,13 +571,16 @@ def body(self):
# In the case of URL-based requirements, display the original URL
# seen in the requirements file rather than the package name,
# so the output can be directly copied into the requirements file.
- package = (self.req.original_link if self.req.original_link
- # In case someone feeds something downright stupid
- # to InstallRequirement's constructor.
- else getattr(self.req, 'req', None))
- return ' {} --hash={}:{}'.format(package or 'unknown package',
- FAVORITE_HASH,
- self.gotten_hash)
+ package = (
+ self.req.original_link
+ if self.req.original_link
+ # In case someone feeds something downright stupid
+ # to InstallRequirement's constructor.
+ else getattr(self.req, "req", None)
+ )
+ return " {} --hash={}:{}".format(
+ package or "unknown package", FAVORITE_HASH, self.gotten_hash
+ )
class HashUnpinned(HashError):
@@ -308,8 +588,10 @@ class HashUnpinned(HashError):
version."""
order = 3
- head = ('In --require-hashes mode, all requirements must have their '
- 'versions pinned with ==. These do not:')
+ head = (
+ "In --require-hashes mode, all requirements must have their "
+ "versions pinned with ==. These do not:"
+ )
class HashMismatch(HashError):
@@ -321,14 +603,16 @@ class HashMismatch(HashError):
improve its error message.
"""
- order = 4
- head = ('THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS '
- 'FILE. If you have updated the package versions, please update '
- 'the hashes. Otherwise, examine the package contents carefully; '
- 'someone may have tampered with them.')
- def __init__(self, allowed, gots):
- # type: (Dict[str, List[str]], Dict[str, _Hash]) -> None
+ order = 4
+ head = (
+ "THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS "
+ "FILE. If you have updated the package versions, please update "
+ "the hashes. Otherwise, examine the package contents carefully; "
+ "someone may have tampered with them."
+ )
+
+ def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
"""
:param allowed: A dict of algorithm names pointing to lists of allowed
hex digests
@@ -338,13 +622,10 @@ def __init__(self, allowed, gots):
self.allowed = allowed
self.gots = gots
- def body(self):
- # type: () -> str
- return ' {}:\n{}'.format(self._requirement_name(),
- self._hash_comparison())
+ def body(self) -> str:
+ return " {}:\n{}".format(self._requirement_name(), self._hash_comparison())
- def _hash_comparison(self):
- # type: () -> str
+ def _hash_comparison(self) -> str:
"""
Return a comparison of actual and expected hash values.
@@ -355,20 +636,22 @@ def _hash_comparison(self):
Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef
"""
- def hash_then_or(hash_name):
- # type: (str) -> chain[str]
+
+ def hash_then_or(hash_name: str) -> "chain[str]":
# For now, all the decent hashes have 6-char names, so we can get
# away with hard-coding space literals.
- return chain([hash_name], repeat(' or'))
+ return chain([hash_name], repeat(" or"))
- lines = [] # type: List[str]
+ lines: List[str] = []
for hash_name, expecteds in self.allowed.items():
prefix = hash_then_or(hash_name)
- lines.extend((' Expected {} {}'.format(next(prefix), e))
- for e in expecteds)
- lines.append(' Got {}\n'.format(
- self.gots[hash_name].hexdigest()))
- return '\n'.join(lines)
+ lines.extend(
+ (" Expected {} {}".format(next(prefix), e)) for e in expecteds
+ )
+ lines.append(
+ " Got {}\n".format(self.gots[hash_name].hexdigest())
+ )
+ return "\n".join(lines)
class UnsupportedPythonVersion(InstallationError):
@@ -377,18 +660,20 @@ class UnsupportedPythonVersion(InstallationError):
class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
- """When there are errors while loading a configuration file
- """
-
- def __init__(self, reason="could not be loaded", fname=None, error=None):
- # type: (str, Optional[str], Optional[configparser.Error]) -> None
+ """When there are errors while loading a configuration file"""
+
+ def __init__(
+ self,
+ reason: str = "could not be loaded",
+ fname: Optional[str] = None,
+ error: Optional[configparser.Error] = None,
+ ) -> None:
super().__init__(error)
self.reason = reason
self.fname = fname
self.error = error
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
if self.fname is not None:
message_part = f" in {self.fname}."
else:
diff --git a/pipenv/patched/notpip/_internal/index/collector.py b/pipenv/patched/notpip/_internal/index/collector.py
index 98ac9d2e34..dd5d5751bb 100644
--- a/pipenv/patched/notpip/_internal/index/collector.py
+++ b/pipenv/patched/notpip/_internal/index/collector.py
@@ -5,7 +5,6 @@
import cgi
import collections
import functools
-import html
import itertools
import logging
import os
@@ -13,15 +12,19 @@
import urllib.parse
import urllib.request
import xml.etree.ElementTree
+from html.parser import HTMLParser
from optparse import Values
from typing import (
+ TYPE_CHECKING,
Callable,
+ Dict,
Iterable,
List,
MutableMapping,
NamedTuple,
Optional,
Sequence,
+ Tuple,
Union,
)
@@ -29,7 +32,11 @@
from pipenv.patched.notpip._vendor.requests import Response
from pipenv.patched.notpip._vendor.requests.exceptions import RetryError, SSLError
-from pipenv.patched.notpip._internal.exceptions import NetworkConnectionError
+from pipenv.patched.notpip._internal.exceptions import (
+ BadHTMLDoctypeDeclaration,
+ MissingHTMLDoctypeDeclaration,
+ NetworkConnectionError,
+)
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.models.search_scope import SearchScope
from pipenv.patched.notpip._internal.network.session import PipSession
@@ -40,6 +47,11 @@
from .sources import CandidatesFromPage, LinkSource, build_source
+if TYPE_CHECKING:
+ from typing import Protocol
+else:
+ Protocol = object
+
logger = logging.getLogger(__name__)
HTMLElement = xml.etree.ElementTree.Element
@@ -52,7 +64,7 @@ def _match_vcs_scheme(url: str) -> Optional[str]:
Returns the matched VCS scheme, or None if there's no match.
"""
for scheme in vcs.schemes:
- if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
+ if url.lower().startswith(scheme) and url[len(scheme)] in "+:":
return scheme
return None
@@ -85,7 +97,7 @@ def _ensure_html_response(url: str, session: PipSession) -> None:
`_NotHTML` if the content type is not text/html.
"""
scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
- if scheme not in {'http', 'https'}:
+ if scheme not in {"http", "https"}:
raise _NotHTTP()
resp = session.head(url, allow_redirects=True)
@@ -110,7 +122,7 @@ def _get_html_response(url: str, session: PipSession) -> Response:
if is_archive_file(Link(url).filename):
_ensure_html_response(url, session=session)
- logger.debug('Getting page %s', redact_auth_from_url(url))
+ logger.debug("Getting page %s", redact_auth_from_url(url))
resp = session.get(
url,
@@ -145,12 +157,11 @@ def _get_html_response(url: str, session: PipSession) -> Response:
def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]:
- """Determine if we have any encoding information in our headers.
- """
+ """Determine if we have any encoding information in our headers."""
if headers and "Content-Type" in headers:
content_type, params = cgi.parse_header(headers["Content-Type"])
if "charset" in params:
- return params['charset']
+ return params["charset"]
return None
@@ -165,6 +176,8 @@ def _determine_base_url(document: HTMLElement, page_url: str) -> str:
:param document: An HTML document representation. The current
implementation expects the result of ``html5lib.parse()``.
:param page_url: The URL of the HTML document.
+
+ TODO: Remove when `html5lib` is dropped.
"""
for base in document.findall(".//base"):
href = base.get("href")
@@ -195,7 +208,7 @@ def _clean_file_url_path(part: str) -> str:
# percent-encoded: /
-_reserved_chars_re = re.compile('(@|%2F)', re.IGNORECASE)
+_reserved_chars_re = re.compile("(@|%2F)", re.IGNORECASE)
def _clean_url_path(path: str, is_local_path: bool) -> str:
@@ -212,12 +225,12 @@ def _clean_url_path(path: str, is_local_path: bool) -> str:
parts = _reserved_chars_re.split(path)
cleaned_parts = []
- for to_clean, reserved in pairwise(itertools.chain(parts, [''])):
+ for to_clean, reserved in pairwise(itertools.chain(parts, [""])):
cleaned_parts.append(clean_func(to_clean))
# Normalize %xx escapes (e.g. %2f -> %2F)
cleaned_parts.append(reserved.upper())
- return ''.join(cleaned_parts)
+ return "".join(cleaned_parts)
def _clean_link(url: str) -> str:
@@ -236,24 +249,20 @@ def _clean_link(url: str) -> str:
def _create_link_from_element(
- anchor: HTMLElement,
+ element_attribs: Dict[str, Optional[str]],
page_url: str,
base_url: str,
) -> Optional[Link]:
"""
- Convert an anchor element in a simple repository page to a Link.
+ Convert an anchor element's attributes in a simple repository page to a Link.
"""
- href = anchor.get("href")
+ href = element_attribs.get("href")
if not href:
return None
url = _clean_link(urllib.parse.urljoin(base_url, href))
- pyrequire = anchor.get('data-requires-python')
- pyrequire = html.unescape(pyrequire) if pyrequire else None
-
- yanked_reason = anchor.get('data-yanked')
- if yanked_reason:
- yanked_reason = html.unescape(yanked_reason)
+ pyrequire = element_attribs.get("data-requires-python")
+ yanked_reason = element_attribs.get("data-yanked")
link = Link(
url,
@@ -271,16 +280,20 @@ def __init__(self, page: "HTMLPage") -> None:
self.page = page
def __eq__(self, other: object) -> bool:
- return (isinstance(other, type(self)) and
- self.page.url == other.page.url)
+ return isinstance(other, type(self)) and self.page.url == other.page.url
def __hash__(self) -> int:
return hash(self.page.url)
-def with_cached_html_pages(
- fn: Callable[["HTMLPage"], Iterable[Link]],
-) -> Callable[["HTMLPage"], List[Link]]:
+class ParseLinks(Protocol):
+ def __call__(
+ self, page: "HTMLPage", use_deprecated_html5lib: bool
+ ) -> Iterable[Link]:
+ ...
+
+
+def with_cached_html_pages(fn: ParseLinks) -> ParseLinks:
"""
Given a function that parses an Iterable[Link] from an HTMLPage, cache the
function's result (keyed by CacheablePageContent), unless the HTMLPage
@@ -288,22 +301,25 @@ def with_cached_html_pages(
"""
@functools.lru_cache(maxsize=None)
- def wrapper(cacheable_page: CacheablePageContent) -> List[Link]:
- return list(fn(cacheable_page.page))
+ def wrapper(
+ cacheable_page: CacheablePageContent, use_deprecated_html5lib: bool
+ ) -> List[Link]:
+ return list(fn(cacheable_page.page, use_deprecated_html5lib))
@functools.wraps(fn)
- def wrapper_wrapper(page: "HTMLPage") -> List[Link]:
+ def wrapper_wrapper(page: "HTMLPage", use_deprecated_html5lib: bool) -> List[Link]:
if page.cache_link_parsing:
- return wrapper(CacheablePageContent(page))
- return list(fn(page))
+ return wrapper(CacheablePageContent(page), use_deprecated_html5lib)
+ return list(fn(page, use_deprecated_html5lib))
return wrapper_wrapper
-@with_cached_html_pages
-def parse_links(page: "HTMLPage") -> Iterable[Link]:
+def _parse_links_html5lib(page: "HTMLPage") -> Iterable[Link]:
"""
Parse an HTML document, and yield its anchor elements as Link objects.
+
+ TODO: Remove when `html5lib` is dropped.
"""
document = html5lib.parse(
page.content,
@@ -314,6 +330,33 @@ def parse_links(page: "HTMLPage") -> Iterable[Link]:
url = page.url
base_url = _determine_base_url(document, url)
for anchor in document.findall(".//a"):
+ link = _create_link_from_element(
+ anchor.attrib,
+ page_url=url,
+ base_url=base_url,
+ )
+ if link is None:
+ continue
+ yield link
+
+
+@with_cached_html_pages
+def parse_links(page: "HTMLPage", use_deprecated_html5lib: bool) -> Iterable[Link]:
+ """
+ Parse an HTML document, and yield its anchor elements as Link objects.
+ """
+
+ if use_deprecated_html5lib:
+ yield from _parse_links_html5lib(page)
+ return
+
+ parser = HTMLLinkParser(page.url)
+ encoding = page.encoding or "utf-8"
+ parser.feed(page.content.decode(encoding))
+
+ url = page.url
+ base_url = parser.base_url or url
+ for anchor in parser.anchors:
link = _create_link_from_element(
anchor,
page_url=url,
@@ -350,10 +393,59 @@ def __str__(self) -> str:
return redact_auth_from_url(self.url)
+class HTMLLinkParser(HTMLParser):
+ """
+ HTMLParser that keeps the first base HREF and a list of all anchor
+ elements' attributes.
+ """
+
+ def __init__(self, url: str) -> None:
+ super().__init__(convert_charrefs=True)
+ self._dealt_with_doctype_issues = False
+
+ self.url: str = url
+ self.base_url: Optional[str] = None
+ self.anchors: List[Dict[str, Optional[str]]] = []
+
+ def handle_decl(self, decl: str) -> None:
+ self._dealt_with_doctype_issues = True
+ match = re.match(
+ r"""doctype\s+html\s*(?:SYSTEM\s+(["'])about:legacy-compat\1)?\s*$""",
+ decl,
+ re.IGNORECASE,
+ )
+ if match is None:
+ logger.warning(
+ "[present-diagnostic] %s",
+ BadHTMLDoctypeDeclaration(url=self.url),
+ )
+
+ def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
+ if not self._dealt_with_doctype_issues:
+ logger.warning(
+ "[present-diagnostic] %s",
+ MissingHTMLDoctypeDeclaration(url=self.url),
+ )
+ self._dealt_with_doctype_issues = True
+
+ if tag == "base" and self.base_url is None:
+ href = self.get_href(attrs)
+ if href is not None:
+ self.base_url = href
+ elif tag == "a":
+ self.anchors.append(dict(attrs))
+
+ def get_href(self, attrs: List[Tuple[str, Optional[str]]]) -> Optional[str]:
+ for name, value in attrs:
+ if name == "href":
+ return value
+ return None
+
+
def _handle_get_page_fail(
link: Link,
reason: Union[str, Exception],
- meth: Optional[Callable[..., None]] = None
+ meth: Optional[Callable[..., None]] = None,
) -> None:
if meth is None:
meth = logger.debug
@@ -366,7 +458,8 @@ def _make_html_page(response: Response, cache_link_parsing: bool = True) -> HTML
response.content,
encoding=encoding,
url=response.url,
- cache_link_parsing=cache_link_parsing)
+ cache_link_parsing=cache_link_parsing,
+ )
def _get_html_page(
@@ -377,37 +470,43 @@ def _get_html_page(
"_get_html_page() missing 1 required keyword argument: 'session'"
)
- url = link.url.split('#', 1)[0]
+ url = link.url.split("#", 1)[0]
# Check for VCS schemes that do not support lookup as web pages.
vcs_scheme = _match_vcs_scheme(url)
if vcs_scheme:
- logger.warning('Cannot look at %s URL %s because it does not support '
- 'lookup as web pages.', vcs_scheme, link)
+ logger.warning(
+ "Cannot look at %s URL %s because it does not support lookup as web pages.",
+ vcs_scheme,
+ link,
+ )
return None
# Tack index.html onto file:// URLs that point to directories
scheme, _, path, _, _, _ = urllib.parse.urlparse(url)
- if (scheme == 'file' and os.path.isdir(urllib.request.url2pathname(path))):
+ if scheme == "file" and os.path.isdir(urllib.request.url2pathname(path)):
# add trailing slash if not present so urljoin doesn't trim
# final segment
- if not url.endswith('/'):
- url += '/'
- url = urllib.parse.urljoin(url, 'index.html')
- logger.debug(' file: URL is directory, getting %s', url)
+ if not url.endswith("/"):
+ url += "/"
+ url = urllib.parse.urljoin(url, "index.html")
+ logger.debug(" file: URL is directory, getting %s", url)
try:
resp = _get_html_response(url, session=session)
except _NotHTTP:
logger.warning(
- 'Skipping page %s because it looks like an archive, and cannot '
- 'be checked by a HTTP HEAD request.', link,
+ "Skipping page %s because it looks like an archive, and cannot "
+ "be checked by a HTTP HEAD request.",
+ link,
)
except _NotHTML as exc:
logger.warning(
- 'Skipping page %s because the %s request got Content-Type: %s.'
- 'The only supported Content-Type is text/html',
- link, exc.request_desc, exc.content_type,
+ "Skipping page %s because the %s request got Content-Type: %s."
+ "The only supported Content-Type is text/html",
+ link,
+ exc.request_desc,
+ exc.content_type,
)
except NetworkConnectionError as exc:
_handle_get_page_fail(link, exc)
@@ -422,8 +521,7 @@ def _get_html_page(
except requests.Timeout:
_handle_get_page_fail(link, "timed out")
else:
- return _make_html_page(resp,
- cache_link_parsing=link.cache_link_parsing)
+ return _make_html_page(resp, cache_link_parsing=link.cache_link_parsing)
return None
@@ -451,9 +549,10 @@ def __init__(
@classmethod
def create(
- cls, session: PipSession,
+ cls,
+ session: PipSession,
options: Values,
- suppress_no_index: bool = False
+ suppress_no_index: bool = False,
) -> "LinkCollector":
"""
:param session: The Session to use to make requests.
@@ -463,8 +562,8 @@ def create(
index_urls = [options.index_url] + options.extra_index_urls
if options.no_index and not suppress_no_index:
logger.debug(
- 'Ignoring indexes: %s',
- ','.join(redact_auth_from_url(url) for url in index_urls),
+ "Ignoring indexes: %s",
+ ",".join(redact_auth_from_url(url) for url in index_urls),
)
index_urls = []
@@ -472,10 +571,12 @@ def create(
find_links = options.find_links or []
search_scope = SearchScope.create(
- find_links=find_links, index_urls=index_urls,
+ find_links=find_links,
+ index_urls=index_urls,
)
link_collector = LinkCollector(
- session=session, search_scope=search_scope,
+ session=session,
+ search_scope=search_scope,
)
return link_collector
diff --git a/pipenv/patched/notpip/_internal/index/package_finder.py b/pipenv/patched/notpip/_internal/index/package_finder.py
index ab48628c87..91970538be 100644
--- a/pipenv/patched/notpip/_internal/index/package_finder.py
+++ b/pipenv/patched/notpip/_internal/index/package_finder.py
@@ -37,17 +37,14 @@
from pipenv.patched.notpip._internal.utils.misc import build_netloc
from pipenv.patched.notpip._internal.utils.packaging import check_requires_python
from pipenv.patched.notpip._internal.utils.unpacking import SUPPORTED_EXTENSIONS
-from pipenv.patched.notpip._internal.utils.urls import url_to_path
-__all__ = ['FormatControl', 'BestCandidateResult', 'PackageFinder']
+__all__ = ["FormatControl", "BestCandidateResult", "PackageFinder"]
logger = getLogger(__name__)
BuildTag = Union[Tuple[()], Tuple[int, str]]
-CandidateSortingKey = (
- Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag]
-)
+CandidateSortingKey = Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag]
def _check_link_requires_python(
@@ -66,27 +63,32 @@ def _check_link_requires_python(
"""
try:
is_compatible = check_requires_python(
- link.requires_python, version_info=version_info,
+ link.requires_python,
+ version_info=version_info,
)
except specifiers.InvalidSpecifier:
logger.debug(
"Ignoring invalid Requires-Python (%r) for link: %s",
- link.requires_python, link,
+ link.requires_python,
+ link,
)
else:
if not is_compatible:
- version = '.'.join(map(str, version_info))
+ version = ".".join(map(str, version_info))
if not ignore_requires_python:
logger.verbose(
- 'Link requires a different Python (%s not in: %r): %s',
- version, link.requires_python, link,
+ "Link requires a different Python (%s not in: %r): %s",
+ version,
+ link.requires_python,
+ link,
)
return False
logger.debug(
- 'Ignoring failed Requires-Python check (%s not in: %r) '
- 'for link: %s',
- version, link.requires_python, link,
+ "Ignoring failed Requires-Python check (%s not in: %r) for link: %s",
+ version,
+ link.requires_python,
+ link,
)
return True
@@ -98,7 +100,7 @@ class LinkEvaluator:
Responsible for evaluating links for a particular project.
"""
- _py_version_re = re.compile(r'-py([123]\.?[0-9]?)$')
+ _py_version_re = re.compile(r"-py([123]\.?[0-9]?)$")
# Don't include an allow_yanked default value to make sure each call
# site considers whether yanked releases are allowed. This also causes
@@ -112,7 +114,6 @@ def __init__(
target_python: TargetPython,
allow_yanked: bool,
ignore_requires_python: Optional[bool] = None,
- ignore_compatibility: Optional[bool] = None,
) -> None:
"""
:param project_name: The user supplied package name.
@@ -130,20 +131,15 @@ def __init__(
:param ignore_requires_python: Whether to ignore incompatible
PEP 503 "data-requires-python" values in HTML links. Defaults
to False.
- :param ignore_compatibility: Whether to ignore
- compatibility of python versions and allow all versions of packages.
"""
if ignore_requires_python is None:
ignore_requires_python = False
- if ignore_compatibility is None:
- ignore_compatibility = True
self._allow_yanked = allow_yanked
self._canonical_name = canonical_name
self._ignore_requires_python = ignore_requires_python
self._formats = formats
self._target_python = target_python
- self._ignore_compatibility = ignore_compatibility
self.project_name = project_name
@@ -158,8 +154,8 @@ def evaluate_link(self, link: Link) -> Tuple[bool, Optional[str]]:
"""
version = None
if link.is_yanked and not self._allow_yanked:
- reason = link.yanked_reason or ''
- return (False, f'yanked for reason: {reason}')
+ reason = link.yanked_reason or ""
+ return (False, f"yanked for reason: {reason}")
if link.egg_fragment:
egg_info = link.egg_fragment
@@ -167,34 +163,32 @@ def evaluate_link(self, link: Link) -> Tuple[bool, Optional[str]]:
else:
egg_info, ext = link.splitext()
if not ext:
- return (False, 'not a file')
+ return (False, "not a file")
if ext not in SUPPORTED_EXTENSIONS:
- return (False, f'unsupported archive format: {ext}')
- if "binary" not in self._formats and ext == WHEEL_EXTENSION and not self._ignore_compatibility:
- reason = 'No binaries permitted for {}'.format(
- self.project_name)
+ return (False, f"unsupported archive format: {ext}")
+ if "binary" not in self._formats and ext == WHEEL_EXTENSION:
+ reason = "No binaries permitted for {}".format(self.project_name)
return (False, reason)
- if "macosx10" in link.path and ext == '.zip' and not self._ignore_compatibility:
- return (False, 'macosx10 one')
+ if "macosx10" in link.path and ext == ".zip":
+ return (False, "macosx10 one")
if ext == WHEEL_EXTENSION:
try:
wheel = Wheel(link.filename)
except InvalidWheelFilename:
- return (False, 'invalid wheel filename')
+ return (False, "invalid wheel filename")
if canonicalize_name(wheel.name) != self._canonical_name:
- reason = 'wrong project name (not {})'.format(
- self.project_name)
+ reason = "wrong project name (not {})".format(self.project_name)
return (False, reason)
supported_tags = self._target_python.get_tags()
- if not wheel.supported(supported_tags) and not self._ignore_compatibility:
+ if not wheel.supported(supported_tags):
# Include the wheel's tags in the reason string to
# simplify troubleshooting compatibility issues.
file_tags = wheel.get_formatted_file_tags()
reason = (
"none of the wheel's tags ({}) are compatible "
"(run pip debug --verbose to show compatible tags)".format(
- ', '.join(file_tags)
+ ", ".join(file_tags)
)
)
return (False, reason)
@@ -203,34 +197,36 @@ def evaluate_link(self, link: Link) -> Tuple[bool, Optional[str]]:
# This should be up by the self.ok_binary check, but see issue 2700.
if "source" not in self._formats and ext != WHEEL_EXTENSION:
- reason = f'No sources permitted for {self.project_name}'
+ reason = f"No sources permitted for {self.project_name}"
return (False, reason)
if not version:
version = _extract_version_from_fragment(
- egg_info, self._canonical_name,
+ egg_info,
+ self._canonical_name,
)
if not version:
- reason = f'Missing project version for {self.project_name}'
+ reason = f"Missing project version for {self.project_name}"
return (False, reason)
match = self._py_version_re.search(version)
if match:
- version = version[:match.start()]
+ version = version[: match.start()]
py_version = match.group(1)
if py_version != self._target_python.py_version:
- return (False, 'Python version is incorrect')
+ return (False, "Python version is incorrect")
supports_python = _check_link_requires_python(
- link, version_info=self._target_python.py_version_info,
+ link,
+ version_info=self._target_python.py_version_info,
ignore_requires_python=self._ignore_requires_python,
)
- if not supports_python and not self._ignore_compatibility:
+ if not supports_python:
# Return None for the reason text to suppress calling
# _log_skipped_link().
return (False, None)
- logger.debug('Found link %s, version: %s', link, version)
+ logger.debug("Found link %s, version: %s", link, version)
return (True, version)
@@ -257,8 +253,8 @@ def filter_unallowed_hashes(
"""
if not hashes:
logger.debug(
- 'Given no hashes to check %s links for project %r: '
- 'discarding no candidates',
+ "Given no hashes to check %s links for project %r: "
+ "discarding no candidates",
len(candidates),
project_name,
)
@@ -288,22 +284,22 @@ def filter_unallowed_hashes(
filtered = list(candidates)
if len(filtered) == len(candidates):
- discard_message = 'discarding no candidates'
+ discard_message = "discarding no candidates"
else:
- discard_message = 'discarding {} non-matches:\n {}'.format(
+ discard_message = "discarding {} non-matches:\n {}".format(
len(non_matches),
- '\n '.join(str(candidate.link) for candidate in non_matches)
+ "\n ".join(str(candidate.link) for candidate in non_matches),
)
logger.debug(
- 'Checked %s links for project %r against %s hashes '
- '(%s matches, %s no digest): %s',
+ "Checked %s links for project %r against %s hashes "
+ "(%s matches, %s no digest): %s",
len(candidates),
project_name,
hashes.digest_count,
match_count,
len(matches_or_no_digest) - match_count,
- discard_message
+ discard_message,
)
return filtered
@@ -360,13 +356,11 @@ def __init__(
self.best_candidate = best_candidate
def iter_all(self) -> Iterable[InstallationCandidate]:
- """Iterate through all candidates.
- """
+ """Iterate through all candidates."""
return iter(self._candidates)
def iter_applicable(self) -> Iterable[InstallationCandidate]:
- """Iterate through the applicable candidates.
- """
+ """Iterate through the applicable candidates."""
return iter(self._applicable_candidates)
@@ -450,7 +444,8 @@ def get_applicable_candidates(
allow_prereleases = self._allow_all_prereleases or None
specifier = self._specifier
versions = {
- str(v) for v in specifier.filter(
+ str(v)
+ for v in specifier.filter(
# We turn the version object into a str here because otherwise
# when we're debundled but setuptools isn't, Python will see
# packaging.version.Version and
@@ -464,9 +459,7 @@ def get_applicable_candidates(
}
# Again, converting version to str to deal with debundling.
- applicable_candidates = [
- c for c in candidates if str(c.version) in versions
- ]
+ applicable_candidates = [c for c in candidates if str(c.version) in versions]
filtered_applicable_candidates = filter_unallowed_hashes(
candidates=applicable_candidates,
@@ -476,10 +469,7 @@ def get_applicable_candidates(
return sorted(filtered_applicable_candidates, key=self._sort_key)
- def _sort_key(
- self, candidate: InstallationCandidate,
- ignore_compatibility: bool = True
- ) -> CandidateSortingKey:
+ def _sort_key(self, candidate: InstallationCandidate) -> CandidateSortingKey:
"""
Function to pass as the `key` argument to a call to sorted() to sort
InstallationCandidates by preference.
@@ -518,20 +508,20 @@ def _sort_key(
# can raise InvalidWheelFilename
wheel = Wheel(link.filename)
try:
- pri = -(wheel.find_most_preferred_tag(
- valid_tags, self._wheel_tag_preferences
- ))
- except ValueError:
- if not ignore_compatibility:
- raise UnsupportedWheel(
- "{} is not a supported wheel for this platform. It "
- "can't be sorted.".format(wheel.filename)
+ pri = -(
+ wheel.find_most_preferred_tag(
+ valid_tags, self._wheel_tag_preferences
)
- pri = -(support_num)
+ )
+ except ValueError:
+ raise UnsupportedWheel(
+ "{} is not a supported wheel for this platform. It "
+ "can't be sorted.".format(wheel.filename)
+ )
if self._prefer_binary:
binary_preference = 1
if wheel.build_tag is not None:
- match = re.match(r'^(\d+)(.*)$', wheel.build_tag)
+ match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
build_tag_groups = match.groups()
build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
else: # sdist
@@ -539,8 +529,12 @@ def _sort_key(
has_allowed_hash = int(link.is_hash_allowed(self._hashes))
yank_value = -1 * int(link.is_yanked) # -1 for yanked.
return (
- has_allowed_hash, yank_value, binary_preference, candidate.version,
- pri, build_tag,
+ has_allowed_hash,
+ yank_value,
+ binary_preference,
+ candidate.version,
+ pri,
+ build_tag,
)
def sort_best_candidate(
@@ -586,10 +580,10 @@ def __init__(
link_collector: LinkCollector,
target_python: TargetPython,
allow_yanked: bool,
+ use_deprecated_html5lib: bool,
format_control: Optional[FormatControl] = None,
candidate_prefs: Optional[CandidatePreferences] = None,
ignore_requires_python: Optional[bool] = None,
- ignore_compatibility: Optional[bool] = False
) -> None:
"""
This constructor is primarily meant to be used by the create() class
@@ -611,7 +605,7 @@ def __init__(
self._ignore_requires_python = ignore_requires_python
self._link_collector = link_collector
self._target_python = target_python
- self._ignore_compatibility = ignore_compatibility
+ self._use_deprecated_html5lib = use_deprecated_html5lib
self.format_control = format_control
@@ -628,6 +622,8 @@ def create(
link_collector: LinkCollector,
selection_prefs: SelectionPreferences,
target_python: Optional[TargetPython] = None,
+ *,
+ use_deprecated_html5lib: bool,
) -> "PackageFinder":
"""Create a PackageFinder.
@@ -652,6 +648,7 @@ def create(
allow_yanked=selection_prefs.allow_yanked,
format_control=selection_prefs.format_control,
ignore_requires_python=selection_prefs.ignore_requires_python,
+ use_deprecated_html5lib=use_deprecated_html5lib,
)
@property
@@ -704,7 +701,6 @@ def make_link_evaluator(self, project_name: str) -> LinkEvaluator:
target_python=self._target_python,
allow_yanked=self._allow_yanked,
ignore_requires_python=self._ignore_requires_python,
- ignore_compatibility=self._ignore_compatibility
)
def _sort_links(self, links: Iterable[Link]) -> List[Link]:
@@ -727,7 +723,7 @@ def _log_skipped_link(self, link: Link, reason: str) -> None:
if link not in self._logged_links:
# Put the link at the end so the reason is more visible and because
# the link string is usually very long.
- logger.debug('Skipping link: %s: %s', reason, link)
+ logger.debug("Skipping link: %s: %s", reason, link)
self._logged_links.add(link)
def get_install_candidate(
@@ -767,13 +763,14 @@ def process_project_url(
self, project_url: Link, link_evaluator: LinkEvaluator
) -> List[InstallationCandidate]:
logger.debug(
- 'Fetching project page and analyzing links: %s', project_url,
+ "Fetching project page and analyzing links: %s",
+ project_url,
)
html_page = self._link_collector.fetch_page(project_url)
if html_page is None:
return []
- page_links = list(parse_links(html_page))
+ page_links = list(parse_links(html_page, self._use_deprecated_html5lib))
with indent_log():
package_links = self.evaluate_links(
@@ -823,7 +820,14 @@ def find_all_candidates(self, project_name: str) -> List[InstallationCandidate]:
)
if logger.isEnabledFor(logging.DEBUG) and file_candidates:
- paths = [url_to_path(c.link.url) for c in file_candidates]
+ paths = []
+ for candidate in file_candidates:
+ assert candidate.link.url # we need to have a URL
+ try:
+ paths.append(candidate.link.file_path)
+ except Exception:
+ paths.append(candidate.link.url) # it's not a local file
+
logger.debug("Local files found: %s", ", ".join(paths))
# This is an intentional priority ordering
@@ -835,8 +839,7 @@ def make_candidate_evaluator(
specifier: Optional[specifiers.BaseSpecifier] = None,
hashes: Optional[Hashes] = None,
) -> CandidateEvaluator:
- """Create a CandidateEvaluator object to use.
- """
+ """Create a CandidateEvaluator object to use."""
candidate_prefs = self._candidate_prefs
return CandidateEvaluator.create(
project_name=project_name,
@@ -881,54 +884,60 @@ def find_requirement(
"""
hashes = req.hashes(trust_internet=False)
best_candidate_result = self.find_best_candidate(
- req.name, specifier=req.specifier, hashes=hashes,
+ req.name,
+ specifier=req.specifier,
+ hashes=hashes,
)
best_candidate = best_candidate_result.best_candidate
installed_version: Optional[_BaseVersion] = None
if req.satisfied_by is not None:
- installed_version = parse_version(req.satisfied_by.version)
+ installed_version = req.satisfied_by.version
def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str:
# This repeated parse_version and str() conversion is needed to
# handle different vendoring sources from pip and pkg_resources.
# If we stop using the pkg_resources provided specifier and start
# using our own, we can drop the cast to str().
- return ", ".join(sorted(
- {str(c.version) for c in cand_iter},
- key=parse_version,
- )) or "none"
+ return (
+ ", ".join(
+ sorted(
+ {str(c.version) for c in cand_iter},
+ key=parse_version,
+ )
+ )
+ or "none"
+ )
if installed_version is None and best_candidate is None:
logger.critical(
- 'Could not find a version that satisfies the requirement %s '
- '(from versions: %s)',
+ "Could not find a version that satisfies the requirement %s "
+ "(from versions: %s)",
req,
_format_versions(best_candidate_result.iter_all()),
)
raise DistributionNotFound(
- 'No matching distribution found for {}'.format(
- req)
+ "No matching distribution found for {}".format(req)
)
best_installed = False
if installed_version and (
- best_candidate is None or
- best_candidate.version <= installed_version):
+ best_candidate is None or best_candidate.version <= installed_version
+ ):
best_installed = True
if not upgrade and installed_version is not None:
if best_installed:
logger.debug(
- 'Existing installed version (%s) is most up-to-date and '
- 'satisfies requirement',
+ "Existing installed version (%s) is most up-to-date and "
+ "satisfies requirement",
installed_version,
)
else:
logger.debug(
- 'Existing installed version (%s) satisfies requirement '
- '(most up-to-date version is %s)',
+ "Existing installed version (%s) satisfies requirement "
+ "(most up-to-date version is %s)",
installed_version,
best_candidate.version,
)
@@ -937,15 +946,14 @@ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str:
if best_installed:
# We have an existing version, and its the best version
logger.debug(
- 'Installed version (%s) is most up-to-date (past versions: '
- '%s)',
+ "Installed version (%s) is most up-to-date (past versions: %s)",
installed_version,
_format_versions(best_candidate_result.iter_applicable()),
)
raise BestVersionAlreadyInstalled
logger.debug(
- 'Using version %s (newest of versions: %s)',
+ "Using version %s (newest of versions: %s)",
best_candidate.version,
_format_versions(best_candidate_result.iter_applicable()),
)
diff --git a/pipenv/patched/notpip/_internal/locations/__init__.py b/pipenv/patched/notpip/_internal/locations/__init__.py
index 83c6920412..093c325026 100644
--- a/pipenv/patched/notpip/_internal/locations/__init__.py
+++ b/pipenv/patched/notpip/_internal/locations/__init__.py
@@ -4,11 +4,12 @@
import pathlib
import sys
import sysconfig
-from typing import Dict, Iterator, List, Optional, Tuple
+from typing import Any, Dict, Iterator, List, Optional, Tuple
from pipenv.patched.notpip._internal.models.scheme import SCHEME_KEYS, Scheme
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
from pipenv.patched.notpip._internal.utils.deprecation import deprecated
+from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv
from . import _distutils, _sysconfig
from .base import (
@@ -37,14 +38,54 @@
logger = logging.getLogger(__name__)
-if os.environ.get("_PIP_LOCATIONS_NO_WARN_ON_MISMATCH"):
- _MISMATCH_LEVEL = logging.DEBUG
-else:
+
+_PLATLIBDIR: str = getattr(sys, "platlibdir", "lib")
+
+_USE_SYSCONFIG_DEFAULT = sys.version_info >= (3, 10)
+
+
+def _should_use_sysconfig() -> bool:
+ """This function determines the value of _USE_SYSCONFIG.
+
+ By default, pip uses sysconfig on Python 3.10+.
+ But Python distributors can override this decision by setting:
+ sysconfig._PIP_USE_SYSCONFIG = True / False
+ Rationale in https://github.com/pypa/pip/issues/10647
+
+ This is a function for testability, but should be constant during any one
+ run.
+ """
+ return bool(getattr(sysconfig, "_PIP_USE_SYSCONFIG", _USE_SYSCONFIG_DEFAULT))
+
+
+_USE_SYSCONFIG = _should_use_sysconfig()
+
+# Be noisy about incompatibilities if this platforms "should" be using
+# sysconfig, but is explicitly opting out and using distutils instead.
+if _USE_SYSCONFIG_DEFAULT and not _USE_SYSCONFIG:
_MISMATCH_LEVEL = logging.WARNING
+else:
+ _MISMATCH_LEVEL = logging.DEBUG
+
+
+def _looks_like_bpo_44860() -> bool:
+ """The resolution to bpo-44860 will change this incorrect platlib.
+
+ See .
+ """
+ from distutils.command.install import INSTALL_SCHEMES # type: ignore
+
+ try:
+ unix_user_platlib = INSTALL_SCHEMES["unix_user"]["platlib"]
+ except KeyError:
+ return False
+ return unix_user_platlib == "$usersite"
def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
platlib = scheme["platlib"]
+ if "/$platlibdir/" in platlib:
+ platlib = platlib.replace("/$platlibdir/", f"/{_PLATLIBDIR}/")
if "/lib64/" not in platlib:
return False
unpatched = platlib.replace("/lib64/", "/lib/")
@@ -52,7 +93,7 @@ def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
@functools.lru_cache(maxsize=None)
-def _looks_like_red_hat_patched() -> bool:
+def _looks_like_red_hat_lib() -> bool:
"""Red Hat patches platlib in unix_prefix and unix_home, but not purelib.
This is the only way I can see to tell a Red Hat-patched Python.
@@ -67,13 +108,67 @@ def _looks_like_red_hat_patched() -> bool:
@functools.lru_cache(maxsize=None)
-def _looks_like_debian_patched() -> bool:
+def _looks_like_debian_scheme() -> bool:
"""Debian adds two additional schemes."""
from distutils.command.install import INSTALL_SCHEMES # type: ignore
return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES
+@functools.lru_cache(maxsize=None)
+def _looks_like_red_hat_scheme() -> bool:
+ """Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``.
+
+ Red Hat's ``00251-change-user-install-location.patch`` changes the install
+ command's ``prefix`` and ``exec_prefix`` to append ``"/local"``. This is
+ (fortunately?) done quite unconditionally, so we create a default command
+ object without any configuration to detect this.
+ """
+ from distutils.command.install import install
+ from distutils.dist import Distribution
+
+ cmd: Any = install(Distribution())
+ cmd.finalize_options()
+ return (
+ cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local"
+ and cmd.prefix == f"{os.path.normpath(sys.prefix)}/local"
+ )
+
+
+@functools.lru_cache(maxsize=None)
+def _looks_like_slackware_scheme() -> bool:
+ """Slackware patches sysconfig but fails to patch distutils and site.
+
+ Slackware changes sysconfig's user scheme to use ``"lib64"`` for the lib
+ path, but does not do the same to the site module.
+ """
+ if user_site is None: # User-site not available.
+ return False
+ try:
+ paths = sysconfig.get_paths(scheme="posix_user", expand=False)
+ except KeyError: # User-site not available.
+ return False
+ return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site
+
+
+@functools.lru_cache(maxsize=None)
+def _looks_like_msys2_mingw_scheme() -> bool:
+ """MSYS2 patches distutils and sysconfig to use a UNIX-like scheme.
+
+ However, MSYS2 incorrectly patches sysconfig ``nt`` scheme. The fix is
+ likely going to be included in their 3.10 release, so we ignore the warning.
+ See msys2/MINGW-packages#9319.
+
+ MSYS2 MINGW's patch uses lowercase ``"lib"`` instead of the usual uppercase,
+ and is missing the final ``"site-packages"``.
+ """
+ paths = sysconfig.get_paths("nt", expand=False)
+ return all(
+ "Lib" not in p and "lib" in p and not p.endswith("site-packages")
+ for p in (paths[key] for key in ("platlib", "purelib"))
+ )
+
+
def _fix_abiflags(parts: Tuple[str]) -> Iterator[str]:
ldversion = sysconfig.get_config_var("LDVERSION")
abiflags: str = getattr(sys, "abiflags", None)
@@ -90,15 +185,6 @@ def _fix_abiflags(parts: Tuple[str]) -> Iterator[str]:
yield part
-def _default_base(*, user: bool) -> str:
- if user:
- base = sysconfig.get_config_var("userbase")
- else:
- base = sysconfig.get_config_var("base")
- assert base is not None
- return base
-
-
@functools.lru_cache(maxsize=None)
def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None:
issue_url = "https://github.com/pypa/pip/issues/10151"
@@ -144,7 +230,7 @@ def get_scheme(
isolated: bool = False,
prefix: Optional[str] = None,
) -> Scheme:
- old = _distutils.get_scheme(
+ new = _sysconfig.get_scheme(
dist_name,
user=user,
home=home,
@@ -152,7 +238,10 @@ def get_scheme(
isolated=isolated,
prefix=prefix,
)
- new = _sysconfig.get_scheme(
+ if _USE_SYSCONFIG:
+ return new
+
+ old = _distutils.get_scheme(
dist_name,
user=user,
home=home,
@@ -161,11 +250,9 @@ def get_scheme(
prefix=prefix,
)
- base = prefix or home or _default_base(user=user)
warning_contexts = []
for k in SCHEME_KEYS:
- # Extra join because distutils can return relative paths.
- old_v = pathlib.Path(base, getattr(old, k))
+ old_v = pathlib.Path(getattr(old, k))
new_v = pathlib.Path(getattr(new, k))
if old_v == new_v:
@@ -201,19 +288,45 @@ def get_scheme(
# On Red Hat and derived Linux distributions, distutils is patched to
# use "lib64" instead of "lib" for platlib.
- if k == "platlib" and _looks_like_red_hat_patched():
+ if k == "platlib" and _looks_like_red_hat_lib():
+ continue
+
+ # On Python 3.9+, sysconfig's posix_user scheme sets platlib against
+ # sys.platlibdir, but distutils's unix_user incorrectly coninutes
+ # using the same $usersite for both platlib and purelib. This creates a
+ # mismatch when sys.platlibdir is not "lib".
+ skip_bpo_44860 = (
+ user
+ and k == "platlib"
+ and not WINDOWS
+ and sys.version_info >= (3, 9)
+ and _PLATLIBDIR != "lib"
+ and _looks_like_bpo_44860()
+ )
+ if skip_bpo_44860:
+ continue
+
+ # Slackware incorrectly patches posix_user to use lib64 instead of lib,
+ # but not usersite to match the location.
+ skip_slackware_user_scheme = (
+ user
+ and k in ("platlib", "purelib")
+ and not WINDOWS
+ and _looks_like_slackware_scheme()
+ )
+ if skip_slackware_user_scheme:
continue
# Both Debian and Red Hat patch Python to place the system site under
# /usr/local instead of /usr. Debian also places lib in dist-packages
# instead of site-packages, but the /usr/local check should cover it.
skip_linux_system_special_case = (
- not (user or home or prefix)
+ not (user or home or prefix or running_under_virtualenv())
and old_v.parts[1:3] == ("usr", "local")
and len(new_v.parts) > 1
and new_v.parts[1] == "usr"
and (len(new_v.parts) < 3 or new_v.parts[2] != "local")
- and (_looks_like_red_hat_patched() or _looks_like_debian_patched())
+ and (_looks_like_red_hat_scheme() or _looks_like_debian_scheme())
)
if skip_linux_system_special_case:
continue
@@ -229,6 +342,26 @@ def get_scheme(
if skip_sysconfig_abiflag_bug:
continue
+ # MSYS2 MINGW's sysconfig patch does not include the "site-packages"
+ # part of the path. This is incorrect and will be fixed in MSYS.
+ skip_msys2_mingw_bug = (
+ WINDOWS and k in ("platlib", "purelib") and _looks_like_msys2_mingw_scheme()
+ )
+ if skip_msys2_mingw_bug:
+ continue
+
+ # CPython's POSIX install script invokes pip (via ensurepip) against the
+ # interpreter located in the source tree, not the install site. This
+ # triggers special logic in sysconfig that's not present in distutils.
+ # https://github.com/python/cpython/blob/8c21941ddaf/Lib/sysconfig.py#L178-L194
+ skip_cpython_build = (
+ sysconfig.is_python_build(check_home=True)
+ and not WINDOWS
+ and k in ("headers", "include", "platinclude")
+ )
+ if skip_cpython_build:
+ continue
+
warning_contexts.append((old_v, new_v, f"scheme.{k}"))
if not warning_contexts:
@@ -248,10 +381,12 @@ def get_scheme(
)
if any(default_old[k] != getattr(old, k) for k in SCHEME_KEYS):
deprecated(
- "Configuring installation scheme with distutils config files "
- "is deprecated and will no longer work in the near future. If you "
- "are using a Homebrew or Linuxbrew Python, please see discussion "
- "at https://github.com/Homebrew/homebrew-core/issues/76621",
+ reason=(
+ "Configuring installation scheme with distutils config files "
+ "is deprecated and will no longer work in the near future. If you "
+ "are using a Homebrew or Linuxbrew Python, please see discussion "
+ "at https://github.com/Homebrew/homebrew-core/issues/76621"
+ ),
replacement=None,
gone_in=None,
)
@@ -266,8 +401,11 @@ def get_scheme(
def get_bin_prefix() -> str:
- old = _distutils.get_bin_prefix()
new = _sysconfig.get_bin_prefix()
+ if _USE_SYSCONFIG:
+ return new
+
+ old = _distutils.get_bin_prefix()
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="bin_prefix"):
_log_context()
return old
@@ -277,10 +415,32 @@ def get_bin_user() -> str:
return _sysconfig.get_scheme("", user=True).scripts
+def _looks_like_deb_system_dist_packages(value: str) -> bool:
+ """Check if the value is Debian's APT-controlled dist-packages.
+
+ Debian's ``distutils.sysconfig.get_python_lib()`` implementation returns the
+ default package path controlled by APT, but does not patch ``sysconfig`` to
+ do the same. This is similar to the bug worked around in ``get_scheme()``,
+ but here the default is ``deb_system`` instead of ``unix_local``. Ultimately
+ we can't do anything about this Debian bug, and this detection allows us to
+ skip the warning when needed.
+ """
+ if not _looks_like_debian_scheme():
+ return False
+ if value == "/usr/lib/python3/dist-packages":
+ return True
+ return False
+
+
def get_purelib() -> str:
"""Return the default pure-Python lib location."""
- old = _distutils.get_purelib()
new = _sysconfig.get_purelib()
+ if _USE_SYSCONFIG:
+ return new
+
+ old = _distutils.get_purelib()
+ if _looks_like_deb_system_dist_packages(old):
+ return old
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="purelib"):
_log_context()
return old
@@ -288,17 +448,59 @@ def get_purelib() -> str:
def get_platlib() -> str:
"""Return the default platform-shared lib location."""
- old = _distutils.get_platlib()
new = _sysconfig.get_platlib()
+ if _USE_SYSCONFIG:
+ return new
+
+ old = _distutils.get_platlib()
+ if _looks_like_deb_system_dist_packages(old):
+ return old
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"):
_log_context()
return old
+def _deduplicated(v1: str, v2: str) -> List[str]:
+ """Deduplicate values from a list."""
+ if v1 == v2:
+ return [v1]
+ return [v1, v2]
+
+
+def _looks_like_apple_library(path: str) -> bool:
+ """Apple patches sysconfig to *always* look under */Library/Python*."""
+ if sys.platform[:6] != "darwin":
+ return False
+ return path == f"/Library/Python/{get_major_minor_version()}/site-packages"
+
+
def get_prefixed_libs(prefix: str) -> List[str]:
"""Return the lib locations under ``prefix``."""
- old_pure, old_plat = _distutils.get_prefixed_libs(prefix)
new_pure, new_plat = _sysconfig.get_prefixed_libs(prefix)
+ if _USE_SYSCONFIG:
+ return _deduplicated(new_pure, new_plat)
+
+ old_pure, old_plat = _distutils.get_prefixed_libs(prefix)
+ old_lib_paths = _deduplicated(old_pure, old_plat)
+
+ # Apple's Python (shipped with Xcode and Command Line Tools) hard-code
+ # platlib and purelib to '/Library/Python/X.Y/site-packages'. This will
+ # cause serious build isolation bugs when Apple starts shipping 3.10 because
+ # pip will install build backends to the wrong location. This tells users
+ # who is at fault so Apple may notice it and fix the issue in time.
+ if all(_looks_like_apple_library(p) for p in old_lib_paths):
+ deprecated(
+ reason=(
+ "Python distributed by Apple's Command Line Tools incorrectly "
+ "patches sysconfig to always point to '/Library/Python'. This "
+ "will cause build isolation to operate incorrectly on Python "
+ "3.10 or later. Please help report this to Apple so they can "
+ "fix this. https://developer.apple.com/bug-reporting/"
+ ),
+ replacement=None,
+ gone_in=None,
+ )
+ return old_lib_paths
warned = [
_warn_if_mismatch(
@@ -315,6 +517,4 @@ def get_prefixed_libs(prefix: str) -> List[str]:
if any(warned):
_log_context(prefix=prefix)
- if old_pure == old_plat:
- return [old_pure]
- return [old_pure, old_plat]
+ return old_lib_paths
diff --git a/pipenv/patched/notpip/_internal/locations/_distutils.py b/pipenv/patched/notpip/_internal/locations/_distutils.py
index 6be0409462..f5481ea0c0 100644
--- a/pipenv/patched/notpip/_internal/locations/_distutils.py
+++ b/pipenv/patched/notpip/_internal/locations/_distutils.py
@@ -81,8 +81,14 @@ def distutils_scheme(
scheme.update(dict(purelib=i.install_lib, platlib=i.install_lib))
if running_under_virtualenv():
+ if home:
+ prefix = home
+ elif user:
+ prefix = i.install_userbase # type: ignore
+ else:
+ prefix = i.prefix
scheme["headers"] = os.path.join(
- i.prefix,
+ prefix,
"include",
"site",
f"python{get_major_minor_version()}",
@@ -91,10 +97,7 @@ def distutils_scheme(
if root is not None:
path_no_drive = os.path.splitdrive(os.path.abspath(scheme["headers"]))[1]
- scheme["headers"] = os.path.join(
- root,
- path_no_drive[1:],
- )
+ scheme["headers"] = os.path.join(root, path_no_drive[1:])
return scheme
@@ -135,17 +138,20 @@ def get_scheme(
def get_bin_prefix() -> str:
+ # XXX: In old virtualenv versions, sys.prefix can contain '..' components,
+ # so we need to call normpath to eliminate them.
+ prefix = os.path.normpath(sys.prefix)
if WINDOWS:
- bin_py = os.path.join(sys.prefix, "Scripts")
+ bin_py = os.path.join(prefix, "Scripts")
# buildout uses 'bin' on Windows too?
if not os.path.exists(bin_py):
- bin_py = os.path.join(sys.prefix, "bin")
+ bin_py = os.path.join(prefix, "bin")
return bin_py
# Forcing to use /usr/local/bin for standard macOS framework installs
# Also log to ~/Library/Logs/ for use with the Console.app log viewer
- if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/":
+ if sys.platform[:6] == "darwin" and prefix[:16] == "/System/Library/":
return "/usr/local/bin"
- return os.path.join(sys.prefix, "bin")
+ return os.path.join(prefix, "bin")
def get_purelib() -> str:
diff --git a/pipenv/patched/notpip/_internal/locations/_sysconfig.py b/pipenv/patched/notpip/_internal/locations/_sysconfig.py
index 6d82ac3b3d..1becc169b5 100644
--- a/pipenv/patched/notpip/_internal/locations/_sysconfig.py
+++ b/pipenv/patched/notpip/_internal/locations/_sysconfig.py
@@ -15,8 +15,8 @@
# Notes on _infer_* functions.
-# Unfortunately ``_get_default_scheme()`` is private, so there's no way to
-# ask things like "what is the '_prefix' scheme on this platform". These
+# Unfortunately ``get_default_scheme()`` didn't exist before 3.10, so there's no
+# way to ask things like "what is the '_prefix' scheme on this platform". These
# functions try to answer that with some heuristics while accounting for ad-hoc
# platforms not covered by CPython's default sysconfig implementation. If the
# ad-hoc implementation does not fully implement sysconfig, we'll fall back to
@@ -24,7 +24,33 @@
_AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names())
-_HAS_PREFERRED_SCHEME_API = sys.version_info >= (3, 10)
+_PREFERRED_SCHEME_API = getattr(sysconfig, "get_preferred_scheme", None)
+
+
+def _should_use_osx_framework_prefix() -> bool:
+ """Check for Apple's ``osx_framework_library`` scheme.
+
+ Python distributed by Apple's Command Line Tools has this special scheme
+ that's used when:
+
+ * This is a framework build.
+ * We are installing into the system prefix.
+
+ This does not account for ``pip install --prefix`` (also means we're not
+ installing to the system prefix), which should use ``posix_prefix``, but
+ logic here means ``_infer_prefix()`` outputs ``osx_framework_library``. But
+ since ``prefix`` is not available for ``sysconfig.get_default_scheme()``,
+ which is the stdlib replacement for ``_infer_prefix()``, presumably Apple
+ wouldn't be able to magically switch between ``osx_framework_library`` and
+ ``posix_prefix``. ``_infer_prefix()`` returning ``osx_framework_library``
+ means its behavior is consistent whether we use the stdlib implementation
+ or our own, and we deal with this special case in ``get_scheme()`` instead.
+ """
+ return (
+ "osx_framework_library" in _AVAILABLE_SCHEMES
+ and not running_under_virtualenv()
+ and is_osx_framework()
+ )
def _infer_prefix() -> str:
@@ -41,10 +67,9 @@ def _infer_prefix() -> str:
If none of the above works, fall back to ``posix_prefix``.
"""
- if _HAS_PREFERRED_SCHEME_API:
- return sysconfig.get_preferred_scheme("prefix") # type: ignore
- os_framework_global = is_osx_framework() and not running_under_virtualenv()
- if os_framework_global and "osx_framework_library" in _AVAILABLE_SCHEMES:
+ if _PREFERRED_SCHEME_API:
+ return _PREFERRED_SCHEME_API("prefix")
+ if _should_use_osx_framework_prefix():
return "osx_framework_library"
implementation_suffixed = f"{sys.implementation.name}_{os.name}"
if implementation_suffixed in _AVAILABLE_SCHEMES:
@@ -61,8 +86,8 @@ def _infer_prefix() -> str:
def _infer_user() -> str:
"""Try to find a user scheme for the current platform."""
- if _HAS_PREFERRED_SCHEME_API:
- return sysconfig.get_preferred_scheme("user") # type: ignore
+ if _PREFERRED_SCHEME_API:
+ return _PREFERRED_SCHEME_API("user")
if is_osx_framework() and not running_under_virtualenv():
suffixed = "osx_framework_user"
else:
@@ -76,8 +101,8 @@ def _infer_user() -> str:
def _infer_home() -> str:
"""Try to find a home for the current platform."""
- if _HAS_PREFERRED_SCHEME_API:
- return sysconfig.get_preferred_scheme("home") # type: ignore
+ if _PREFERRED_SCHEME_API:
+ return _PREFERRED_SCHEME_API("home")
suffixed = f"{os.name}_home"
if suffixed in _AVAILABLE_SCHEMES:
return suffixed
@@ -130,6 +155,12 @@ def get_scheme(
else:
scheme_name = _infer_prefix()
+ # Special case: When installing into a custom prefix, use posix_prefix
+ # instead of osx_framework_library. See _should_use_osx_framework_prefix()
+ # docstring for details.
+ if prefix is not None and scheme_name == "osx_framework_library":
+ scheme_name = "posix_prefix"
+
if home is not None:
variables = {k: home for k in _HOME_KEYS}
elif prefix is not None:
diff --git a/pipenv/patched/notpip/_internal/main.py b/pipenv/patched/notpip/_internal/main.py
index d57e93c9de..3390484ce4 100644
--- a/pipenv/patched/notpip/_internal/main.py
+++ b/pipenv/patched/notpip/_internal/main.py
@@ -1,8 +1,7 @@
from typing import List, Optional
-def main(args=None):
- # type: (Optional[List[str]]) -> int
+def main(args: Optional[List[str]] = None) -> int:
"""This is preserved for old console scripts that may still be referencing
it.
diff --git a/pipenv/patched/notpip/_internal/metadata/__init__.py b/pipenv/patched/notpip/_internal/metadata/__init__.py
index e3429d2551..cc037c14f0 100644
--- a/pipenv/patched/notpip/_internal/metadata/__init__.py
+++ b/pipenv/patched/notpip/_internal/metadata/__init__.py
@@ -1,10 +1,13 @@
from typing import List, Optional
-from .base import BaseDistribution, BaseEnvironment
+from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
__all__ = [
"BaseDistribution",
"BaseEnvironment",
+ "FilesystemWheel",
+ "MemoryWheel",
+ "Wheel",
"get_default_environment",
"get_environment",
"get_wheel_distribution",
@@ -35,7 +38,18 @@ def get_environment(paths: Optional[List[str]]) -> BaseEnvironment:
return Environment.from_paths(paths)
-def get_wheel_distribution(wheel_path: str, canonical_name: str) -> BaseDistribution:
+def get_directory_distribution(directory: str) -> BaseDistribution:
+ """Get the distribution metadata representation in the specified directory.
+
+ This returns a Distribution instance from the chosen backend based on
+ the given on-disk ``.dist-info`` directory.
+ """
+ from .pkg_resources import Distribution
+
+ return Distribution.from_directory(directory)
+
+
+def get_wheel_distribution(wheel: Wheel, canonical_name: str) -> BaseDistribution:
"""Get the representation of the specified wheel's distribution metadata.
This returns a Distribution instance from the chosen backend based on
@@ -45,4 +59,4 @@ def get_wheel_distribution(wheel_path: str, canonical_name: str) -> BaseDistribu
"""
from .pkg_resources import Distribution
- return Distribution.from_wheel(wheel_path, canonical_name)
+ return Distribution.from_wheel(wheel, canonical_name)
diff --git a/pipenv/patched/notpip/_internal/metadata/base.py b/pipenv/patched/notpip/_internal/metadata/base.py
index af3f811c13..1ff7c95113 100644
--- a/pipenv/patched/notpip/_internal/metadata/base.py
+++ b/pipenv/patched/notpip/_internal/metadata/base.py
@@ -1,8 +1,12 @@
+import csv
import email.message
import json
import logging
+import pathlib
import re
+import zipfile
from typing import (
+ IO,
TYPE_CHECKING,
Collection,
Container,
@@ -10,28 +14,39 @@
Iterator,
List,
Optional,
+ Tuple,
Union,
)
from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
+from pipenv.patched.notpip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
+from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName
from pipenv.patched.notpip._vendor.packaging.version import LegacyVersion, Version
+from pipenv.patched.notpip._internal.exceptions import NoneMetadataError
+from pipenv.patched.notpip._internal.locations import site_packages, user_site
from pipenv.patched.notpip._internal.models.direct_url import (
DIRECT_URL_METADATA_NAME,
DirectUrl,
DirectUrlValidationError,
)
-from pipenv.patched.notpip._internal.utils.misc import stdlib_pkgs # TODO: Move definition here.
+from pipenv.patched.notpip._internal.utils.compat import stdlib_pkgs # TODO: Move definition here.
+from pipenv.patched.notpip._internal.utils.egg_link import (
+ egg_link_path_from_location,
+ egg_link_path_from_sys_path,
+)
+from pipenv.patched.notpip._internal.utils.misc import is_local, normalize_path
+from pipenv.patched.notpip._internal.utils.urls import url_to_path
if TYPE_CHECKING:
from typing import Protocol
-
- from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName
else:
Protocol = object
DistributionVersion = Union[LegacyVersion, Version]
+InfoPath = Union[str, pathlib.PurePosixPath]
+
logger = logging.getLogger(__name__)
@@ -49,7 +64,43 @@ def group(self) -> str:
raise NotImplementedError()
+def _convert_installed_files_path(
+ entry: Tuple[str, ...],
+ info: Tuple[str, ...],
+) -> str:
+ """Convert a legacy installed-files.txt path into modern RECORD path.
+
+ The legacy format stores paths relative to the info directory, while the
+ modern format stores paths relative to the package root, e.g. the
+ site-packages directory.
+
+ :param entry: Path parts of the installed-files.txt entry.
+ :param info: Path parts of the egg-info directory relative to package root.
+ :returns: The converted entry.
+
+ For best compatibility with symlinks, this does not use ``abspath()`` or
+ ``Path.resolve()``, but tries to work with path parts:
+
+ 1. While ``entry`` starts with ``..``, remove the equal amounts of parts
+ from ``info``; if ``info`` is empty, start appending ``..`` instead.
+ 2. Join the two directly.
+ """
+ while entry and entry[0] == "..":
+ if not info or info[-1] == "..":
+ info += ("..",)
+ else:
+ info = info[:-1]
+ entry = entry[1:]
+ return str(pathlib.Path(*info, *entry))
+
+
class BaseDistribution(Protocol):
+ def __repr__(self) -> str:
+ return f"{self.raw_name} {self.version} ({self.location})"
+
+ def __str__(self) -> str:
+ return f"{self.raw_name} {self.version}"
+
@property
def location(self) -> Optional[str]:
"""Where the distribution is loaded from.
@@ -65,8 +116,50 @@ def location(self) -> Optional[str]:
raise NotImplementedError()
@property
- def info_directory(self) -> Optional[str]:
- """Location of the .[egg|dist]-info directory.
+ def editable_project_location(self) -> Optional[str]:
+ """The project location for editable distributions.
+
+ This is the directory where pyproject.toml or setup.py is located.
+ None if the distribution is not installed in editable mode.
+ """
+ # TODO: this property is relatively costly to compute, memoize it ?
+ direct_url = self.direct_url
+ if direct_url:
+ if direct_url.is_local_editable():
+ return url_to_path(direct_url.url)
+ else:
+ # Search for an .egg-link file by walking sys.path, as it was
+ # done before by dist_is_editable().
+ egg_link_path = egg_link_path_from_sys_path(self.raw_name)
+ if egg_link_path:
+ # TODO: get project location from second line of egg_link file
+ # (https://github.com/pypa/pip/issues/10243)
+ return self.location
+ return None
+
+ @property
+ def installed_location(self) -> Optional[str]:
+ """The distribution's "installed" location.
+
+ This should generally be a ``site-packages`` directory. This is
+ usually ``dist.location``, except for legacy develop-installed packages,
+ where ``dist.location`` is the source code location, and this is where
+ the ``.egg-link`` file is.
+
+ The returned location is normalized (in particular, with symlinks removed).
+ """
+ egg_link = egg_link_path_from_location(self.raw_name)
+ if egg_link:
+ location = egg_link
+ elif self.location:
+ location = self.location
+ else:
+ return None
+ return normalize_path(location)
+
+ @property
+ def info_location(self) -> Optional[str]:
+ """Location of the .[egg|dist]-info directory or file.
Similarly to ``location``, a string value is not necessarily a
filesystem path. ``None`` means the distribution is created in-memory.
@@ -81,13 +174,80 @@ def info_directory(self) -> Optional[str]:
raise NotImplementedError()
@property
- def canonical_name(self) -> "NormalizedName":
+ def installed_by_distutils(self) -> bool:
+ """Whether this distribution is installed with legacy distutils format.
+
+ A distribution installed with "raw" distutils not patched by setuptools
+ uses one single file at ``info_location`` to store metadata. We need to
+ treat this specially on uninstallation.
+ """
+ info_location = self.info_location
+ if not info_location:
+ return False
+ return pathlib.Path(info_location).is_file()
+
+ @property
+ def installed_as_egg(self) -> bool:
+ """Whether this distribution is installed as an egg.
+
+ This usually indicates the distribution was installed by (older versions
+ of) easy_install.
+ """
+ location = self.location
+ if not location:
+ return False
+ return location.endswith(".egg")
+
+ @property
+ def installed_with_setuptools_egg_info(self) -> bool:
+ """Whether this distribution is installed with the ``.egg-info`` format.
+
+ This usually indicates the distribution was installed with setuptools
+ with an old pip version or with ``single-version-externally-managed``.
+
+ Note that this ensure the metadata store is a directory. distutils can
+ also installs an ``.egg-info``, but as a file, not a directory. This
+ property is *False* for that case. Also see ``installed_by_distutils``.
+ """
+ info_location = self.info_location
+ if not info_location:
+ return False
+ if not info_location.endswith(".egg-info"):
+ return False
+ return pathlib.Path(info_location).is_dir()
+
+ @property
+ def installed_with_dist_info(self) -> bool:
+ """Whether this distribution is installed with the "modern format".
+
+ This indicates a "modern" installation, e.g. storing metadata in the
+ ``.dist-info`` directory. This applies to installations made by
+ setuptools (but through pip, not directly), or anything using the
+ standardized build backend interface (PEP 517).
+ """
+ info_location = self.info_location
+ if not info_location:
+ return False
+ if not info_location.endswith(".dist-info"):
+ return False
+ return pathlib.Path(info_location).is_dir()
+
+ @property
+ def canonical_name(self) -> NormalizedName:
raise NotImplementedError()
@property
def version(self) -> DistributionVersion:
raise NotImplementedError()
+ @property
+ def setuptools_filename(self) -> str:
+ """Convert a project name to its setuptools-compatible filename.
+
+ This is a copy of ``pkg_resources.to_filename()`` for compatibility.
+ """
+ return self.raw_name.replace("-", "_")
+
@property
def direct_url(self) -> Optional[DirectUrl]:
"""Obtain a DirectUrl from this distribution.
@@ -116,29 +276,62 @@ def direct_url(self) -> Optional[DirectUrl]:
@property
def installer(self) -> str:
- raise NotImplementedError()
+ try:
+ installer_text = self.read_text("INSTALLER")
+ except (OSError, ValueError, NoneMetadataError):
+ return "" # Fail silently if the installer file cannot be read.
+ for line in installer_text.splitlines():
+ cleaned_line = line.strip()
+ if cleaned_line:
+ return cleaned_line
+ return ""
@property
def editable(self) -> bool:
- raise NotImplementedError()
+ return bool(self.editable_project_location)
@property
def local(self) -> bool:
- raise NotImplementedError()
+ """If distribution is installed in the current virtual environment.
+
+ Always True if we're not in a virtualenv.
+ """
+ if self.installed_location is None:
+ return False
+ return is_local(self.installed_location)
@property
def in_usersite(self) -> bool:
- raise NotImplementedError()
+ if self.installed_location is None or user_site is None:
+ return False
+ return self.installed_location.startswith(normalize_path(user_site))
@property
def in_site_packages(self) -> bool:
+ if self.installed_location is None or site_packages is None:
+ return False
+ return self.installed_location.startswith(normalize_path(site_packages))
+
+ def is_file(self, path: InfoPath) -> bool:
+ """Check whether an entry in the info directory is a file."""
+ raise NotImplementedError()
+
+ def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
+ """Iterate through a directory in the info directory.
+
+ Each item yielded would be a path relative to the info directory.
+
+ :raise FileNotFoundError: If ``name`` does not exist in the directory.
+ :raise NotADirectoryError: If ``name`` does not point to a directory.
+ """
raise NotImplementedError()
- def read_text(self, name: str) -> str:
- """Read a file in the .dist-info (or .egg-info) directory.
+ def read_text(self, path: InfoPath) -> str:
+ """Read a file in the info directory.
- Should raise ``FileNotFoundError`` if ``name`` does not exist in the
- metadata directory.
+ :raise FileNotFoundError: If ``name`` does not exist in the directory.
+ :raise NoneMetadataError: If ``name`` exists in the info directory, but
+ cannot be read.
"""
raise NotImplementedError()
@@ -147,7 +340,13 @@ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
@property
def metadata(self) -> email.message.Message:
- """Metadata of distribution parsed from e.g. METADATA or PKG-INFO."""
+ """Metadata of distribution parsed from e.g. METADATA or PKG-INFO.
+
+ This should return an empty message if the metadata file is unavailable.
+
+ :raises NoneMetadataError: If the metadata file is available, but does
+ not contain valid metadata.
+ """
raise NotImplementedError()
@property
@@ -159,12 +358,89 @@ def metadata_version(self) -> Optional[str]:
def raw_name(self) -> str:
"""Value of "Name:" in distribution metadata."""
# The metadata should NEVER be missing the Name: key, but if it somehow
- # does not, fall back to the known canonical name.
+ # does, fall back to the known canonical name.
return self.metadata.get("Name", self.canonical_name)
+ @property
+ def requires_python(self) -> SpecifierSet:
+ """Value of "Requires-Python:" in distribution metadata.
+
+ If the key does not exist or contains an invalid value, an empty
+ SpecifierSet should be returned.
+ """
+ value = self.metadata.get("Requires-Python")
+ if value is None:
+ return SpecifierSet()
+ try:
+ # Convert to str to satisfy the type checker; this can be a Header object.
+ spec = SpecifierSet(str(value))
+ except InvalidSpecifier as e:
+ message = "Package %r has an invalid Requires-Python: %s"
+ logger.warning(message, self.raw_name, e)
+ return SpecifierSet()
+ return spec
+
def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
+ """Dependencies of this distribution.
+
+ For modern .dist-info distributions, this is the collection of
+ "Requires-Dist:" entries in distribution metadata.
+ """
raise NotImplementedError()
+ def iter_provided_extras(self) -> Iterable[str]:
+ """Extras provided by this distribution.
+
+ For modern .dist-info distributions, this is the collection of
+ "Provides-Extra:" entries in distribution metadata.
+ """
+ raise NotImplementedError()
+
+ def _iter_declared_entries_from_record(self) -> Optional[Iterator[str]]:
+ try:
+ text = self.read_text("RECORD")
+ except FileNotFoundError:
+ return None
+ # This extra Path-str cast normalizes entries.
+ return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
+
+ def _iter_declared_entries_from_legacy(self) -> Optional[Iterator[str]]:
+ try:
+ text = self.read_text("installed-files.txt")
+ except FileNotFoundError:
+ return None
+ paths = (p for p in text.splitlines(keepends=False) if p)
+ root = self.location
+ info = self.info_location
+ if root is None or info is None:
+ return paths
+ try:
+ info_rel = pathlib.Path(info).relative_to(root)
+ except ValueError: # info is not relative to root.
+ return paths
+ if not info_rel.parts: # info *is* root.
+ return paths
+ return (
+ _convert_installed_files_path(pathlib.Path(p).parts, info_rel.parts)
+ for p in paths
+ )
+
+ def iter_declared_entries(self) -> Optional[Iterator[str]]:
+ """Iterate through file entires declared in this distribution.
+
+ For modern .dist-info distributions, this is the files listed in the
+ ``RECORD`` metadata file. For legacy setuptools distributions, this
+ comes from ``installed-files.txt``, with entries normalized to be
+ compatible with the format used by ``RECORD``.
+
+ :return: An iterator for listed entries, or None if the distribution
+ contains neither ``RECORD`` nor ``installed-files.txt``.
+ """
+ return (
+ self._iter_declared_entries_from_record()
+ or self._iter_declared_entries_from_legacy()
+ )
+
class BaseEnvironment:
"""An environment containing distributions to introspect."""
@@ -178,7 +454,11 @@ def from_paths(cls, paths: Optional[List[str]]) -> "BaseEnvironment":
raise NotImplementedError()
def get_distribution(self, name: str) -> Optional["BaseDistribution"]:
- """Given a requirement name, return the installed distributions."""
+ """Given a requirement name, return the installed distributions.
+
+ The name may not be normalized. The implementation must canonicalize
+ it for lookup.
+ """
raise NotImplementedError()
def _iter_distributions(self) -> Iterator["BaseDistribution"]:
@@ -240,3 +520,27 @@ def iter_installed_distributions(
if user_only:
it = (d for d in it if d.in_usersite)
return (d for d in it if d.canonical_name not in skip)
+
+
+class Wheel(Protocol):
+ location: str
+
+ def as_zipfile(self) -> zipfile.ZipFile:
+ raise NotImplementedError()
+
+
+class FilesystemWheel(Wheel):
+ def __init__(self, location: str) -> None:
+ self.location = location
+
+ def as_zipfile(self) -> zipfile.ZipFile:
+ return zipfile.ZipFile(self.location, allowZip64=True)
+
+
+class MemoryWheel(Wheel):
+ def __init__(self, location: str, stream: IO[bytes]) -> None:
+ self.location = location
+ self.stream = stream
+
+ def as_zipfile(self) -> zipfile.ZipFile:
+ return zipfile.ZipFile(self.stream, allowZip64=True)
diff --git a/pipenv/patched/notpip/_internal/metadata/pkg_resources.py b/pipenv/patched/notpip/_internal/metadata/pkg_resources.py
index 179a0831d2..b56713d64f 100644
--- a/pipenv/patched/notpip/_internal/metadata/pkg_resources.py
+++ b/pipenv/patched/notpip/_internal/metadata/pkg_resources.py
@@ -1,29 +1,28 @@
import email.message
+import email.parser
import logging
+import os
+import pathlib
import zipfile
-from typing import (
- TYPE_CHECKING,
- Collection,
- Iterable,
- Iterator,
- List,
- NamedTuple,
- Optional,
-)
+from typing import Collection, Iterable, Iterator, List, Mapping, NamedTuple, Optional
from pipenv.patched.notpip._vendor import pkg_resources
from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
-from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
+from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
-from pipenv.patched.notpip._internal.utils import misc # TODO: Move definition here.
-from pipenv.patched.notpip._internal.utils.packaging import get_installer, get_metadata
-from pipenv.patched.notpip._internal.utils.wheel import pkg_resources_distribution_for_wheel
-
-from .base import BaseDistribution, BaseEntryPoint, BaseEnvironment, DistributionVersion
-
-if TYPE_CHECKING:
- from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName
+from pipenv.patched.notpip._internal.exceptions import InvalidWheel, NoneMetadataError, UnsupportedWheel
+from pipenv.patched.notpip._internal.utils.misc import display_path
+from pipenv.patched.notpip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
+
+from .base import (
+ BaseDistribution,
+ BaseEntryPoint,
+ BaseEnvironment,
+ DistributionVersion,
+ InfoPath,
+ Wheel,
+)
logger = logging.getLogger(__name__)
@@ -34,14 +33,91 @@ class EntryPoint(NamedTuple):
group: str
+class WheelMetadata:
+ """IMetadataProvider that reads metadata files from a dictionary.
+
+ This also maps metadata decoding exceptions to our internal exception type.
+ """
+
+ def __init__(self, metadata: Mapping[str, bytes], wheel_name: str) -> None:
+ self._metadata = metadata
+ self._wheel_name = wheel_name
+
+ def has_metadata(self, name: str) -> bool:
+ return name in self._metadata
+
+ def get_metadata(self, name: str) -> str:
+ try:
+ return self._metadata[name].decode()
+ except UnicodeDecodeError as e:
+ # Augment the default error with the origin of the file.
+ raise UnsupportedWheel(
+ f"Error decoding metadata for {self._wheel_name}: {e} in {name} file"
+ )
+
+ def get_metadata_lines(self, name: str) -> Iterable[str]:
+ return pkg_resources.yield_lines(self.get_metadata(name))
+
+ def metadata_isdir(self, name: str) -> bool:
+ return False
+
+ def metadata_listdir(self, name: str) -> List[str]:
+ return []
+
+ def run_script(self, script_name: str, namespace: str) -> None:
+ pass
+
+
class Distribution(BaseDistribution):
def __init__(self, dist: pkg_resources.Distribution) -> None:
self._dist = dist
@classmethod
- def from_wheel(cls, path: str, name: str) -> "Distribution":
- with zipfile.ZipFile(path, allowZip64=True) as zf:
- dist = pkg_resources_distribution_for_wheel(zf, name, path)
+ def from_directory(cls, directory: str) -> "Distribution":
+ dist_dir = directory.rstrip(os.sep)
+
+ # Build a PathMetadata object, from path to metadata. :wink:
+ base_dir, dist_dir_name = os.path.split(dist_dir)
+ metadata = pkg_resources.PathMetadata(base_dir, dist_dir)
+
+ # Determine the correct Distribution object type.
+ if dist_dir.endswith(".egg-info"):
+ dist_cls = pkg_resources.Distribution
+ dist_name = os.path.splitext(dist_dir_name)[0]
+ else:
+ assert dist_dir.endswith(".dist-info")
+ dist_cls = pkg_resources.DistInfoDistribution
+ dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0]
+
+ dist = dist_cls(base_dir, project_name=dist_name, metadata=metadata)
+ return cls(dist)
+
+ @classmethod
+ def from_wheel(cls, wheel: Wheel, name: str) -> "Distribution":
+ """Load the distribution from a given wheel.
+
+ :raises InvalidWheel: Whenever loading of the wheel causes a
+ :py:exc:`zipfile.BadZipFile` exception to be thrown.
+ :raises UnsupportedWheel: If the wheel is a valid zip, but malformed
+ internally.
+ """
+ try:
+ with wheel.as_zipfile() as zf:
+ info_dir, _ = parse_wheel(zf, name)
+ metadata_text = {
+ path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path)
+ for path in zf.namelist()
+ if path.startswith(f"{info_dir}/")
+ }
+ except zipfile.BadZipFile as e:
+ raise InvalidWheel(wheel.location, name) from e
+ except UnsupportedWheel as e:
+ raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
+ dist = pkg_resources.DistInfoDistribution(
+ location=wheel.location,
+ metadata=WheelMetadata(metadata_text, wheel.location),
+ project_name=name,
+ )
return cls(dist)
@property
@@ -49,41 +125,47 @@ def location(self) -> Optional[str]:
return self._dist.location
@property
- def info_directory(self) -> Optional[str]:
+ def info_location(self) -> Optional[str]:
return self._dist.egg_info
@property
- def canonical_name(self) -> "NormalizedName":
+ def installed_by_distutils(self) -> bool:
+ # A distutils-installed distribution is provided by FileMetadata. This
+ # provider has a "path" attribute not present anywhere else. Not the
+ # best introspection logic, but pip has been doing this for a long time.
+ try:
+ return bool(self._dist._provider.path)
+ except AttributeError:
+ return False
+
+ @property
+ def canonical_name(self) -> NormalizedName:
return canonicalize_name(self._dist.project_name)
@property
def version(self) -> DistributionVersion:
return parse_version(self._dist.version)
- @property
- def installer(self) -> str:
- return get_installer(self._dist)
-
- @property
- def editable(self) -> bool:
- return misc.dist_is_editable(self._dist)
+ def is_file(self, path: InfoPath) -> bool:
+ return self._dist.has_metadata(str(path))
- @property
- def local(self) -> bool:
- return misc.dist_is_local(self._dist)
-
- @property
- def in_usersite(self) -> bool:
- return misc.dist_in_usersite(self._dist)
-
- @property
- def in_site_packages(self) -> bool:
- return misc.dist_in_site_packages(self._dist)
+ def iterdir(self, path: InfoPath) -> Iterator[pathlib.PurePosixPath]:
+ name = str(path)
+ if not self._dist.has_metadata(name):
+ raise FileNotFoundError(name)
+ if not self._dist.isdir(name):
+ raise NotADirectoryError(name)
+ for child in self._dist.metadata_listdir(name):
+ yield pathlib.PurePosixPath(path, child)
- def read_text(self, name: str) -> str:
+ def read_text(self, path: InfoPath) -> str:
+ name = str(path)
if not self._dist.has_metadata(name):
raise FileNotFoundError(name)
- return self._dist.get_metadata(name)
+ content = self._dist.get_metadata(name)
+ if content is None:
+ raise NoneMetadataError(self, name)
+ return content
def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
for group, entries in self._dist.get_entry_map().items():
@@ -93,13 +175,35 @@ def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
@property
def metadata(self) -> email.message.Message:
- return get_metadata(self._dist)
+ """
+ :raises NoneMetadataError: if the distribution reports `has_metadata()`
+ True but `get_metadata()` returns None.
+ """
+ if isinstance(self._dist, pkg_resources.DistInfoDistribution):
+ metadata_name = "METADATA"
+ else:
+ metadata_name = "PKG-INFO"
+ try:
+ metadata = self.read_text(metadata_name)
+ except FileNotFoundError:
+ if self.location:
+ displaying_path = display_path(self.location)
+ else:
+ displaying_path = repr(self.location)
+ logger.warning("No metadata found in %s", displaying_path)
+ metadata = ""
+ feed_parser = email.parser.FeedParser()
+ feed_parser.feed(metadata)
+ return feed_parser.close()
def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]:
if extras: # pkg_resources raises on invalid extras, so we sanitize.
extras = frozenset(extras).intersection(self._dist.extras)
return self._dist.requires(extras)
+ def iter_provided_extras(self) -> Iterable[str]:
+ return self._dist.extras
+
class Environment(BaseEnvironment):
def __init__(self, ws: pkg_resources.WorkingSet) -> None:
@@ -126,7 +230,6 @@ def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
return None
def get_distribution(self, name: str) -> Optional[BaseDistribution]:
-
# Search the distribution by looking through the working set.
dist = self._search_distribution(name)
if dist:
diff --git a/pipenv/patched/notpip/_internal/models/candidate.py b/pipenv/patched/notpip/_internal/models/candidate.py
index ea8227cf40..36ee0c6d3d 100644
--- a/pipenv/patched/notpip/_internal/models/candidate.py
+++ b/pipenv/patched/notpip/_internal/models/candidate.py
@@ -5,8 +5,7 @@
class InstallationCandidate(KeyBasedCompareMixin):
- """Represents a potential "candidate" for installation.
- """
+ """Represents a potential "candidate" for installation."""
__slots__ = ["name", "version", "link"]
@@ -17,15 +16,19 @@ def __init__(self, name: str, version: str, link: Link) -> None:
super().__init__(
key=(self.name, self.version, self.link),
- defining_class=InstallationCandidate
+ defining_class=InstallationCandidate,
)
def __repr__(self) -> str:
return "".format(
- self.name, self.version, self.link,
+ self.name,
+ self.version,
+ self.link,
)
def __str__(self) -> str:
- return '{!r} candidate (version {} at {})'.format(
- self.name, self.version, self.link,
+ return "{!r} candidate (version {} at {})".format(
+ self.name,
+ self.version,
+ self.link,
)
diff --git a/pipenv/patched/notpip/_internal/models/direct_url.py b/pipenv/patched/notpip/_internal/models/direct_url.py
index 3f9b6993e3..92060d45db 100644
--- a/pipenv/patched/notpip/_internal/models/direct_url.py
+++ b/pipenv/patched/notpip/_internal/models/direct_url.py
@@ -137,9 +137,7 @@ def __init__(
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["DirInfo"]:
if d is None:
return None
- return cls(
- editable=_get_required(d, bool, "editable", default=False)
- )
+ return cls(editable=_get_required(d, bool, "editable", default=False))
def _to_dict(self) -> Dict[str, Any]:
return _filter_none(editable=self.editable or None)
@@ -149,7 +147,6 @@ def _to_dict(self) -> Dict[str, Any]:
class DirectUrl:
-
def __init__(
self,
url: str,
@@ -165,9 +162,9 @@ def _remove_auth_from_netloc(self, netloc: str) -> str:
return netloc
user_pass, netloc_no_user_pass = netloc.split("@", 1)
if (
- isinstance(self.info, VcsInfo) and
- self.info.vcs == "git" and
- user_pass == "git"
+ isinstance(self.info, VcsInfo)
+ and self.info.vcs == "git"
+ and user_pass == "git"
):
return netloc
if ENV_VAR_RE.match(user_pass):
@@ -218,3 +215,6 @@ def from_json(cls, s: str) -> "DirectUrl":
def to_json(self) -> str:
return json.dumps(self.to_dict(), sort_keys=True)
+
+ def is_local_editable(self) -> bool:
+ return isinstance(self.info, DirInfo) and self.info.editable
diff --git a/pipenv/patched/notpip/_internal/models/format_control.py b/pipenv/patched/notpip/_internal/models/format_control.py
index 40eae29554..a7de83ba6b 100644
--- a/pipenv/patched/notpip/_internal/models/format_control.py
+++ b/pipenv/patched/notpip/_internal/models/format_control.py
@@ -6,15 +6,14 @@
class FormatControl:
- """Helper for managing formats from which a package can be installed.
- """
+ """Helper for managing formats from which a package can be installed."""
__slots__ = ["no_binary", "only_binary"]
def __init__(
self,
no_binary: Optional[Set[str]] = None,
- only_binary: Optional[Set[str]] = None
+ only_binary: Optional[Set[str]] = None,
) -> None:
if no_binary is None:
no_binary = set()
@@ -31,35 +30,30 @@ def __eq__(self, other: object) -> bool:
if self.__slots__ != other.__slots__:
return False
- return all(
- getattr(self, k) == getattr(other, k)
- for k in self.__slots__
- )
+ return all(getattr(self, k) == getattr(other, k) for k in self.__slots__)
def __repr__(self) -> str:
return "{}({}, {})".format(
- self.__class__.__name__,
- self.no_binary,
- self.only_binary
+ self.__class__.__name__, self.no_binary, self.only_binary
)
@staticmethod
def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None:
- if value.startswith('-'):
+ if value.startswith("-"):
raise CommandError(
"--no-binary / --only-binary option requires 1 argument."
)
- new = value.split(',')
- while ':all:' in new:
+ new = value.split(",")
+ while ":all:" in new:
other.clear()
target.clear()
- target.add(':all:')
- del new[:new.index(':all:') + 1]
+ target.add(":all:")
+ del new[: new.index(":all:") + 1]
# Without a none, we want to discard everything as :all: covers it
- if ':none:' not in new:
+ if ":none:" not in new:
return
for name in new:
- if name == ':none:':
+ if name == ":none:":
target.clear()
continue
name = canonicalize_name(name)
@@ -69,16 +63,18 @@ def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> Non
def get_allowed_formats(self, canonical_name: str) -> FrozenSet[str]:
result = {"binary", "source"}
if canonical_name in self.only_binary:
- result.discard('source')
+ result.discard("source")
elif canonical_name in self.no_binary:
- result.discard('binary')
- elif ':all:' in self.only_binary:
- result.discard('source')
- elif ':all:' in self.no_binary:
- result.discard('binary')
+ result.discard("binary")
+ elif ":all:" in self.only_binary:
+ result.discard("source")
+ elif ":all:" in self.no_binary:
+ result.discard("binary")
return frozenset(result)
def disallow_binaries(self) -> None:
self.handle_mutual_excludes(
- ':all:', self.no_binary, self.only_binary,
+ ":all:",
+ self.no_binary,
+ self.only_binary,
)
diff --git a/pipenv/patched/notpip/_internal/models/index.py b/pipenv/patched/notpip/_internal/models/index.py
index 1874a5b60d..b94c32511f 100644
--- a/pipenv/patched/notpip/_internal/models/index.py
+++ b/pipenv/patched/notpip/_internal/models/index.py
@@ -2,18 +2,16 @@
class PackageIndex:
- """Represents a Package Index and provides easier access to endpoints
- """
+ """Represents a Package Index and provides easier access to endpoints"""
- __slots__ = ['url', 'netloc', 'simple_url', 'pypi_url',
- 'file_storage_domain']
+ __slots__ = ["url", "netloc", "simple_url", "pypi_url", "file_storage_domain"]
def __init__(self, url: str, file_storage_domain: str) -> None:
super().__init__()
self.url = url
self.netloc = urllib.parse.urlsplit(url).netloc
- self.simple_url = self._url_for_path('simple')
- self.pypi_url = self._url_for_path('pypi')
+ self.simple_url = self._url_for_path("simple")
+ self.pypi_url = self._url_for_path("pypi")
# This is part of a temporary hack used to block installs of PyPI
# packages which depend on external urls only necessary until PyPI can
@@ -24,9 +22,7 @@ def _url_for_path(self, path: str) -> str:
return urllib.parse.urljoin(self.url, path)
-PyPI = PackageIndex(
- 'https://pypi.org/', file_storage_domain='files.pythonhosted.org'
-)
+PyPI = PackageIndex("https://pypi.org/", file_storage_domain="files.pythonhosted.org")
TestPyPI = PackageIndex(
- 'https://test.pypi.org/', file_storage_domain='test-files.pythonhosted.org'
+ "https://test.pypi.org/", file_storage_domain="test-files.pythonhosted.org"
)
diff --git a/pipenv/patched/notpip/_internal/models/link.py b/pipenv/patched/notpip/_internal/models/link.py
index 8baa9ac06a..334a38c30a 100644
--- a/pipenv/patched/notpip/_internal/models/link.py
+++ b/pipenv/patched/notpip/_internal/models/link.py
@@ -26,8 +26,7 @@
class Link(KeyBasedCompareMixin):
- """Represents a parsed link from a Package Index's simple URL
- """
+ """Represents a parsed link from a Package Index's simple URL"""
__slots__ = [
"_parsed_url",
@@ -68,7 +67,7 @@ def __init__(
"""
# url can be a UNC windows share
- if url.startswith('\\\\'):
+ if url.startswith("\\\\"):
url = path_to_url(url)
self._parsed_url = urllib.parse.urlsplit(url)
@@ -86,17 +85,18 @@ def __init__(
def __str__(self) -> str:
if self.requires_python:
- rp = f' (requires-python:{self.requires_python})'
+ rp = f" (requires-python:{self.requires_python})"
else:
- rp = ''
+ rp = ""
if self.comes_from:
- return '{} (from {}){}'.format(
- redact_auth_from_url(self._url), self.comes_from, rp)
+ return "{} (from {}){}".format(
+ redact_auth_from_url(self._url), self.comes_from, rp
+ )
else:
return redact_auth_from_url(str(self._url))
def __repr__(self) -> str:
- return f''
+ return f""
@property
def url(self) -> str:
@@ -104,7 +104,7 @@ def url(self) -> str:
@property
def filename(self) -> str:
- path = self.path.rstrip('/')
+ path = self.path.rstrip("/")
name = posixpath.basename(path)
if not name:
# Make sure we don't leak auth information if the netloc
@@ -113,7 +113,7 @@ def filename(self) -> str:
return netloc
name = urllib.parse.unquote(name)
- assert name, f'URL {self._url!r} produced no filename'
+ assert name, f"URL {self._url!r} produced no filename"
return name
@property
@@ -136,7 +136,7 @@ def path(self) -> str:
return urllib.parse.unquote(self._parsed_url.path)
def splitext(self) -> Tuple[str, str]:
- return splitext(posixpath.basename(self.path.rstrip('/')))
+ return splitext(posixpath.basename(self.path.rstrip("/")))
@property
def ext(self) -> str:
@@ -145,9 +145,9 @@ def ext(self) -> str:
@property
def url_without_fragment(self) -> str:
scheme, netloc, path, query, fragment = self._parsed_url
- return urllib.parse.urlunsplit((scheme, netloc, path, query, ''))
+ return urllib.parse.urlunsplit((scheme, netloc, path, query, ""))
- _egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')
+ _egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")
@property
def egg_fragment(self) -> Optional[str]:
@@ -156,7 +156,7 @@ def egg_fragment(self) -> Optional[str]:
return None
return match.group(1)
- _subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)')
+ _subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
@property
def subdirectory_fragment(self) -> Optional[str]:
@@ -166,7 +166,7 @@ def subdirectory_fragment(self) -> Optional[str]:
return match.group(1)
_hash_re = re.compile(
- r'({choices})=([a-f0-9]+)'.format(choices="|".join(_SUPPORTED_HASHES))
+ r"({choices})=([a-f0-9]+)".format(choices="|".join(_SUPPORTED_HASHES))
)
@property
@@ -185,11 +185,11 @@ def hash_name(self) -> Optional[str]:
@property
def show_url(self) -> str:
- return posixpath.basename(self._url.split('#', 1)[0].split('?', 1)[0])
+ return posixpath.basename(self._url.split("#", 1)[0].split("?", 1)[0])
@property
def is_file(self) -> bool:
- return self.scheme == 'file'
+ return self.scheme == "file"
def is_existing_dir(self) -> bool:
return self.is_file and os.path.isdir(self.file_path)
@@ -256,33 +256,33 @@ class _CleanResult(NamedTuple):
subdirectory: str
hashes: Dict[str, str]
- @classmethod
- def from_link(cls, link: Link) -> "_CleanResult":
- parsed = link._parsed_url
- netloc = parsed.netloc.rsplit("@", 1)[-1]
- # According to RFC 8089, an empty host in file: means localhost.
- if parsed.scheme == "file" and not netloc:
- netloc = "localhost"
- fragment = urllib.parse.parse_qs(parsed.fragment)
- if "egg" in fragment:
- logger.debug("Ignoring egg= fragment in %s", link)
- try:
- # If there are multiple subdirectory values, use the first one.
- # This matches the behavior of Link.subdirectory_fragment.
- subdirectory = fragment["subdirectory"][0]
- except (IndexError, KeyError):
- subdirectory = ""
- # If there are multiple hash values under the same algorithm, use the
- # first one. This matches the behavior of Link.hash_value.
- hashes = {k: fragment[k][0] for k in _SUPPORTED_HASHES if k in fragment}
- return cls(
- parsed=parsed._replace(netloc=netloc, query="", fragment=""),
- query=urllib.parse.parse_qs(parsed.query),
- subdirectory=subdirectory,
- hashes=hashes,
- )
+
+def _clean_link(link: Link) -> _CleanResult:
+ parsed = link._parsed_url
+ netloc = parsed.netloc.rsplit("@", 1)[-1]
+ # According to RFC 8089, an empty host in file: means localhost.
+ if parsed.scheme == "file" and not netloc:
+ netloc = "localhost"
+ fragment = urllib.parse.parse_qs(parsed.fragment)
+ if "egg" in fragment:
+ logger.debug("Ignoring egg= fragment in %s", link)
+ try:
+ # If there are multiple subdirectory values, use the first one.
+ # This matches the behavior of Link.subdirectory_fragment.
+ subdirectory = fragment["subdirectory"][0]
+ except (IndexError, KeyError):
+ subdirectory = ""
+ # If there are multiple hash values under the same algorithm, use the
+ # first one. This matches the behavior of Link.hash_value.
+ hashes = {k: fragment[k][0] for k in _SUPPORTED_HASHES if k in fragment}
+ return _CleanResult(
+ parsed=parsed._replace(netloc=netloc, query="", fragment=""),
+ query=urllib.parse.parse_qs(parsed.query),
+ subdirectory=subdirectory,
+ hashes=hashes,
+ )
@functools.lru_cache(maxsize=None)
def links_equivalent(link1: Link, link2: Link) -> bool:
- return _CleanResult.from_link(link1) == _CleanResult.from_link(link2)
+ return _clean_link(link1) == _clean_link(link2)
diff --git a/pipenv/patched/notpip/_internal/models/scheme.py b/pipenv/patched/notpip/_internal/models/scheme.py
index 9a8dafba30..f51190ac60 100644
--- a/pipenv/patched/notpip/_internal/models/scheme.py
+++ b/pipenv/patched/notpip/_internal/models/scheme.py
@@ -6,7 +6,7 @@
"""
-SCHEME_KEYS = ['platlib', 'purelib', 'headers', 'scripts', 'data']
+SCHEME_KEYS = ["platlib", "purelib", "headers", "scripts", "data"]
class Scheme:
diff --git a/pipenv/patched/notpip/_internal/models/search_scope.py b/pipenv/patched/notpip/_internal/models/search_scope.py
index 4b70040732..3ace085b14 100644
--- a/pipenv/patched/notpip/_internal/models/search_scope.py
+++ b/pipenv/patched/notpip/_internal/models/search_scope.py
@@ -38,7 +38,7 @@ def create(
# blindly normalize anything starting with a ~...
built_find_links: List[str] = []
for link in find_links:
- if link.startswith('~'):
+ if link.startswith("~"):
new_link = normalize_path(link)
if os.path.exists(new_link):
link = new_link
@@ -49,11 +49,11 @@ def create(
if not has_tls():
for link in itertools.chain(index_urls, built_find_links):
parsed = urllib.parse.urlparse(link)
- if parsed.scheme == 'https':
+ if parsed.scheme == "https":
logger.warning(
- 'pip is configured with locations that require '
- 'TLS/SSL, however the ssl module in Python is not '
- 'available.'
+ "pip is configured with locations that require "
+ "TLS/SSL, however the ssl module in Python is not "
+ "available."
)
break
@@ -88,20 +88,23 @@ def get_formatted_locations(self) -> str:
# exceptions for malformed URLs
if not purl.scheme and not purl.netloc:
logger.warning(
- 'The index url "%s" seems invalid, '
- 'please provide a scheme.', redacted_index_url)
+ 'The index url "%s" seems invalid, please provide a scheme.',
+ redacted_index_url,
+ )
redacted_index_urls.append(redacted_index_url)
- lines.append('Looking in indexes: {}'.format(
- ', '.join(redacted_index_urls)))
+ lines.append(
+ "Looking in indexes: {}".format(", ".join(redacted_index_urls))
+ )
if self.find_links:
lines.append(
- 'Looking in links: {}'.format(', '.join(
- redact_auth_from_url(url) for url in self.find_links))
+ "Looking in links: {}".format(
+ ", ".join(redact_auth_from_url(url) for url in self.find_links)
+ )
)
- return '\n'.join(lines)
+ return "\n".join(lines)
def get_index_urls_locations(self, project_name: str) -> List[str]:
"""Returns the locations found via self.index_urls
@@ -112,15 +115,15 @@ def get_index_urls_locations(self, project_name: str) -> List[str]:
def mkurl_pypi_url(url: str) -> str:
loc = posixpath.join(
- url,
- urllib.parse.quote(canonicalize_name(project_name)))
+ url, urllib.parse.quote(canonicalize_name(project_name))
+ )
# For maximum compatibility with easy_install, ensure the path
# ends in a trailing slash. Although this isn't in the spec
# (and PyPI can handle it without the slash) some other index
# implementations might break if they relied on easy_install's
# behavior.
- if not loc.endswith('/'):
- loc = loc + '/'
+ if not loc.endswith("/"):
+ loc = loc + "/"
return loc
return [mkurl_pypi_url(url) for url in self.index_urls]
diff --git a/pipenv/patched/notpip/_internal/models/selection_prefs.py b/pipenv/patched/notpip/_internal/models/selection_prefs.py
index a6b6388126..7e93637bc9 100644
--- a/pipenv/patched/notpip/_internal/models/selection_prefs.py
+++ b/pipenv/patched/notpip/_internal/models/selection_prefs.py
@@ -9,8 +9,13 @@ class SelectionPreferences:
and installing files.
"""
- __slots__ = ['allow_yanked', 'allow_all_prereleases', 'format_control',
- 'prefer_binary', 'ignore_requires_python']
+ __slots__ = [
+ "allow_yanked",
+ "allow_all_prereleases",
+ "format_control",
+ "prefer_binary",
+ "ignore_requires_python",
+ ]
# Don't include an allow_yanked default value to make sure each call
# site considers whether yanked releases are allowed. This also causes
diff --git a/pipenv/patched/notpip/_internal/models/target_python.py b/pipenv/patched/notpip/_internal/models/target_python.py
index 9bd6fba562..0eafa97fa8 100644
--- a/pipenv/patched/notpip/_internal/models/target_python.py
+++ b/pipenv/patched/notpip/_internal/models/target_python.py
@@ -53,7 +53,7 @@ def __init__(
else:
py_version_info = normalize_version_info(py_version_info)
- py_version = '.'.join(map(str, py_version_info[:2]))
+ py_version = ".".join(map(str, py_version_info[:2]))
self.abis = abis
self.implementation = implementation
@@ -70,19 +70,18 @@ def format_given(self) -> str:
"""
display_version = None
if self._given_py_version_info is not None:
- display_version = '.'.join(
+ display_version = ".".join(
str(part) for part in self._given_py_version_info
)
key_values = [
- ('platforms', self.platforms),
- ('version_info', display_version),
- ('abis', self.abis),
- ('implementation', self.implementation),
+ ("platforms", self.platforms),
+ ("version_info", display_version),
+ ("abis", self.abis),
+ ("implementation", self.implementation),
]
- return ' '.join(
- f'{key}={value!r}' for key, value in key_values
- if value is not None
+ return " ".join(
+ f"{key}={value!r}" for key, value in key_values if value is not None
)
def get_tags(self) -> List[Tag]:
diff --git a/pipenv/patched/notpip/_internal/models/wheel.py b/pipenv/patched/notpip/_internal/models/wheel.py
index ed690340f9..9a5fccda2f 100644
--- a/pipenv/patched/notpip/_internal/models/wheel.py
+++ b/pipenv/patched/notpip/_internal/models/wheel.py
@@ -16,7 +16,7 @@ class Wheel:
r"""^(?P(?P.+?)-(?P.*?))
((-(?P\d[^-]*?))?-(?P.+?)-(?P.+?)-(?P.+?)
\.whl|\.dist-info)$""",
- re.VERBOSE
+ re.VERBOSE,
)
def __init__(self, filename: str) -> None:
@@ -25,23 +25,20 @@ def __init__(self, filename: str) -> None:
"""
wheel_info = self.wheel_file_re.match(filename)
if not wheel_info:
- raise InvalidWheelFilename(
- f"{filename} is not a valid wheel filename."
- )
+ raise InvalidWheelFilename(f"{filename} is not a valid wheel filename.")
self.filename = filename
- self.name = wheel_info.group('name').replace('_', '-')
+ self.name = wheel_info.group("name").replace("_", "-")
# we'll assume "_" means "-" due to wheel naming scheme
# (https://github.com/pypa/pip/issues/1150)
- self.version = wheel_info.group('ver').replace('_', '-')
- self.build_tag = wheel_info.group('build')
- self.pyversions = wheel_info.group('pyver').split('.')
- self.abis = wheel_info.group('abi').split('.')
- self.plats = wheel_info.group('plat').split('.')
+ self.version = wheel_info.group("ver").replace("_", "-")
+ self.build_tag = wheel_info.group("build")
+ self.pyversions = wheel_info.group("pyver").split(".")
+ self.abis = wheel_info.group("abi").split(".")
+ self.plats = wheel_info.group("plat").split(".")
# All the tag combinations from this file
self.file_tags = {
- Tag(x, y, z) for x in self.pyversions
- for y in self.abis for z in self.plats
+ Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats
}
def get_formatted_file_tags(self) -> List[str]:
diff --git a/pipenv/patched/notpip/_internal/network/auth.py b/pipenv/patched/notpip/_internal/network/auth.py
index 102ce5be61..66ce543b52 100644
--- a/pipenv/patched/notpip/_internal/network/auth.py
+++ b/pipenv/patched/notpip/_internal/network/auth.py
@@ -28,13 +28,13 @@
try:
import keyring
except ImportError:
- keyring = None
+ keyring = None # type: ignore[assignment]
except Exception as exc:
logger.warning(
"Keyring is skipped due to an exception: %s",
str(exc),
)
- keyring = None
+ keyring = None # type: ignore[assignment]
def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[AuthInfo]:
@@ -66,7 +66,7 @@ def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[Au
"Keyring is skipped due to an exception: %s",
str(exc),
)
- keyring = None
+ keyring = None # type: ignore[assignment]
return None
@@ -179,9 +179,16 @@ def _get_url_and_credentials(
# Try to get credentials from original url
username, password = self._get_new_credentials(original_url)
- # If credentials not found, use any stored credentials for this netloc
- if username is None and password is None:
- username, password = self.passwords.get(netloc, (None, None))
+ # If credentials not found, use any stored credentials for this netloc.
+ # Do this if either the username or the password is missing.
+ # This accounts for the situation in which the user has specified
+ # the username in the index url, but the password comes from keyring.
+ if (username is None or password is None) and netloc in self.passwords:
+ un, pw = self.passwords[netloc]
+ # It is possible that the cached credentials are for a different username,
+ # in which case the cache should be ignored.
+ if username is None or username == un:
+ username, password = un, pw
if username is not None or password is not None:
# Convert the username and password if they're None, so that
diff --git a/pipenv/patched/notpip/_internal/network/cache.py b/pipenv/patched/notpip/_internal/network/cache.py
index 0ec6f96747..99b84b7d8d 100644
--- a/pipenv/patched/notpip/_internal/network/cache.py
+++ b/pipenv/patched/notpip/_internal/network/cache.py
@@ -53,7 +53,7 @@ def get(self, key: str) -> Optional[bytes]:
with open(path, "rb") as f:
return f.read()
- def set(self, key: str, value: bytes) -> None:
+ def set(self, key: str, value: bytes, expires: Optional[int] = None) -> None:
path = self._get_cache_path(key)
with suppressed_cache_errors():
ensure_dir(os.path.dirname(path))
diff --git a/pipenv/patched/notpip/_internal/network/download.py b/pipenv/patched/notpip/_internal/network/download.py
index fe229686d3..7175398b46 100644
--- a/pipenv/patched/notpip/_internal/network/download.py
+++ b/pipenv/patched/notpip/_internal/network/download.py
@@ -8,7 +8,7 @@
from pipenv.patched.notpip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
-from pipenv.patched.notpip._internal.cli.progress_bars import DownloadProgressProvider
+from pipenv.patched.notpip._internal.cli.progress_bars import get_download_progress_renderer
from pipenv.patched.notpip._internal.exceptions import NetworkConnectionError
from pipenv.patched.notpip._internal.models.index import PyPI
from pipenv.patched.notpip._internal.models.link import Link
@@ -65,7 +65,8 @@ def _prepare_download(
if not show_progress:
return chunks
- return DownloadProgressProvider(progress_bar, max=total_length)(chunks)
+ renderer = get_download_progress_renderer(bar_type=progress_bar, size=total_length)
+ return renderer(chunks)
def sanitize_content_filename(filename: str) -> str:
diff --git a/pipenv/patched/notpip/_internal/network/lazy_wheel.py b/pipenv/patched/notpip/_internal/network/lazy_wheel.py
index 17565c7e33..42545850c5 100644
--- a/pipenv/patched/notpip/_internal/network/lazy_wheel.py
+++ b/pipenv/patched/notpip/_internal/network/lazy_wheel.py
@@ -8,33 +8,33 @@
from typing import Any, Dict, Iterator, List, Optional, Tuple
from zipfile import BadZipfile, ZipFile
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
+from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
+from pipenv.patched.notpip._internal.metadata import BaseDistribution, MemoryWheel, get_wheel_distribution
from pipenv.patched.notpip._internal.network.session import PipSession
from pipenv.patched.notpip._internal.network.utils import HEADERS, raise_for_status, response_chunks
-from pipenv.patched.notpip._internal.utils.wheel import pkg_resources_distribution_for_wheel
class HTTPRangeRequestUnsupported(Exception):
pass
-def dist_from_wheel_url(name: str, url: str, session: PipSession) -> Distribution:
- """Return a pkg_resources.Distribution from the given wheel URL.
+def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistribution:
+ """Return a distribution object from the given wheel URL.
This uses HTTP range requests to only fetch the potion of the wheel
containing metadata, just enough for the object to be constructed.
If such requests are not supported, HTTPRangeRequestUnsupported
is raised.
"""
- with LazyZipOverHTTP(url, session) as wheel:
+ with LazyZipOverHTTP(url, session) as zf:
# For read-only ZIP files, ZipFile only needs methods read,
# seek, seekable and tell, not the whole IO protocol.
- zip_file = ZipFile(wheel) # type: ignore
+ wheel = MemoryWheel(zf.name, zf) # type: ignore
# After context manager exit, wheel.name
# is an invalid file by intention.
- return pkg_resources_distribution_for_wheel(zip_file, name, wheel.name)
+ return get_wheel_distribution(wheel, canonicalize_name(name))
class LazyZipOverHTTP:
diff --git a/pipenv/patched/notpip/_internal/network/session.py b/pipenv/patched/notpip/_internal/network/session.py
index cf54d62291..88f79b0b38 100644
--- a/pipenv/patched/notpip/_internal/network/session.py
+++ b/pipenv/patched/notpip/_internal/network/session.py
@@ -2,17 +2,8 @@
network request configuration and behavior.
"""
-# When mypy runs on Windows the call to distro.linux_distribution() is skipped
-# resulting in the failure:
-#
-# error: unused 'type: ignore' comment
-#
-# If the upstream module adds typing, this comment should be removed. See
-# https://github.com/nir0s/distro/pull/269
-#
-# mypy: warn-unused-ignores=False
-
import email.utils
+import io
import ipaddress
import json
import logging
@@ -128,9 +119,8 @@ def user_agent() -> str:
if sys.platform.startswith("linux"):
from pipenv.patched.notpip._vendor import distro
- # https://github.com/nir0s/distro/pull/269
- linux_distribution = distro.linux_distribution() # type: ignore
- distro_infos = dict(
+ linux_distribution = distro.name(), distro.version(), distro.codename()
+ distro_infos: Dict[str, Any] = dict(
filter(
lambda x: x[1],
zip(["name", "version", "id"], linux_distribution),
@@ -218,8 +208,11 @@ def send(
try:
stats = os.stat(pathname)
except OSError as exc:
+ # format the exception raised as a io.BytesIO object,
+ # to return a better error message:
resp.status_code = 404
- resp.raw = exc
+ resp.reason = type(exc).__name__
+ resp.raw = io.BytesIO(f"{resp.reason}: {exc}".encode("utf8"))
else:
modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
content_type = mimetypes.guess_type(pathname)[0] or "text/plain"
@@ -369,8 +362,15 @@ def add_trusted_host(
if host_port not in self.pip_trusted_origins:
self.pip_trusted_origins.append(host_port)
+ self.mount(
+ build_url_from_netloc(host, scheme="http") + "/", self._trusted_host_adapter
+ )
self.mount(build_url_from_netloc(host) + "/", self._trusted_host_adapter)
if not host_port[1]:
+ self.mount(
+ build_url_from_netloc(host, scheme="http") + ":",
+ self._trusted_host_adapter,
+ )
# Mount wildcard ports for the same host.
self.mount(build_url_from_netloc(host) + ":", self._trusted_host_adapter)
diff --git a/pipenv/patched/notpip/_internal/operations/build/metadata.py b/pipenv/patched/notpip/_internal/operations/build/metadata.py
index ced231b483..b1ecfb8c77 100644
--- a/pipenv/patched/notpip/_internal/operations/build/metadata.py
+++ b/pipenv/patched/notpip/_internal/operations/build/metadata.py
@@ -6,19 +6,22 @@
from pipenv.patched.notpip._vendor.pep517.wrappers import Pep517HookCaller
from pipenv.patched.notpip._internal.build_env import BuildEnvironment
+from pipenv.patched.notpip._internal.exceptions import (
+ InstallationSubprocessError,
+ MetadataGenerationFailed,
+)
from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
-def generate_metadata(build_env, backend):
- # type: (BuildEnvironment, Pep517HookCaller) -> str
+def generate_metadata(
+ build_env: BuildEnvironment, backend: Pep517HookCaller, details: str
+) -> str:
"""Generate metadata using mechanisms described in PEP 517.
Returns the generated metadata directory.
"""
- metadata_tmpdir = TempDirectory(
- kind="modern-metadata", globally_managed=True
- )
+ metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
metadata_dir = metadata_tmpdir.path
@@ -26,10 +29,11 @@ def generate_metadata(build_env, backend):
# Note that Pep517HookCaller implements a fallback for
# prepare_metadata_for_build_wheel, so we don't have to
# consider the possibility that this hook doesn't exist.
- runner = runner_with_spinner_message("Preparing wheel metadata")
+ runner = runner_with_spinner_message("Preparing metadata (pyproject.toml)")
with backend.subprocess_runner(runner):
- distinfo_dir = backend.prepare_metadata_for_build_wheel(
- metadata_dir
- )
+ try:
+ distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir)
+ except InstallationSubprocessError as error:
+ raise MetadataGenerationFailed(package_details=details) from error
return os.path.join(metadata_dir, distinfo_dir)
diff --git a/pipenv/patched/notpip/_internal/operations/build/metadata_editable.py b/pipenv/patched/notpip/_internal/operations/build/metadata_editable.py
new file mode 100644
index 0000000000..cdd3fd5350
--- /dev/null
+++ b/pipenv/patched/notpip/_internal/operations/build/metadata_editable.py
@@ -0,0 +1,41 @@
+"""Metadata generation logic for source distributions.
+"""
+
+import os
+
+from pipenv.patched.notpip._vendor.pep517.wrappers import Pep517HookCaller
+
+from pipenv.patched.notpip._internal.build_env import BuildEnvironment
+from pipenv.patched.notpip._internal.exceptions import (
+ InstallationSubprocessError,
+ MetadataGenerationFailed,
+)
+from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message
+from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
+
+
+def generate_editable_metadata(
+ build_env: BuildEnvironment, backend: Pep517HookCaller, details: str
+) -> str:
+ """Generate metadata using mechanisms described in PEP 660.
+
+ Returns the generated metadata directory.
+ """
+ metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
+
+ metadata_dir = metadata_tmpdir.path
+
+ with build_env:
+ # Note that Pep517HookCaller implements a fallback for
+ # prepare_metadata_for_build_wheel/editable, so we don't have to
+ # consider the possibility that this hook doesn't exist.
+ runner = runner_with_spinner_message(
+ "Preparing editable metadata (pyproject.toml)"
+ )
+ with backend.subprocess_runner(runner):
+ try:
+ distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir)
+ except InstallationSubprocessError as error:
+ raise MetadataGenerationFailed(package_details=details) from error
+
+ return os.path.join(metadata_dir, distinfo_dir)
diff --git a/pipenv/patched/notpip/_internal/operations/build/metadata_legacy.py b/pipenv/patched/notpip/_internal/operations/build/metadata_legacy.py
index 5443b86669..fa345a530c 100644
--- a/pipenv/patched/notpip/_internal/operations/build/metadata_legacy.py
+++ b/pipenv/patched/notpip/_internal/operations/build/metadata_legacy.py
@@ -5,7 +5,12 @@
import os
from pipenv.patched.notpip._internal.build_env import BuildEnvironment
-from pipenv.patched.notpip._internal.exceptions import InstallationError
+from pipenv.patched.notpip._internal.cli.spinners import open_spinner
+from pipenv.patched.notpip._internal.exceptions import (
+ InstallationError,
+ InstallationSubprocessError,
+ MetadataGenerationFailed,
+)
from pipenv.patched.notpip._internal.utils.setuptools_build import make_setuptools_egg_info_args
from pipenv.patched.notpip._internal.utils.subprocess import call_subprocess
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory
@@ -13,49 +18,39 @@
logger = logging.getLogger(__name__)
-def _find_egg_info(directory):
- # type: (str) -> str
- """Find an .egg-info subdirectory in `directory`.
- """
- filenames = [
- f for f in os.listdir(directory) if f.endswith(".egg-info")
- ]
+def _find_egg_info(directory: str) -> str:
+ """Find an .egg-info subdirectory in `directory`."""
+ filenames = [f for f in os.listdir(directory) if f.endswith(".egg-info")]
if not filenames:
- raise InstallationError(
- f"No .egg-info directory found in {directory}"
- )
+ raise InstallationError(f"No .egg-info directory found in {directory}")
if len(filenames) > 1:
raise InstallationError(
- "More than one .egg-info directory found in {}".format(
- directory
- )
+ "More than one .egg-info directory found in {}".format(directory)
)
return os.path.join(directory, filenames[0])
def generate_metadata(
- build_env, # type: BuildEnvironment
- setup_py_path, # type: str
- source_dir, # type: str
- isolated, # type: bool
- details, # type: str
-):
- # type: (...) -> str
+ build_env: BuildEnvironment,
+ setup_py_path: str,
+ source_dir: str,
+ isolated: bool,
+ details: str,
+) -> str:
"""Generate metadata using setup.py-based defacto mechanisms.
Returns the generated metadata directory.
"""
logger.debug(
- 'Running setup.py (path:%s) egg_info for package %s',
- setup_py_path, details,
+ "Running setup.py (path:%s) egg_info for package %s",
+ setup_py_path,
+ details,
)
- egg_info_dir = TempDirectory(
- kind="pip-egg-info", globally_managed=True
- ).path
+ egg_info_dir = TempDirectory(kind="pip-egg-info", globally_managed=True).path
args = make_setuptools_egg_info_args(
setup_py_path,
@@ -64,11 +59,16 @@ def generate_metadata(
)
with build_env:
- call_subprocess(
- args,
- cwd=source_dir,
- command_desc='python setup.py egg_info',
- )
+ with open_spinner("Preparing metadata (setup.py)") as spinner:
+ try:
+ call_subprocess(
+ args,
+ cwd=source_dir,
+ command_desc="python setup.py egg_info",
+ spinner=spinner,
+ )
+ except InstallationSubprocessError as error:
+ raise MetadataGenerationFailed(package_details=details) from error
# Return the .egg-info directory.
return _find_egg_info(egg_info_dir)
diff --git a/pipenv/patched/notpip/_internal/operations/build/wheel.py b/pipenv/patched/notpip/_internal/operations/build/wheel.py
index d8ea255ff2..9e36925ff0 100644
--- a/pipenv/patched/notpip/_internal/operations/build/wheel.py
+++ b/pipenv/patched/notpip/_internal/operations/build/wheel.py
@@ -10,22 +10,21 @@
def build_wheel_pep517(
- name, # type: str
- backend, # type: Pep517HookCaller
- metadata_directory, # type: str
- tempd, # type: str
-):
- # type: (...) -> Optional[str]
+ name: str,
+ backend: Pep517HookCaller,
+ metadata_directory: str,
+ tempd: str,
+) -> Optional[str]:
"""Build one InstallRequirement using the PEP 517 build process.
Returns path to wheel if successfully built. Otherwise, returns None.
"""
assert metadata_directory is not None
try:
- logger.debug('Destination directory: %s', tempd)
+ logger.debug("Destination directory: %s", tempd)
runner = runner_with_spinner_message(
- f'Building wheel for {name} (PEP 517)'
+ f"Building wheel for {name} (pyproject.toml)"
)
with backend.subprocess_runner(runner):
wheel_name = backend.build_wheel(
@@ -33,6 +32,6 @@ def build_wheel_pep517(
metadata_directory=metadata_directory,
)
except Exception:
- logger.error('Failed building wheel for %s', name)
+ logger.error("Failed building wheel for %s", name)
return None
return os.path.join(tempd, wheel_name)
diff --git a/pipenv/patched/notpip/_internal/operations/build/wheel_editable.py b/pipenv/patched/notpip/_internal/operations/build/wheel_editable.py
new file mode 100644
index 0000000000..7826533405
--- /dev/null
+++ b/pipenv/patched/notpip/_internal/operations/build/wheel_editable.py
@@ -0,0 +1,46 @@
+import logging
+import os
+from typing import Optional
+
+from pipenv.patched.notpip._vendor.pep517.wrappers import HookMissing, Pep517HookCaller
+
+from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message
+
+logger = logging.getLogger(__name__)
+
+
+def build_wheel_editable(
+ name: str,
+ backend: Pep517HookCaller,
+ metadata_directory: str,
+ tempd: str,
+) -> Optional[str]:
+ """Build one InstallRequirement using the PEP 660 build process.
+
+ Returns path to wheel if successfully built. Otherwise, returns None.
+ """
+ assert metadata_directory is not None
+ try:
+ logger.debug("Destination directory: %s", tempd)
+
+ runner = runner_with_spinner_message(
+ f"Building editable for {name} (pyproject.toml)"
+ )
+ with backend.subprocess_runner(runner):
+ try:
+ wheel_name = backend.build_editable(
+ tempd,
+ metadata_directory=metadata_directory,
+ )
+ except HookMissing as e:
+ logger.error(
+ "Cannot build editable %s because the build "
+ "backend does not have the %s hook",
+ name,
+ e,
+ )
+ return None
+ except Exception:
+ logger.error("Failed building editable for %s", name)
+ return None
+ return os.path.join(tempd, wheel_name)
diff --git a/pipenv/patched/notpip/_internal/operations/build/wheel_legacy.py b/pipenv/patched/notpip/_internal/operations/build/wheel_legacy.py
index f631c78ae8..ec5b836717 100644
--- a/pipenv/patched/notpip/_internal/operations/build/wheel_legacy.py
+++ b/pipenv/patched/notpip/_internal/operations/build/wheel_legacy.py
@@ -4,59 +4,51 @@
from pipenv.patched.notpip._internal.cli.spinners import open_spinner
from pipenv.patched.notpip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args
-from pipenv.patched.notpip._internal.utils.subprocess import (
- LOG_DIVIDER,
- call_subprocess,
- format_command_args,
-)
+from pipenv.patched.notpip._internal.utils.subprocess import call_subprocess, format_command_args
logger = logging.getLogger(__name__)
def format_command_result(
- command_args, # type: List[str]
- command_output, # type: str
-):
- # type: (...) -> str
+ command_args: List[str],
+ command_output: str,
+) -> str:
"""Format command information for logging."""
command_desc = format_command_args(command_args)
- text = f'Command arguments: {command_desc}\n'
+ text = f"Command arguments: {command_desc}\n"
if not command_output:
- text += 'Command output: None'
+ text += "Command output: None"
elif logger.getEffectiveLevel() > logging.DEBUG:
- text += 'Command output: [use --verbose to show]'
+ text += "Command output: [use --verbose to show]"
else:
- if not command_output.endswith('\n'):
- command_output += '\n'
- text += f'Command output:\n{command_output}{LOG_DIVIDER}'
+ if not command_output.endswith("\n"):
+ command_output += "\n"
+ text += f"Command output:\n{command_output}"
return text
def get_legacy_build_wheel_path(
- names, # type: List[str]
- temp_dir, # type: str
- name, # type: str
- command_args, # type: List[str]
- command_output, # type: str
-):
- # type: (...) -> Optional[str]
+ names: List[str],
+ temp_dir: str,
+ name: str,
+ command_args: List[str],
+ command_output: str,
+) -> Optional[str]:
"""Return the path to the wheel in the temporary build directory."""
# Sort for determinism.
names = sorted(names)
if not names:
- msg = (
- 'Legacy build of wheel for {!r} created no files.\n'
- ).format(name)
+ msg = ("Legacy build of wheel for {!r} created no files.\n").format(name)
msg += format_command_result(command_args, command_output)
logger.warning(msg)
return None
if len(names) > 1:
msg = (
- 'Legacy build of wheel for {!r} created more than one file.\n'
- 'Filenames (choosing first): {}\n'
+ "Legacy build of wheel for {!r} created more than one file.\n"
+ "Filenames (choosing first): {}\n"
).format(name, names)
msg += format_command_result(command_args, command_output)
logger.warning(msg)
@@ -65,14 +57,13 @@ def get_legacy_build_wheel_path(
def build_wheel_legacy(
- name, # type: str
- setup_py_path, # type: str
- source_dir, # type: str
- global_options, # type: List[str]
- build_options, # type: List[str]
- tempd, # type: str
-):
- # type: (...) -> Optional[str]
+ name: str,
+ setup_py_path: str,
+ source_dir: str,
+ global_options: List[str],
+ build_options: List[str],
+ tempd: str,
+) -> Optional[str]:
"""Build one unpacked package using the "legacy" build process.
Returns path to wheel if successfully built. Otherwise, returns None.
@@ -84,19 +75,20 @@ def build_wheel_legacy(
destination_dir=tempd,
)
- spin_message = f'Building wheel for {name} (setup.py)'
+ spin_message = f"Building wheel for {name} (setup.py)"
with open_spinner(spin_message) as spinner:
- logger.debug('Destination directory: %s', tempd)
+ logger.debug("Destination directory: %s", tempd)
try:
output = call_subprocess(
wheel_args,
+ command_desc="python setup.py bdist_wheel",
cwd=source_dir,
spinner=spinner,
)
except Exception:
spinner.finish("error")
- logger.error('Failed building wheel for %s', name)
+ logger.error("Failed building wheel for %s", name)
return None
names = os.listdir(tempd)
diff --git a/pipenv/patched/notpip/_internal/operations/check.py b/pipenv/patched/notpip/_internal/operations/check.py
index a65cb08f46..f26bdc4512 100644
--- a/pipenv/patched/notpip/_internal/operations/check.py
+++ b/pipenv/patched/notpip/_internal/operations/check.py
@@ -2,19 +2,16 @@
"""
import logging
-from typing import TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Set, Tuple
+from typing import Callable, Dict, List, NamedTuple, Optional, Set, Tuple
from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
-from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
+from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pipenv.patched.notpip._internal.distributions import make_distribution_for_install_requirement
from pipenv.patched.notpip._internal.metadata import get_default_environment
from pipenv.patched.notpip._internal.metadata.base import DistributionVersion
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
-if TYPE_CHECKING:
- from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName
-
logger = logging.getLogger(__name__)
@@ -24,12 +21,12 @@ class PackageDetails(NamedTuple):
# Shorthands
-PackageSet = Dict['NormalizedName', PackageDetails]
-Missing = Tuple['NormalizedName', Requirement]
-Conflicting = Tuple['NormalizedName', DistributionVersion, Requirement]
+PackageSet = Dict[NormalizedName, PackageDetails]
+Missing = Tuple[NormalizedName, Requirement]
+Conflicting = Tuple[NormalizedName, DistributionVersion, Requirement]
-MissingDict = Dict['NormalizedName', List[Missing]]
-ConflictingDict = Dict['NormalizedName', List[Conflicting]]
+MissingDict = Dict[NormalizedName, List[Missing]]
+ConflictingDict = Dict[NormalizedName, List[Conflicting]]
CheckResult = Tuple[MissingDict, ConflictingDict]
ConflictDetails = Tuple[PackageSet, CheckResult]
@@ -51,8 +48,9 @@ def create_package_set_from_installed() -> Tuple[PackageSet, bool]:
return package_set, problems
-def check_package_set(package_set, should_ignore=None):
- # type: (PackageSet, Optional[Callable[[str], bool]]) -> CheckResult
+def check_package_set(
+ package_set: PackageSet, should_ignore: Optional[Callable[[str], bool]] = None
+) -> CheckResult:
"""Check if a package set is consistent
If should_ignore is passed, it should be a callable that takes a
@@ -64,8 +62,8 @@ def check_package_set(package_set, should_ignore=None):
for package_name, package_detail in package_set.items():
# Info about dependencies of package_name
- missing_deps = set() # type: Set[Missing]
- conflicting_deps = set() # type: Set[Conflicting]
+ missing_deps: Set[Missing] = set()
+ conflicting_deps: Set[Conflicting] = set()
if should_ignore and should_ignore(package_name):
continue
@@ -95,8 +93,7 @@ def check_package_set(package_set, should_ignore=None):
return missing, conflicting
-def check_install_conflicts(to_install):
- # type: (List[InstallRequirement]) -> ConflictDetails
+def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDetails:
"""For checking if the dependency graph would be consistent after \
installing given requirements
"""
@@ -112,33 +109,32 @@ def check_install_conflicts(to_install):
package_set,
check_package_set(
package_set, should_ignore=lambda name: name not in whitelist
- )
+ ),
)
-def _simulate_installation_of(to_install, package_set):
- # type: (List[InstallRequirement], PackageSet) -> Set[NormalizedName]
- """Computes the version of packages after installing to_install.
- """
+def _simulate_installation_of(
+ to_install: List[InstallRequirement], package_set: PackageSet
+) -> Set[NormalizedName]:
+ """Computes the version of packages after installing to_install."""
# Keep track of packages that were installed
installed = set()
# Modify it as installing requirement_set would (assuming no errors)
for inst_req in to_install:
abstract_dist = make_distribution_for_install_requirement(inst_req)
- dist = abstract_dist.get_pkg_resources_distribution()
-
- assert dist is not None
- name = canonicalize_name(dist.project_name)
- package_set[name] = PackageDetails(dist.parsed_version, dist.requires())
+ dist = abstract_dist.get_metadata_distribution()
+ name = dist.canonical_name
+ package_set[name] = PackageDetails(dist.version, list(dist.iter_dependencies()))
installed.add(name)
return installed
-def _create_whitelist(would_be_installed, package_set):
- # type: (Set[NormalizedName], PackageSet) -> Set[NormalizedName]
+def _create_whitelist(
+ would_be_installed: Set[NormalizedName], package_set: PackageSet
+) -> Set[NormalizedName]:
packages_affected = set(would_be_installed)
for package_name in package_set:
diff --git a/pipenv/patched/notpip/_internal/operations/freeze.py b/pipenv/patched/notpip/_internal/operations/freeze.py
index 103655363d..8c4471beab 100644
--- a/pipenv/patched/notpip/_internal/operations/freeze.py
+++ b/pipenv/patched/notpip/_internal/operations/freeze.py
@@ -1,19 +1,8 @@
import collections
import logging
import os
-from typing import (
- Container,
- Dict,
- Iterable,
- Iterator,
- List,
- NamedTuple,
- Optional,
- Set,
- Union,
-)
+from typing import Container, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set
-from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
from pipenv.patched.notpip._vendor.packaging.version import Version
@@ -30,22 +19,20 @@
class _EditableInfo(NamedTuple):
- requirement: Optional[str]
- editable: bool
+ requirement: str
comments: List[str]
def freeze(
- requirement=None, # type: Optional[List[str]]
- local_only=False, # type: bool
- user_only=False, # type: bool
- paths=None, # type: Optional[List[str]]
- isolated=False, # type: bool
- exclude_editable=False, # type: bool
- skip=() # type: Container[str]
-):
- # type: (...) -> Iterator[str]
- installations = {} # type: Dict[str, FrozenRequirement]
+ requirement: Optional[List[str]] = None,
+ local_only: bool = False,
+ user_only: bool = False,
+ paths: Optional[List[str]] = None,
+ isolated: bool = False,
+ exclude_editable: bool = False,
+ skip: Container[str] = (),
+) -> Iterator[str]:
+ installations: Dict[str, FrozenRequirement] = {}
dists = get_environment(paths).iter_installed_distributions(
local_only=local_only,
@@ -63,42 +50,50 @@ def freeze(
# should only be emitted once, even if the same option is in multiple
# requirements files, so we need to keep track of what has been emitted
# so that we don't emit it again if it's seen again
- emitted_options = set() # type: Set[str]
+ emitted_options: Set[str] = set()
# keep track of which files a requirement is in so that we can
# give an accurate warning if a requirement appears multiple times.
- req_files = collections.defaultdict(list) # type: Dict[str, List[str]]
+ req_files: Dict[str, List[str]] = collections.defaultdict(list)
for req_file_path in requirement:
with open(req_file_path) as req_file:
for line in req_file:
- if (not line.strip() or
- line.strip().startswith('#') or
- line.startswith((
- '-r', '--requirement',
- '-f', '--find-links',
- '-i', '--index-url',
- '--pre',
- '--trusted-host',
- '--process-dependency-links',
- '--extra-index-url',
- '--use-feature'))):
+ if (
+ not line.strip()
+ or line.strip().startswith("#")
+ or line.startswith(
+ (
+ "-r",
+ "--requirement",
+ "-f",
+ "--find-links",
+ "-i",
+ "--index-url",
+ "--pre",
+ "--trusted-host",
+ "--process-dependency-links",
+ "--extra-index-url",
+ "--use-feature",
+ )
+ )
+ ):
line = line.rstrip()
if line not in emitted_options:
emitted_options.add(line)
yield line
continue
- if line.startswith('-e') or line.startswith('--editable'):
- if line.startswith('-e'):
+ if line.startswith("-e") or line.startswith("--editable"):
+ if line.startswith("-e"):
line = line[2:].strip()
else:
- line = line[len('--editable'):].strip().lstrip('=')
+ line = line[len("--editable") :].strip().lstrip("=")
line_req = install_req_from_editable(
line,
isolated=isolated,
)
else:
line_req = install_req_from_line(
- COMMENT_RE.sub('', line).strip(),
+ COMMENT_RE.sub("", line).strip(),
isolated=isolated,
)
@@ -106,15 +101,15 @@ def freeze(
logger.info(
"Skipping line in requirement file [%s] because "
"it's not clear what it would install: %s",
- req_file_path, line.strip(),
+ req_file_path,
+ line.strip(),
)
logger.info(
" (add #egg=PackageName to the URL to avoid"
" this warning)"
)
else:
- line_req_canonical_name = canonicalize_name(
- line_req.name)
+ line_req_canonical_name = canonicalize_name(line_req.name)
if line_req_canonical_name not in installations:
# either it's not installed, or it is installed
# but has been processed already
@@ -123,14 +118,13 @@ def freeze(
"Requirement file [%s] contains %s, but "
"package %r is not installed",
req_file_path,
- COMMENT_RE.sub('', line).strip(),
- line_req.name
+ COMMENT_RE.sub("", line).strip(),
+ line_req.name,
)
else:
req_files[line_req.name].append(req_file_path)
else:
- yield str(installations[
- line_req_canonical_name]).rstrip()
+ yield str(installations[line_req_canonical_name]).rstrip()
del installations[line_req_canonical_name]
req_files[line_req.name].append(req_file_path)
@@ -138,15 +132,14 @@ def freeze(
# single requirements file or in different requirements files).
for name, files in req_files.items():
if len(files) > 1:
- logger.warning("Requirement %s included multiple times [%s]",
- name, ', '.join(sorted(set(files))))
+ logger.warning(
+ "Requirement %s included multiple times [%s]",
+ name,
+ ", ".join(sorted(set(files))),
+ )
- yield(
- '## The following requirements were added by '
- 'pip freeze:'
- )
- for installation in sorted(
- installations.values(), key=lambda x: x.name.lower()):
+ yield ("## The following requirements were added by pip freeze:")
+ for installation in sorted(installations.values(), key=lambda x: x.name.lower()):
if installation.canonical_name not in skip:
yield str(installation).rstrip()
@@ -159,21 +152,12 @@ def _format_as_name_version(dist: BaseDistribution) -> str:
def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
"""
- Compute and return values (req, editable, comments) for use in
+ Compute and return values (req, comments) for use in
FrozenRequirement.from_dist().
"""
- if not dist.editable:
- return _EditableInfo(requirement=None, editable=False, comments=[])
- if dist.location is None:
- display = _format_as_name_version(dist)
- logger.warning("Editable requirement not found on disk: %s", display)
- return _EditableInfo(
- requirement=None,
- editable=True,
- comments=[f"# Editable install not found ({display})"],
- )
-
- location = os.path.normcase(os.path.abspath(dist.location))
+ editable_project_location = dist.editable_project_location
+ assert editable_project_location
+ location = os.path.normcase(os.path.abspath(editable_project_location))
from pipenv.patched.notpip._internal.vcs import RemoteNotFoundError, RemoteNotValidError, vcs
@@ -182,13 +166,13 @@ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
if vcs_backend is None:
display = _format_as_name_version(dist)
logger.debug(
- 'No VCS found for editable requirement "%s" in: %r', display,
+ 'No VCS found for editable requirement "%s" in: %r',
+ display,
location,
)
return _EditableInfo(
requirement=location,
- editable=True,
- comments=[f'# Editable install with no version control ({display})'],
+ comments=[f"# Editable install with no version control ({display})"],
)
vcs_name = type(vcs_backend).__name__
@@ -199,50 +183,47 @@ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
display = _format_as_name_version(dist)
return _EditableInfo(
requirement=location,
- editable=True,
- comments=[f'# Editable {vcs_name} install with no remote ({display})'],
+ comments=[f"# Editable {vcs_name} install with no remote ({display})"],
)
except RemoteNotValidError as ex:
display = _format_as_name_version(dist)
return _EditableInfo(
requirement=location,
- editable=True,
comments=[
f"# Editable {vcs_name} install ({display}) with either a deleted "
f"local remote or invalid URI:",
f"# '{ex.url}'",
],
)
-
except BadCommand:
logger.warning(
- 'cannot determine version of editable source in %s '
- '(%s command not found in path)',
+ "cannot determine version of editable source in %s "
+ "(%s command not found in path)",
location,
vcs_backend.name,
)
- return _EditableInfo(requirement=None, editable=True, comments=[])
-
+ return _EditableInfo(requirement=location, comments=[])
except InstallationError as exc:
- logger.warning(
- "Error when trying to get requirement for VCS system %s, "
- "falling back to uneditable format", exc
- )
+ logger.warning("Error when trying to get requirement for VCS system %s", exc)
else:
- return _EditableInfo(requirement=req, editable=True, comments=[])
+ return _EditableInfo(requirement=req, comments=[])
- logger.warning('Could not determine repository location of %s', location)
+ logger.warning("Could not determine repository location of %s", location)
return _EditableInfo(
- requirement=None,
- editable=False,
- comments=['## !! Could not determine repository location'],
+ requirement=location,
+ comments=["## !! Could not determine repository location"],
)
class FrozenRequirement:
- def __init__(self, name, req, editable, comments=()):
- # type: (str, Union[str, Requirement], bool, Iterable[str]) -> None
+ def __init__(
+ self,
+ name: str,
+ req: str,
+ editable: bool,
+ comments: Iterable[str] = (),
+ ) -> None:
self.name = name
self.canonical_name = canonicalize_name(name)
self.req = req
@@ -251,27 +232,23 @@ def __init__(self, name, req, editable, comments=()):
@classmethod
def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":
- # TODO `get_requirement_info` is taking care of editable requirements.
- # TODO This should be refactored when we will add detection of
- # editable that provide .dist-info metadata.
- req, editable, comments = _get_editable_info(dist)
- if req is None and not editable:
- # if PEP 610 metadata is present, attempt to use it
+ editable = dist.editable
+ if editable:
+ req, comments = _get_editable_info(dist)
+ else:
+ comments = []
direct_url = dist.direct_url
if direct_url:
- req = direct_url_as_pep440_direct_reference(
- direct_url, dist.raw_name
- )
- comments = []
- if req is None:
- # name==version requirement
- req = _format_as_name_version(dist)
+ # if PEP 610 metadata is present, use it
+ req = direct_url_as_pep440_direct_reference(direct_url, dist.raw_name)
+ else:
+ # name==version requirement
+ req = _format_as_name_version(dist)
return cls(dist.raw_name, req, editable, comments=comments)
- def __str__(self):
- # type: () -> str
+ def __str__(self) -> str:
req = self.req
if self.editable:
- req = f'-e {req}'
- return '\n'.join(list(self.comments) + [str(req)]) + '\n'
+ req = f"-e {req}"
+ return "\n".join(list(self.comments) + [str(req)]) + "\n"
diff --git a/pipenv/patched/notpip/_internal/operations/install/editable_legacy.py b/pipenv/patched/notpip/_internal/operations/install/editable_legacy.py
index 2c627458ef..7cd114f70d 100644
--- a/pipenv/patched/notpip/_internal/operations/install/editable_legacy.py
+++ b/pipenv/patched/notpip/_internal/operations/install/editable_legacy.py
@@ -12,22 +12,21 @@
def install_editable(
- install_options, # type: List[str]
- global_options, # type: Sequence[str]
- prefix, # type: Optional[str]
- home, # type: Optional[str]
- use_user_site, # type: bool
- name, # type: str
- setup_py_path, # type: str
- isolated, # type: bool
- build_env, # type: BuildEnvironment
- unpacked_source_directory, # type: str
-):
- # type: (...) -> None
+ install_options: List[str],
+ global_options: Sequence[str],
+ prefix: Optional[str],
+ home: Optional[str],
+ use_user_site: bool,
+ name: str,
+ setup_py_path: str,
+ isolated: bool,
+ build_env: BuildEnvironment,
+ unpacked_source_directory: str,
+) -> None:
"""Install a package in editable mode. Most arguments are pass-through
to setuptools.
"""
- logger.info('Running setup.py develop for %s', name)
+ logger.info("Running setup.py develop for %s", name)
args = make_setuptools_develop_args(
setup_py_path,
@@ -43,5 +42,6 @@ def install_editable(
with build_env:
call_subprocess(
args,
+ command_desc="python setup.py develop",
cwd=unpacked_source_directory,
)
diff --git a/pipenv/patched/notpip/_internal/operations/install/legacy.py b/pipenv/patched/notpip/_internal/operations/install/legacy.py
index e7dcc42f08..74df467db0 100644
--- a/pipenv/patched/notpip/_internal/operations/install/legacy.py
+++ b/pipenv/patched/notpip/_internal/operations/install/legacy.py
@@ -3,14 +3,12 @@
import logging
import os
-import sys
from distutils.util import change_root
from typing import List, Optional, Sequence
from pipenv.patched.notpip._internal.build_env import BuildEnvironment
-from pipenv.patched.notpip._internal.exceptions import InstallationError
+from pipenv.patched.notpip._internal.exceptions import InstallationError, LegacyInstallFailure
from pipenv.patched.notpip._internal.models.scheme import Scheme
-from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import ensure_dir
from pipenv.patched.notpip._internal.utils.setuptools_build import make_setuptools_install_args
from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message
@@ -19,19 +17,12 @@
logger = logging.getLogger(__name__)
-class LegacyInstallFailure(Exception):
- def __init__(self):
- # type: () -> None
- self.parent = sys.exc_info()
-
-
def write_installed_files_from_setuptools_record(
record_lines: List[str],
root: Optional[str],
req_description: str,
) -> None:
- def prepend_root(path):
- # type: (str) -> str
+ def prepend_root(path: str) -> str:
if root is None or not os.path.isabs(path):
return path
else:
@@ -39,7 +30,7 @@ def prepend_root(path):
for line in record_lines:
directory = os.path.dirname(line)
- if directory.endswith('.egg-info'):
+ if directory.endswith(".egg-info"):
egg_info_dir = prepend_root(directory)
break
else:
@@ -55,39 +46,36 @@ def prepend_root(path):
filename = line.strip()
if os.path.isdir(filename):
filename += os.path.sep
- new_lines.append(
- os.path.relpath(prepend_root(filename), egg_info_dir)
- )
+ new_lines.append(os.path.relpath(prepend_root(filename), egg_info_dir))
new_lines.sort()
ensure_dir(egg_info_dir)
- inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt')
- with open(inst_files_path, 'w') as f:
- f.write('\n'.join(new_lines) + '\n')
+ inst_files_path = os.path.join(egg_info_dir, "installed-files.txt")
+ with open(inst_files_path, "w") as f:
+ f.write("\n".join(new_lines) + "\n")
def install(
- install_options, # type: List[str]
- global_options, # type: Sequence[str]
- root, # type: Optional[str]
- home, # type: Optional[str]
- prefix, # type: Optional[str]
- use_user_site, # type: bool
- pycompile, # type: bool
- scheme, # type: Scheme
- setup_py_path, # type: str
- isolated, # type: bool
- req_name, # type: str
- build_env, # type: BuildEnvironment
- unpacked_source_directory, # type: str
- req_description, # type: str
-):
- # type: (...) -> bool
+ install_options: List[str],
+ global_options: Sequence[str],
+ root: Optional[str],
+ home: Optional[str],
+ prefix: Optional[str],
+ use_user_site: bool,
+ pycompile: bool,
+ scheme: Scheme,
+ setup_py_path: str,
+ isolated: bool,
+ req_name: str,
+ build_env: BuildEnvironment,
+ unpacked_source_directory: str,
+ req_description: str,
+) -> bool:
header_dir = scheme.headers
with TempDirectory(kind="record") as temp_dir:
try:
- record_filename = os.path.join(temp_dir.path, 'install-record.txt')
+ record_filename = os.path.join(temp_dir.path, "install-record.txt")
install_args = make_setuptools_install_args(
setup_py_path,
global_options=global_options,
@@ -105,20 +93,20 @@ def install(
runner = runner_with_spinner_message(
f"Running setup.py install for {req_name}"
)
- with indent_log(), build_env:
+ with build_env:
runner(
cmd=install_args,
cwd=unpacked_source_directory,
)
if not os.path.exists(record_filename):
- logger.debug('Record file %s not found', record_filename)
+ logger.debug("Record file %s not found", record_filename)
# Signal to the caller that we didn't install the new package
return False
- except Exception:
+ except Exception as e:
# Signal to the caller that we didn't install the new package
- raise LegacyInstallFailure
+ raise LegacyInstallFailure(package_details=req_name) from e
# At this point, we have successfully installed the requirement.
diff --git a/pipenv/patched/notpip/_internal/operations/install/wheel.py b/pipenv/patched/notpip/_internal/operations/install/wheel.py
index d26ef15164..41a3b32daf 100644
--- a/pipenv/patched/notpip/_internal/operations/install/wheel.py
+++ b/pipenv/patched/notpip/_internal/operations/install/wheel.py
@@ -38,11 +38,14 @@
from pipenv.patched.notpip._vendor.distlib.scripts import ScriptMaker
from pipenv.patched.notpip._vendor.distlib.util import get_export_entry
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
-from pipenv.patched.notpip._vendor.six import ensure_str, ensure_text, reraise
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.locations import get_major_minor_version
-from pipenv.patched.notpip._internal.metadata import BaseDistribution, get_wheel_distribution
+from pipenv.patched.notpip._internal.metadata import (
+ BaseDistribution,
+ FilesystemWheel,
+ get_wheel_distribution,
+)
from pipenv.patched.notpip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, DirectUrl
from pipenv.patched.notpip._internal.models.scheme import SCHEME_KEYS, Scheme
from pipenv.patched.notpip._internal.utils.filesystem import adjacent_tmp_file, replace
@@ -59,62 +62,55 @@
from typing import Protocol
class File(Protocol):
- src_record_path = None # type: RecordPath
- dest_path = None # type: str
- changed = None # type: bool
+ src_record_path: "RecordPath"
+ dest_path: str
+ changed: bool
- def save(self):
- # type: () -> None
+ def save(self) -> None:
pass
logger = logging.getLogger(__name__)
-RecordPath = NewType('RecordPath', str)
+RecordPath = NewType("RecordPath", str)
InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]]
-def rehash(path, blocksize=1 << 20):
- # type: (str, int) -> Tuple[str, str]
+def rehash(path: str, blocksize: int = 1 << 20) -> Tuple[str, str]:
"""Return (encoded_digest, length) for path using hashlib.sha256()"""
h, length = hash_file(path, blocksize)
- digest = 'sha256=' + urlsafe_b64encode(
- h.digest()
- ).decode('latin1').rstrip('=')
+ digest = "sha256=" + urlsafe_b64encode(h.digest()).decode("latin1").rstrip("=")
return (digest, str(length))
-def csv_io_kwargs(mode):
- # type: (str) -> Dict[str, Any]
+def csv_io_kwargs(mode: str) -> Dict[str, Any]:
"""Return keyword arguments to properly open a CSV file
in the given mode.
"""
- return {'mode': mode, 'newline': '', 'encoding': 'utf-8'}
+ return {"mode": mode, "newline": "", "encoding": "utf-8"}
-def fix_script(path):
- # type: (str) -> bool
+def fix_script(path: str) -> bool:
"""Replace #!python with #!/path/to/python
Return True if file was changed.
"""
# XXX RECORD hashes will need to be updated
assert os.path.isfile(path)
- with open(path, 'rb') as script:
+ with open(path, "rb") as script:
firstline = script.readline()
- if not firstline.startswith(b'#!python'):
+ if not firstline.startswith(b"#!python"):
return False
exename = sys.executable.encode(sys.getfilesystemencoding())
- firstline = b'#!' + exename + os.linesep.encode("ascii")
+ firstline = b"#!" + exename + os.linesep.encode("ascii")
rest = script.read()
- with open(path, 'wb') as script:
+ with open(path, "wb") as script:
script.write(firstline)
script.write(rest)
return True
-def wheel_root_is_purelib(metadata):
- # type: (Message) -> bool
+def wheel_root_is_purelib(metadata: Message) -> bool:
return metadata.get("Root-Is-Purelib", "").lower() == "true"
@@ -129,8 +125,7 @@ def get_entrypoints(dist: BaseDistribution) -> Tuple[Dict[str, str], Dict[str, s
return console_scripts, gui_scripts
-def message_about_scripts_not_on_PATH(scripts):
- # type: (Sequence[str]) -> Optional[str]
+def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
"""Determine if any scripts are not on PATH and format a warning.
Returns a warning message if one or more scripts are not on PATH,
otherwise None.
@@ -139,7 +134,7 @@ def message_about_scripts_not_on_PATH(scripts):
return None
# Group scripts by the path they were installed in
- grouped_by_dir = collections.defaultdict(set) # type: Dict[str, Set[str]]
+ grouped_by_dir: Dict[str, Set[str]] = collections.defaultdict(set)
for destfile in scripts:
parent_dir = os.path.dirname(destfile)
script_name = os.path.basename(destfile)
@@ -147,23 +142,24 @@ def message_about_scripts_not_on_PATH(scripts):
# We don't want to warn for directories that are on PATH.
not_warn_dirs = [
- os.path.normcase(i).rstrip(os.sep) for i in
- os.environ.get("PATH", "").split(os.pathsep)
+ os.path.normcase(i).rstrip(os.sep)
+ for i in os.environ.get("PATH", "").split(os.pathsep)
]
# If an executable sits with sys.executable, we don't warn for it.
# This covers the case of venv invocations without activating the venv.
not_warn_dirs.append(os.path.normcase(os.path.dirname(sys.executable)))
- warn_for = {
- parent_dir: scripts for parent_dir, scripts in grouped_by_dir.items()
+ warn_for: Dict[str, Set[str]] = {
+ parent_dir: scripts
+ for parent_dir, scripts in grouped_by_dir.items()
if os.path.normcase(parent_dir) not in not_warn_dirs
- } # type: Dict[str, Set[str]]
+ }
if not warn_for:
return None
# Format a message
msg_lines = []
for parent_dir, dir_scripts in warn_for.items():
- sorted_scripts = sorted(dir_scripts) # type: List[str]
+ sorted_scripts: List[str] = sorted(dir_scripts)
if len(sorted_scripts) == 1:
start_text = "script {} is".format(sorted_scripts[0])
else:
@@ -172,8 +168,9 @@ def message_about_scripts_not_on_PATH(scripts):
)
msg_lines.append(
- "The {} installed in '{}' which is not on PATH."
- .format(start_text, parent_dir)
+ "The {} installed in '{}' which is not on PATH.".format(
+ start_text, parent_dir
+ )
)
last_line_fmt = (
@@ -200,8 +197,9 @@ def message_about_scripts_not_on_PATH(scripts):
return "\n".join(msg_lines)
-def _normalized_outrows(outrows):
- # type: (Iterable[InstalledCSVRow]) -> List[Tuple[str, str, str]]
+def _normalized_outrows(
+ outrows: Iterable[InstalledCSVRow],
+) -> List[Tuple[str, str, str]]:
"""Normalize the given rows of a RECORD file.
Items in each row are converted into str. Rows are then sorted to make
@@ -221,69 +219,60 @@ def _normalized_outrows(outrows):
# For additional background, see--
# https://github.com/pypa/pip/issues/5868
return sorted(
- (ensure_str(record_path, encoding='utf-8'), hash_, str(size))
- for record_path, hash_, size in outrows
+ (record_path, hash_, str(size)) for record_path, hash_, size in outrows
)
-def _record_to_fs_path(record_path):
- # type: (RecordPath) -> str
+def _record_to_fs_path(record_path: RecordPath) -> str:
return record_path
-def _fs_to_record_path(path, relative_to=None):
- # type: (str, Optional[str]) -> RecordPath
+def _fs_to_record_path(path: str, relative_to: Optional[str] = None) -> RecordPath:
if relative_to is not None:
# On Windows, do not handle relative paths if they belong to different
# logical disks
- if os.path.splitdrive(path)[0].lower() == \
- os.path.splitdrive(relative_to)[0].lower():
+ if (
+ os.path.splitdrive(path)[0].lower()
+ == os.path.splitdrive(relative_to)[0].lower()
+ ):
path = os.path.relpath(path, relative_to)
- path = path.replace(os.path.sep, '/')
- return cast('RecordPath', path)
-
-
-def _parse_record_path(record_column):
- # type: (str) -> RecordPath
- p = ensure_text(record_column, encoding='utf-8')
- return cast('RecordPath', p)
+ path = path.replace(os.path.sep, "/")
+ return cast("RecordPath", path)
def get_csv_rows_for_installed(
- old_csv_rows, # type: List[List[str]]
- installed, # type: Dict[RecordPath, RecordPath]
- changed, # type: Set[RecordPath]
- generated, # type: List[str]
- lib_dir, # type: str
-):
- # type: (...) -> List[InstalledCSVRow]
+ old_csv_rows: List[List[str]],
+ installed: Dict[RecordPath, RecordPath],
+ changed: Set[RecordPath],
+ generated: List[str],
+ lib_dir: str,
+) -> List[InstalledCSVRow]:
"""
:param installed: A map from archive RECORD path to installation RECORD
path.
"""
- installed_rows = [] # type: List[InstalledCSVRow]
+ installed_rows: List[InstalledCSVRow] = []
for row in old_csv_rows:
if len(row) > 3:
- logger.warning('RECORD line has more than three elements: %s', row)
- old_record_path = _parse_record_path(row[0])
+ logger.warning("RECORD line has more than three elements: %s", row)
+ old_record_path = cast("RecordPath", row[0])
new_record_path = installed.pop(old_record_path, old_record_path)
if new_record_path in changed:
digest, length = rehash(_record_to_fs_path(new_record_path))
else:
- digest = row[1] if len(row) > 1 else ''
- length = row[2] if len(row) > 2 else ''
+ digest = row[1] if len(row) > 1 else ""
+ length = row[2] if len(row) > 2 else ""
installed_rows.append((new_record_path, digest, length))
for f in generated:
path = _fs_to_record_path(f, lib_dir)
digest, length = rehash(f)
installed_rows.append((path, digest, length))
for installed_record_path in installed.values():
- installed_rows.append((installed_record_path, '', ''))
+ installed_rows.append((installed_record_path, "", ""))
return installed_rows
-def get_console_script_specs(console):
- # type: (Dict[str, str]) -> List[str]
+def get_console_script_specs(console: Dict[str, str]) -> List[str]:
"""
Given the mapping from entrypoint name to callable, return the relevant
console script specs.
@@ -326,62 +315,57 @@ def get_console_script_specs(console):
# DEFAULT
# - The default behavior is to install pip, pipX, pipX.Y, easy_install
# and easy_install-X.Y.
- pip_script = console.pop('pip', None)
+ pip_script = console.pop("pip", None)
if pip_script:
if "ENSUREPIP_OPTIONS" not in os.environ:
- scripts_to_generate.append('pip = ' + pip_script)
+ scripts_to_generate.append("pip = " + pip_script)
if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall":
scripts_to_generate.append(
- 'pip{} = {}'.format(sys.version_info[0], pip_script)
+ "pip{} = {}".format(sys.version_info[0], pip_script)
)
- scripts_to_generate.append(
- f'pip{get_major_minor_version()} = {pip_script}'
- )
+ scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}")
# Delete any other versioned pip entry points
- pip_ep = [k for k in console if re.match(r'pip(\d(\.\d)?)?$', k)]
+ pip_ep = [k for k in console if re.match(r"pip(\d(\.\d)?)?$", k)]
for k in pip_ep:
del console[k]
- easy_install_script = console.pop('easy_install', None)
+ easy_install_script = console.pop("easy_install", None)
if easy_install_script:
if "ENSUREPIP_OPTIONS" not in os.environ:
- scripts_to_generate.append(
- 'easy_install = ' + easy_install_script
- )
+ scripts_to_generate.append("easy_install = " + easy_install_script)
scripts_to_generate.append(
- 'easy_install-{} = {}'.format(
+ "easy_install-{} = {}".format(
get_major_minor_version(), easy_install_script
)
)
# Delete any other versioned easy_install entry points
easy_install_ep = [
- k for k in console if re.match(r'easy_install(-\d\.\d)?$', k)
+ k for k in console if re.match(r"easy_install(-\d\.\d)?$", k)
]
for k in easy_install_ep:
del console[k]
# Generate the console entry points specified in the wheel
- scripts_to_generate.extend(starmap('{} = {}'.format, console.items()))
+ scripts_to_generate.extend(starmap("{} = {}".format, console.items()))
return scripts_to_generate
class ZipBackedFile:
- def __init__(self, src_record_path, dest_path, zip_file):
- # type: (RecordPath, str, ZipFile) -> None
+ def __init__(
+ self, src_record_path: RecordPath, dest_path: str, zip_file: ZipFile
+ ) -> None:
self.src_record_path = src_record_path
self.dest_path = dest_path
self._zip_file = zip_file
self.changed = False
- def _getinfo(self):
- # type: () -> ZipInfo
+ def _getinfo(self) -> ZipInfo:
return self._zip_file.getinfo(self.src_record_path)
- def save(self):
- # type: () -> None
+ def save(self) -> None:
# directory creation is lazy and after file filtering
# to ensure we don't install empty dirs; empty dirs can't be
# uninstalled.
@@ -410,22 +394,19 @@ def save(self):
class ScriptFile:
- def __init__(self, file):
- # type: (File) -> None
+ def __init__(self, file: "File") -> None:
self._file = file
self.src_record_path = self._file.src_record_path
self.dest_path = self._file.dest_path
self.changed = False
- def save(self):
- # type: () -> None
+ def save(self) -> None:
self._file.save()
self.changed = fix_script(self.dest_path)
class MissingCallableSuffix(InstallationError):
- def __init__(self, entry_point):
- # type: (str) -> None
+ def __init__(self, entry_point: str) -> None:
super().__init__(
"Invalid script entry point: {} - A callable "
"suffix is required. Cf https://packaging.python.org/"
@@ -434,31 +415,28 @@ def __init__(self, entry_point):
)
-def _raise_for_invalid_entrypoint(specification):
- # type: (str) -> None
+def _raise_for_invalid_entrypoint(specification: str) -> None:
entry = get_export_entry(specification)
if entry is not None and entry.suffix is None:
raise MissingCallableSuffix(str(entry))
class PipScriptMaker(ScriptMaker):
- def make(self, specification, options=None):
- # type: (str, Dict[str, Any]) -> List[str]
+ def make(self, specification: str, options: Dict[str, Any] = None) -> List[str]:
_raise_for_invalid_entrypoint(specification)
return super().make(specification, options)
def _install_wheel(
- name, # type: str
- wheel_zip, # type: ZipFile
- wheel_path, # type: str
- scheme, # type: Scheme
- pycompile=True, # type: bool
- warn_script_location=True, # type: bool
- direct_url=None, # type: Optional[DirectUrl]
- requested=False, # type: bool
-):
- # type: (...) -> None
+ name: str,
+ wheel_zip: ZipFile,
+ wheel_path: str,
+ scheme: Scheme,
+ pycompile: bool = True,
+ warn_script_location: bool = True,
+ direct_url: Optional[DirectUrl] = None,
+ requested: bool = False,
+) -> None:
"""Install a wheel.
:param name: Name of the project to install
@@ -485,33 +463,23 @@ def _install_wheel(
# installed = files copied from the wheel to the destination
# changed = files changed while installing (scripts #! line typically)
# generated = files newly generated during the install (script wrappers)
- installed = {} # type: Dict[RecordPath, RecordPath]
- changed = set() # type: Set[RecordPath]
- generated = [] # type: List[str]
+ installed: Dict[RecordPath, RecordPath] = {}
+ changed: Set[RecordPath] = set()
+ generated: List[str] = []
- def record_installed(srcfile, destfile, modified=False):
- # type: (RecordPath, str, bool) -> None
+ def record_installed(
+ srcfile: RecordPath, destfile: str, modified: bool = False
+ ) -> None:
"""Map archive RECORD paths to installation RECORD paths."""
newpath = _fs_to_record_path(destfile, lib_dir)
installed[srcfile] = newpath
if modified:
changed.add(_fs_to_record_path(destfile))
- def all_paths():
- # type: () -> Iterable[RecordPath]
- names = wheel_zip.namelist()
- # If a flag is set, names may be unicode in Python 2. We convert to
- # text explicitly so these are valid for lookup in RECORD.
- decoded_names = map(ensure_text, names)
- for name in decoded_names:
- yield cast("RecordPath", name)
-
- def is_dir_path(path):
- # type: (RecordPath) -> bool
+ def is_dir_path(path: RecordPath) -> bool:
return path.endswith("/")
- def assert_no_path_traversal(dest_dir_path, target_path):
- # type: (str, str) -> None
+ def assert_no_path_traversal(dest_dir_path: str, target_path: str) -> None:
if not is_within_directory(dest_dir_path, target_path):
message = (
"The wheel {!r} has a file {!r} trying to install"
@@ -521,10 +489,10 @@ def assert_no_path_traversal(dest_dir_path, target_path):
message.format(wheel_path, target_path, dest_dir_path)
)
- def root_scheme_file_maker(zip_file, dest):
- # type: (ZipFile, str) -> Callable[[RecordPath], File]
- def make_root_scheme_file(record_path):
- # type: (RecordPath) -> File
+ def root_scheme_file_maker(
+ zip_file: ZipFile, dest: str
+ ) -> Callable[[RecordPath], "File"]:
+ def make_root_scheme_file(record_path: RecordPath) -> "File":
normed_path = os.path.normpath(record_path)
dest_path = os.path.join(dest, normed_path)
assert_no_path_traversal(dest, dest_path)
@@ -532,17 +500,12 @@ def make_root_scheme_file(record_path):
return make_root_scheme_file
- def data_scheme_file_maker(zip_file, scheme):
- # type: (ZipFile, Scheme) -> Callable[[RecordPath], File]
- scheme_paths = {}
- for key in SCHEME_KEYS:
- encoded_key = ensure_text(key)
- scheme_paths[encoded_key] = ensure_text(
- getattr(scheme, key), encoding=sys.getfilesystemencoding()
- )
+ def data_scheme_file_maker(
+ zip_file: ZipFile, scheme: Scheme
+ ) -> Callable[[RecordPath], "File"]:
+ scheme_paths = {key: getattr(scheme, key) for key in SCHEME_KEYS}
- def make_data_scheme_file(record_path):
- # type: (RecordPath) -> File
+ def make_data_scheme_file(record_path: RecordPath) -> "File":
normed_path = os.path.normpath(record_path)
try:
_, scheme_key, dest_subpath = normed_path.split(os.path.sep, 2)
@@ -561,9 +524,7 @@ def make_data_scheme_file(record_path):
"Unknown scheme key used in {}: {} (for file {!r}). .data"
" directory contents should be in subdirectories named"
" with a valid scheme key ({})"
- ).format(
- wheel_path, scheme_key, record_path, valid_scheme_keys
- )
+ ).format(wheel_path, scheme_key, record_path, valid_scheme_keys)
raise InstallationError(message)
dest_path = os.path.join(scheme_path, dest_subpath)
@@ -572,30 +533,19 @@ def make_data_scheme_file(record_path):
return make_data_scheme_file
- def is_data_scheme_path(path):
- # type: (RecordPath) -> bool
+ def is_data_scheme_path(path: RecordPath) -> bool:
return path.split("/", 1)[0].endswith(".data")
- paths = all_paths()
+ paths = cast(List[RecordPath], wheel_zip.namelist())
file_paths = filterfalse(is_dir_path, paths)
- root_scheme_paths, data_scheme_paths = partition(
- is_data_scheme_path, file_paths
- )
+ root_scheme_paths, data_scheme_paths = partition(is_data_scheme_path, file_paths)
- make_root_scheme_file = root_scheme_file_maker(
- wheel_zip,
- ensure_text(lib_dir, encoding=sys.getfilesystemencoding()),
- )
- files = map(make_root_scheme_file, root_scheme_paths)
+ make_root_scheme_file = root_scheme_file_maker(wheel_zip, lib_dir)
+ files: Iterator[File] = map(make_root_scheme_file, root_scheme_paths)
- def is_script_scheme_path(path):
- # type: (RecordPath) -> bool
+ def is_script_scheme_path(path: RecordPath) -> bool:
parts = path.split("/", 2)
- return (
- len(parts) > 2 and
- parts[0].endswith(".data") and
- parts[1] == "scripts"
- )
+ return len(parts) > 2 and parts[0].endswith(".data") and parts[1] == "scripts"
other_scheme_paths, script_scheme_paths = partition(
is_script_scheme_path, data_scheme_paths
@@ -606,30 +556,32 @@ def is_script_scheme_path(path):
files = chain(files, other_scheme_files)
# Get the defined entry points
- distribution = get_wheel_distribution(wheel_path, canonicalize_name(name))
+ distribution = get_wheel_distribution(
+ FilesystemWheel(wheel_path),
+ canonicalize_name(name),
+ )
console, gui = get_entrypoints(distribution)
- def is_entrypoint_wrapper(file):
- # type: (File) -> bool
+ def is_entrypoint_wrapper(file: "File") -> bool:
# EP, EP.exe and EP-script.py are scripts generated for
# entry point EP by setuptools
path = file.dest_path
name = os.path.basename(path)
- if name.lower().endswith('.exe'):
+ if name.lower().endswith(".exe"):
matchname = name[:-4]
- elif name.lower().endswith('-script.py'):
+ elif name.lower().endswith("-script.py"):
matchname = name[:-10]
elif name.lower().endswith(".pya"):
matchname = name[:-4]
else:
matchname = name
# Ignore setuptools-generated scripts
- return (matchname in console or matchname in gui)
+ return matchname in console or matchname in gui
- script_scheme_files = map(make_data_scheme_file, script_scheme_paths)
- script_scheme_files = filterfalse(
- is_entrypoint_wrapper, script_scheme_files
+ script_scheme_files: Iterator[File] = map(
+ make_data_scheme_file, script_scheme_paths
)
+ script_scheme_files = filterfalse(is_entrypoint_wrapper, script_scheme_files)
script_scheme_files = map(ScriptFile, script_scheme_files)
files = chain(files, script_scheme_files)
@@ -637,8 +589,7 @@ def is_entrypoint_wrapper(file):
file.save()
record_installed(file.src_record_path, file.dest_path, file.changed)
- def pyc_source_file_paths():
- # type: () -> Iterator[str]
+ def pyc_source_file_paths() -> Iterator[str]:
# We de-duplicate installation paths, since there can be overlap (e.g.
# file in .data maps to same location as file in wheel root).
# Sorting installation paths makes it easier to reproduce and debug
@@ -647,30 +598,21 @@ def pyc_source_file_paths():
full_installed_path = os.path.join(lib_dir, installed_path)
if not os.path.isfile(full_installed_path):
continue
- if not full_installed_path.endswith('.py'):
+ if not full_installed_path.endswith(".py"):
continue
yield full_installed_path
- def pyc_output_path(path):
- # type: (str) -> str
- """Return the path the pyc file would have been written to.
- """
+ def pyc_output_path(path: str) -> str:
+ """Return the path the pyc file would have been written to."""
return importlib.util.cache_from_source(path)
# Compile all of the pyc files for the installed files
if pycompile:
with captured_stdout() as stdout:
with warnings.catch_warnings():
- warnings.filterwarnings('ignore')
+ warnings.filterwarnings("ignore")
for path in pyc_source_file_paths():
- # Python 2's `compileall.compile_file` requires a str in
- # error cases, so we must convert to the native type.
- path_arg = ensure_str(
- path, encoding=sys.getfilesystemencoding()
- )
- success = compileall.compile_file(
- path_arg, force=True, quiet=True
- )
+ success = compileall.compile_file(path, force=True, quiet=True)
if success:
pyc_path = pyc_output_path(path)
assert os.path.exists(pyc_path)
@@ -689,7 +631,7 @@ def pyc_output_path(path):
# Ensure we don't generate any variants for scripts because this is almost
# never what somebody wants.
# See https://bitbucket.org/pypa/distlib/issue/35/
- maker.variants = {''}
+ maker.variants = {""}
# This is required because otherwise distlib creates scripts that are not
# executable.
@@ -699,14 +641,12 @@ def pyc_output_path(path):
# Generate the console and GUI entry points specified in the wheel
scripts_to_generate = get_console_script_specs(console)
- gui_scripts_to_generate = list(starmap('{} = {}'.format, gui.items()))
+ gui_scripts_to_generate = list(starmap("{} = {}".format, gui.items()))
generated_console_scripts = maker.make_multiple(scripts_to_generate)
generated.extend(generated_console_scripts)
- generated.extend(
- maker.make_multiple(gui_scripts_to_generate, {'gui': True})
- )
+ generated.extend(maker.make_multiple(gui_scripts_to_generate, {"gui": True}))
if warn_script_location:
msg = message_about_scripts_not_on_PATH(generated_console_scripts)
@@ -716,8 +656,7 @@ def pyc_output_path(path):
generated_file_mode = 0o666 & ~current_umask()
@contextlib.contextmanager
- def _generate_file(path, **kwargs):
- # type: (str, **Any) -> Iterator[BinaryIO]
+ def _generate_file(path: str, **kwargs: Any) -> Iterator[BinaryIO]:
with adjacent_tmp_file(path, **kwargs) as f:
yield f
os.chmod(f.name, generated_file_mode)
@@ -726,9 +665,9 @@ def _generate_file(path, **kwargs):
dest_info_dir = os.path.join(lib_dir, info_dir)
# Record pip as the installer
- installer_path = os.path.join(dest_info_dir, 'INSTALLER')
+ installer_path = os.path.join(dest_info_dir, "INSTALLER")
with _generate_file(installer_path) as installer_file:
- installer_file.write(b'pip\n')
+ installer_file.write(b"pip\n")
generated.append(installer_path)
# Record the PEP 610 direct URL reference
@@ -740,12 +679,12 @@ def _generate_file(path, **kwargs):
# Record the REQUESTED file
if requested:
- requested_path = os.path.join(dest_info_dir, 'REQUESTED')
+ requested_path = os.path.join(dest_info_dir, "REQUESTED")
with open(requested_path, "wb"):
pass
generated.append(requested_path)
- record_text = distribution.read_text('RECORD')
+ record_text = distribution.read_text("RECORD")
record_rows = list(csv.reader(record_text.splitlines()))
rows = get_csv_rows_for_installed(
@@ -753,42 +692,38 @@ def _generate_file(path, **kwargs):
installed=installed,
changed=changed,
generated=generated,
- lib_dir=lib_dir)
+ lib_dir=lib_dir,
+ )
# Record details of all files installed
- record_path = os.path.join(dest_info_dir, 'RECORD')
+ record_path = os.path.join(dest_info_dir, "RECORD")
- with _generate_file(record_path, **csv_io_kwargs('w')) as record_file:
- # The type mypy infers for record_file is different for Python 3
- # (typing.IO[Any]) and Python 2 (typing.BinaryIO). We explicitly
- # cast to typing.IO[str] as a workaround.
- writer = csv.writer(cast('IO[str]', record_file))
+ with _generate_file(record_path, **csv_io_kwargs("w")) as record_file:
+ # Explicitly cast to typing.IO[str] as a workaround for the mypy error:
+ # "writer" has incompatible type "BinaryIO"; expected "_Writer"
+ writer = csv.writer(cast("IO[str]", record_file))
writer.writerows(_normalized_outrows(rows))
@contextlib.contextmanager
-def req_error_context(req_description):
- # type: (str) -> Iterator[None]
+def req_error_context(req_description: str) -> Iterator[None]:
try:
yield
except InstallationError as e:
message = "For req: {}. {}".format(req_description, e.args[0])
- reraise(
- InstallationError, InstallationError(message), sys.exc_info()[2]
- )
+ raise InstallationError(message) from e
def install_wheel(
- name, # type: str
- wheel_path, # type: str
- scheme, # type: Scheme
- req_description, # type: str
- pycompile=True, # type: bool
- warn_script_location=True, # type: bool
- direct_url=None, # type: Optional[DirectUrl]
- requested=False, # type: bool
-):
- # type: (...) -> None
+ name: str,
+ wheel_path: str,
+ scheme: Scheme,
+ req_description: str,
+ pycompile: bool = True,
+ warn_script_location: bool = True,
+ direct_url: Optional[DirectUrl] = None,
+ requested: bool = False,
+) -> None:
with ZipFile(wheel_path, allowZip64=True) as z:
with req_error_context(req_description):
_install_wheel(
diff --git a/pipenv/patched/notpip/_internal/operations/prepare.py b/pipenv/patched/notpip/_internal/operations/prepare.py
index 0c256d6b05..58a6f3f2d6 100644
--- a/pipenv/patched/notpip/_internal/operations/prepare.py
+++ b/pipenv/patched/notpip/_internal/operations/prepare.py
@@ -8,10 +8,9 @@
import mimetypes
import os
import shutil
-from typing import Dict, Iterable, List, Optional, Tuple
+from typing import Dict, Iterable, List, Optional
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
from pipenv.patched.notpip._internal.distributions import make_distribution_for_install_requirement
from pipenv.patched.notpip._internal.distributions.installed import InstalledDistribution
@@ -25,6 +24,7 @@
VcsHashUnsupported,
)
from pipenv.patched.notpip._internal.index.package_finder import PackageFinder
+from pipenv.patched.notpip._internal.metadata import BaseDistribution
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.models.wheel import Wheel
from pipenv.patched.notpip._internal.network.download import BatchDownloader, Downloader
@@ -35,7 +35,6 @@
from pipenv.patched.notpip._internal.network.session import PipSession
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.req.req_tracker import RequirementTracker
-from pipenv.patched.notpip._internal.utils.deprecation import deprecated
from pipenv.patched.notpip._internal.utils.filesystem import copy2_fixed
from pipenv.patched.notpip._internal.utils.hashes import Hashes, MissingHashes
from pipenv.patched.notpip._internal.utils.logging import indent_log
@@ -48,30 +47,26 @@
def _get_prepared_distribution(
- req, # type: InstallRequirement
- req_tracker, # type: RequirementTracker
- finder, # type: PackageFinder
- build_isolation, # type: bool
-):
- # type: (...) -> Distribution
+ req: InstallRequirement,
+ req_tracker: RequirementTracker,
+ finder: PackageFinder,
+ build_isolation: bool,
+) -> BaseDistribution:
"""Prepare a distribution for installation."""
abstract_dist = make_distribution_for_install_requirement(req)
with req_tracker.track(req):
abstract_dist.prepare_distribution_metadata(finder, build_isolation)
- return abstract_dist.get_pkg_resources_distribution()
+ return abstract_dist.get_metadata_distribution()
-def unpack_vcs_link(link, location):
- # type: (Link, str) -> None
+def unpack_vcs_link(link: Link, location: str, verbosity: int) -> None:
vcs_backend = vcs.get_backend_for_scheme(link.scheme)
assert vcs_backend is not None
- vcs_backend.unpack(location, url=hide_url(link.url))
+ vcs_backend.unpack(location, url=hide_url(link.url), verbosity=verbosity)
class File:
-
- def __init__(self, path, content_type):
- # type: (str, Optional[str]) -> None
+ def __init__(self, path: str, content_type: Optional[str]) -> None:
self.path = path
if content_type is None:
self.content_type = mimetypes.guess_type(path)[0]
@@ -80,19 +75,16 @@ def __init__(self, path, content_type):
def get_http_url(
- link, # type: Link
- download, # type: Downloader
- download_dir=None, # type: Optional[str]
- hashes=None, # type: Optional[Hashes]
-):
- # type: (...) -> File
+ link: Link,
+ download: Downloader,
+ download_dir: Optional[str] = None,
+ hashes: Optional[Hashes] = None,
+) -> File:
temp_dir = TempDirectory(kind="unpack", globally_managed=True)
# If a download dir is specified, is the file already downloaded there?
already_downloaded_path = None
if download_dir:
- already_downloaded_path = _check_download_dir(
- link, download_dir, hashes
- )
+ already_downloaded_path = _check_download_dir(link, download_dir, hashes)
if already_downloaded_path:
from_path = already_downloaded_path
@@ -106,8 +98,7 @@ def get_http_url(
return File(from_path, content_type)
-def _copy2_ignoring_special_files(src, dest):
- # type: (str, str) -> None
+def _copy2_ignoring_special_files(src: str, dest: str) -> None:
"""Copying special files is not supported, but as a convenience to users
we skip errors copying them. This supports tools that may create e.g.
socket files in the project source directory.
@@ -127,21 +118,19 @@ def _copy2_ignoring_special_files(src, dest):
)
-def _copy_source_tree(source, target):
- # type: (str, str) -> None
+def _copy_source_tree(source: str, target: str) -> None:
target_abspath = os.path.abspath(target)
target_basename = os.path.basename(target_abspath)
target_dirname = os.path.dirname(target_abspath)
- def ignore(d, names):
- # type: (str, List[str]) -> List[str]
- skipped = [] # type: List[str]
+ def ignore(d: str, names: List[str]) -> List[str]:
+ skipped: List[str] = []
if d == source:
# Pulling in those directories can potentially be very slow,
# exclude the following directories if they appear in the top
# level dir (and only it).
# See discussion at https://github.com/pypa/pip/pull/6770
- skipped += ['.tox', '.nox']
+ skipped += [".tox", ".nox"]
if os.path.abspath(d) == target_dirname:
# Prevent an infinite recursion if the target is in source.
# This can happen when TMPDIR is set to ${PWD}/...
@@ -159,19 +148,13 @@ def ignore(d, names):
def get_file_url(
- link, # type: Link
- download_dir=None, # type: Optional[str]
- hashes=None # type: Optional[Hashes]
-):
- # type: (...) -> File
- """Get file and optionally check its hash.
- """
+ link: Link, download_dir: Optional[str] = None, hashes: Optional[Hashes] = None
+) -> File:
+ """Get file and optionally check its hash."""
# If a download dir is specified, is the file already there and valid?
already_downloaded_path = None
if download_dir:
- already_downloaded_path = _check_download_dir(
- link, download_dir, hashes
- )
+ already_downloaded_path = _check_download_dir(link, download_dir, hashes)
if already_downloaded_path:
from_path = already_downloaded_path
@@ -189,13 +172,13 @@ def get_file_url(
def unpack_url(
- link, # type: Link
- location, # type: str
- download, # type: Downloader
- download_dir=None, # type: Optional[str]
- hashes=None, # type: Optional[Hashes]
-):
- # type: (...) -> Optional[File]
+ link: Link,
+ location: str,
+ download: Downloader,
+ verbosity: int,
+ download_dir: Optional[str] = None,
+ hashes: Optional[Hashes] = None,
+) -> Optional[File]:
"""Unpack link into location, downloading if required.
:param hashes: A Hashes object, one of whose embedded hashes must match,
@@ -205,7 +188,7 @@ def unpack_url(
"""
# non-editable vcs urls
if link.is_vcs:
- unpack_vcs_link(link, location)
+ unpack_vcs_link(link, location, verbosity=verbosity)
return None
# Once out-of-tree-builds are no longer supported, could potentially
@@ -214,17 +197,9 @@ def unpack_url(
#
# As further cleanup, _copy_source_tree and accompanying tests can
# be removed.
+ #
+ # TODO when use-deprecated=out-of-tree-build is removed
if link.is_existing_dir():
- deprecated(
- "A future pip version will change local packages to be built "
- "in-place without first copying to a temporary directory. "
- "We recommend you use --use-feature=in-tree-build to test "
- "your packages with this new behavior before it becomes the "
- "default.\n",
- replacement=None,
- gone_in="21.3",
- issue=7555
- )
if os.path.isdir(location):
rmtree(location)
_copy_source_tree(link.file_path, location)
@@ -251,10 +226,11 @@ def unpack_url(
return file
-def _check_download_dir(link, download_dir, hashes):
- # type: (Link, str, Optional[Hashes]) -> Optional[str]
- """ Check download_dir for previously downloaded file with correct hash
- If a correct file is found return its path else None
+def _check_download_dir(
+ link: Link, download_dir: str, hashes: Optional[Hashes]
+) -> Optional[str]:
+ """Check download_dir for previously downloaded file with correct hash
+ If a correct file is found return its path else None
"""
download_path = os.path.join(download_dir, link.filename)
@@ -262,15 +238,14 @@ def _check_download_dir(link, download_dir, hashes):
return None
# If already downloaded, does its hash match?
- logger.info('File was already downloaded %s', download_path)
+ logger.info("File was already downloaded %s", download_path)
if hashes:
try:
hashes.check_against_path(download_path)
except HashMismatch:
logger.warning(
- 'Previously-downloaded file %s has bad hash. '
- 'Re-downloading.',
- download_path
+ "Previously-downloaded file %s has bad hash. Re-downloading.",
+ download_path,
)
os.unlink(download_path)
return None
@@ -278,25 +253,24 @@ def _check_download_dir(link, download_dir, hashes):
class RequirementPreparer:
- """Prepares a Requirement
- """
+ """Prepares a Requirement"""
def __init__(
self,
- build_dir, # type: str
- download_dir, # type: Optional[str]
- src_dir, # type: str
- build_isolation, # type: bool
- req_tracker, # type: RequirementTracker
- session, # type: PipSession
- progress_bar, # type: str
- finder, # type: PackageFinder
- require_hashes, # type: bool
- use_user_site, # type: bool
- lazy_wheel, # type: bool
- in_tree_build, # type: bool
- ):
- # type: (...) -> None
+ build_dir: str,
+ download_dir: Optional[str],
+ src_dir: str,
+ build_isolation: bool,
+ req_tracker: RequirementTracker,
+ session: PipSession,
+ progress_bar: str,
+ finder: PackageFinder,
+ require_hashes: bool,
+ use_user_site: bool,
+ lazy_wheel: bool,
+ verbosity: int,
+ in_tree_build: bool,
+ ) -> None:
super().__init__()
self.src_dir = src_dir
@@ -323,17 +297,19 @@ def __init__(
# Should wheels be downloaded lazily?
self.use_lazy_wheel = lazy_wheel
+ # How verbose should underlying tooling be?
+ self.verbosity = verbosity
+
# Should in-tree builds be used for local paths?
self.in_tree_build = in_tree_build
- # Memoized downloaded files, as mapping of url: (path, mime type)
- self._downloaded = {} # type: Dict[str, Tuple[str, str]]
+ # Memoized downloaded files, as mapping of url: path.
+ self._downloaded: Dict[str, str] = {}
# Previous "header" printed for a link-based InstallRequirement
self._previous_requirement_header = ("", "")
- def _log_preparing_link(self, req):
- # type: (InstallRequirement) -> None
+ def _log_preparing_link(self, req: InstallRequirement) -> None:
"""Provide context for the requirement being prepared."""
if req.link.is_file and not req.original_link_is_in_wheel_cache:
message = "Processing %s"
@@ -350,8 +326,9 @@ def _log_preparing_link(self, req):
with indent_log():
logger.info("Using cached %s", req.link.filename)
- def _ensure_link_req_src_dir(self, req, parallel_builds):
- # type: (InstallRequirement, bool) -> None
+ def _ensure_link_req_src_dir(
+ self, req: InstallRequirement, parallel_builds: bool
+ ) -> None:
"""Ensure source_dir of a linked InstallRequirement."""
# Since source_dir is only set for editable requirements.
if req.link.is_wheel:
@@ -376,6 +353,7 @@ def _ensure_link_req_src_dir(self, req, parallel_builds):
# installation.
# FIXME: this won't upgrade when there's an existing
# package unpacked in `req.source_dir`
+ # TODO: this check is now probably dead code
if is_installable_dir(req.source_dir):
raise PreviousBuildDirError(
"pip can't proceed with requirements '{}' due to a"
@@ -385,8 +363,7 @@ def _ensure_link_req_src_dir(self, req, parallel_builds):
"Please delete it and try again.".format(req, req.source_dir)
)
- def _get_linked_req_hashes(self, req):
- # type: (InstallRequirement) -> Hashes
+ def _get_linked_req_hashes(self, req: InstallRequirement) -> Hashes:
# By the time this is called, the requirement's link should have
# been checked so we can tell what kind of requirements req is
# and raise some more informative errors than otherwise.
@@ -418,18 +395,19 @@ def _get_linked_req_hashes(self, req):
# showing the user what the hash should be.
return req.hashes(trust_internet=False) or MissingHashes()
- def _fetch_metadata_using_lazy_wheel(self, link):
- # type: (Link) -> Optional[Distribution]
+ def _fetch_metadata_using_lazy_wheel(
+ self,
+ link: Link,
+ ) -> Optional[BaseDistribution]:
"""Fetch metadata using lazy wheel, if possible."""
if not self.use_lazy_wheel:
return None
if self.require_hashes:
- logger.debug('Lazy wheel is not used as hash checking is required')
+ logger.debug("Lazy wheel is not used as hash checking is required")
return None
if link.is_file or not link.is_wheel:
logger.debug(
- 'Lazy wheel is not used as '
- '%r does not points to a remote wheel',
+ "Lazy wheel is not used as %r does not points to a remote wheel",
link,
)
return None
@@ -437,22 +415,22 @@ def _fetch_metadata_using_lazy_wheel(self, link):
wheel = Wheel(link.filename)
name = canonicalize_name(wheel.name)
logger.info(
- 'Obtaining dependency information from %s %s',
- name, wheel.version,
+ "Obtaining dependency information from %s %s",
+ name,
+ wheel.version,
)
- url = link.url.split('#', 1)[0]
+ url = link.url.split("#", 1)[0]
try:
return dist_from_wheel_url(name, url, self._session)
except HTTPRangeRequestUnsupported:
- logger.debug('%s does not support range requests', url)
+ logger.debug("%s does not support range requests", url)
return None
def _complete_partial_requirements(
self,
- partially_downloaded_reqs, # type: Iterable[InstallRequirement]
- parallel_builds=False, # type: bool
- ):
- # type: (...) -> None
+ partially_downloaded_reqs: Iterable[InstallRequirement],
+ parallel_builds: bool = False,
+ ) -> None:
"""Download any requirements which were only fetched by metadata."""
# Download to a temporary directory. These will be copied over as
# needed for downstream 'download', 'wheel', and 'install' commands.
@@ -461,7 +439,7 @@ def _complete_partial_requirements(
# Map each link to the requirement that owns it. This allows us to set
# `req.local_file_path` on the appropriate requirement after passing
# all the links at once into BatchDownloader.
- links_to_fully_download = {} # type: Dict[Link, InstallRequirement]
+ links_to_fully_download: Dict[Link, InstallRequirement] = {}
for req in partially_downloaded_reqs:
assert req.link
links_to_fully_download[req.link] = req
@@ -480,8 +458,9 @@ def _complete_partial_requirements(
for req in partially_downloaded_reqs:
self._prepare_linked_requirement(req, parallel_builds)
- def prepare_linked_requirement(self, req, parallel_builds=False):
- # type: (InstallRequirement, bool) -> Distribution
+ def prepare_linked_requirement(
+ self, req: InstallRequirement, parallel_builds: bool = False
+ ) -> BaseDistribution:
"""Prepare a requirement to be obtained from req.link."""
assert req.link
link = req.link
@@ -496,7 +475,7 @@ def prepare_linked_requirement(self, req, parallel_builds=False):
if file_path is not None:
# The file is already available, so mark it as downloaded
- self._downloaded[req.link.url] = file_path, None
+ self._downloaded[req.link.url] = file_path
else:
# The file is not available, attempt to fetch only metadata
wheel_dist = self._fetch_metadata_using_lazy_wheel(link)
@@ -507,8 +486,9 @@ def prepare_linked_requirement(self, req, parallel_builds=False):
# None of the optimizations worked, fully prepare the requirement
return self._prepare_linked_requirement(req, parallel_builds)
- def prepare_linked_requirements_more(self, reqs, parallel_builds=False):
- # type: (Iterable[InstallRequirement], bool) -> None
+ def prepare_linked_requirements_more(
+ self, reqs: Iterable[InstallRequirement], parallel_builds: bool = False
+ ) -> None:
"""Prepare linked requirements more, if needed."""
reqs = [req for req in reqs if req.needs_more_preparation]
for req in reqs:
@@ -517,12 +497,12 @@ def prepare_linked_requirements_more(self, reqs, parallel_builds=False):
hashes = self._get_linked_req_hashes(req)
file_path = _check_download_dir(req.link, self.download_dir, hashes)
if file_path is not None:
- self._downloaded[req.link.url] = file_path, None
+ self._downloaded[req.link.url] = file_path
req.needs_more_preparation = False
# Prepare requirements we found were already downloaded for some
# reason. The other downloads will be completed separately.
- partially_downloaded_reqs = [] # type: List[InstallRequirement]
+ partially_downloaded_reqs: List[InstallRequirement] = []
for req in reqs:
if req.needs_more_preparation:
partially_downloaded_reqs.append(req)
@@ -532,11 +512,13 @@ def prepare_linked_requirements_more(self, reqs, parallel_builds=False):
# TODO: separate this part out from RequirementPreparer when the v1
# resolver can be removed!
self._complete_partial_requirements(
- partially_downloaded_reqs, parallel_builds=parallel_builds,
+ partially_downloaded_reqs,
+ parallel_builds=parallel_builds,
)
- def _prepare_linked_requirement(self, req, parallel_builds):
- # type: (InstallRequirement, bool) -> Distribution
+ def _prepare_linked_requirement(
+ self, req: InstallRequirement, parallel_builds: bool
+ ) -> BaseDistribution:
assert req.link
link = req.link
@@ -548,19 +530,23 @@ def _prepare_linked_requirement(self, req, parallel_builds):
elif link.url not in self._downloaded:
try:
local_file = unpack_url(
- link, req.source_dir, self._download,
- self.download_dir, hashes
+ link,
+ req.source_dir,
+ self._download,
+ self.verbosity,
+ self.download_dir,
+ hashes,
)
except NetworkConnectionError as exc:
raise InstallationError(
- 'Could not install requirement {} because of HTTP '
- 'error {} for URL {}'.format(req, exc, link)
+ "Could not install requirement {} because of HTTP "
+ "error {} for URL {}".format(req, exc, link)
)
else:
- file_path, content_type = self._downloaded[link.url]
+ file_path = self._downloaded[link.url]
if hashes:
hashes.check_against_path(file_path)
- local_file = File(file_path, content_type)
+ local_file = File(file_path, content_type=None)
# For use in later processing,
# preserve the file path on the requirement.
@@ -568,12 +554,14 @@ def _prepare_linked_requirement(self, req, parallel_builds):
req.local_file_path = local_file.path
dist = _get_prepared_distribution(
- req, self.req_tracker, self.finder, self.build_isolation,
+ req,
+ self.req_tracker,
+ self.finder,
+ self.build_isolation,
)
return dist
- def save_linked_requirement(self, req):
- # type: (InstallRequirement) -> None
+ def save_linked_requirement(self, req: InstallRequirement) -> None:
assert self.download_dir is not None
assert req.link is not None
link = req.link
@@ -584,8 +572,9 @@ def save_linked_requirement(self, req):
if link.is_existing_dir():
logger.debug(
- 'Not copying link to destination directory '
- 'since it is a directory: %s', link,
+ "Not copying link to destination directory "
+ "since it is a directory: %s",
+ link,
)
return
if req.local_file_path is None:
@@ -596,31 +585,32 @@ def save_linked_requirement(self, req):
if not os.path.exists(download_location):
shutil.copy(req.local_file_path, download_location)
download_path = display_path(download_location)
- logger.info('Saved %s', download_path)
+ logger.info("Saved %s", download_path)
def prepare_editable_requirement(
self,
- req, # type: InstallRequirement
- ):
- # type: (...) -> Distribution
- """Prepare an editable requirement
- """
+ req: InstallRequirement,
+ ) -> BaseDistribution:
+ """Prepare an editable requirement."""
assert req.editable, "cannot prepare a non-editable req as editable"
- logger.info('Obtaining %s', req)
+ logger.info("Obtaining %s", req)
with indent_log():
if self.require_hashes:
raise InstallationError(
- 'The editable requirement {} cannot be installed when '
- 'requiring hashes, because there is no single file to '
- 'hash.'.format(req)
+ "The editable requirement {} cannot be installed when "
+ "requiring hashes, because there is no single file to "
+ "hash.".format(req)
)
req.ensure_has_source_dir(self.src_dir)
req.update_editable()
dist = _get_prepared_distribution(
- req, self.req_tracker, self.finder, self.build_isolation,
+ req,
+ self.req_tracker,
+ self.finder,
+ self.build_isolation,
)
req.check_if_exists(self.use_user_site)
@@ -629,27 +619,24 @@ def prepare_editable_requirement(
def prepare_installed_requirement(
self,
- req, # type: InstallRequirement
- skip_reason # type: str
- ):
- # type: (...) -> Distribution
- """Prepare an already-installed requirement
- """
+ req: InstallRequirement,
+ skip_reason: str,
+ ) -> BaseDistribution:
+ """Prepare an already-installed requirement."""
assert req.satisfied_by, "req should have been satisfied but isn't"
assert skip_reason is not None, (
"did not get skip reason skipped but req.satisfied_by "
"is set to {}".format(req.satisfied_by)
)
logger.info(
- 'Requirement %s: %s (%s)',
- skip_reason, req, req.satisfied_by.version
+ "Requirement %s: %s (%s)", skip_reason, req, req.satisfied_by.version
)
with indent_log():
if self.require_hashes:
logger.debug(
- 'Since it is already installed, we are trusting this '
- 'package without checking its hash. To ensure a '
- 'completely repeatable environment, install into an '
- 'empty virtualenv.'
+ "Since it is already installed, we are trusting this "
+ "package without checking its hash. To ensure a "
+ "completely repeatable environment, install into an "
+ "empty virtualenv."
)
- return InstalledDistribution(req).get_pkg_resources_distribution()
+ return InstalledDistribution(req).get_metadata_distribution()
diff --git a/pipenv/patched/notpip/_internal/pyproject.py b/pipenv/patched/notpip/_internal/pyproject.py
index 6e4dc3e638..c9544e181e 100644
--- a/pipenv/patched/notpip/_internal/pyproject.py
+++ b/pipenv/patched/notpip/_internal/pyproject.py
@@ -5,34 +5,29 @@
from pipenv.patched.notpip._vendor import tomli
from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
-from pipenv.patched.notpip._internal.exceptions import InstallationError
+from pipenv.patched.notpip._internal.exceptions import (
+ InstallationError,
+ InvalidPyProjectBuildRequires,
+ MissingPyProjectBuildRequires,
+)
-def _is_list_of_str(obj):
- # type: (Any) -> bool
- return (
- isinstance(obj, list) and
- all(isinstance(item, str) for item in obj)
- )
+def _is_list_of_str(obj: Any) -> bool:
+ return isinstance(obj, list) and all(isinstance(item, str) for item in obj)
-def make_pyproject_path(unpacked_source_directory):
- # type: (str) -> str
- return os.path.join(unpacked_source_directory, 'pyproject.toml')
+def make_pyproject_path(unpacked_source_directory: str) -> str:
+ return os.path.join(unpacked_source_directory, "pyproject.toml")
-BuildSystemDetails = namedtuple('BuildSystemDetails', [
- 'requires', 'backend', 'check', 'backend_path'
-])
+BuildSystemDetails = namedtuple(
+ "BuildSystemDetails", ["requires", "backend", "check", "backend_path"]
+)
def load_pyproject_toml(
- use_pep517, # type: Optional[bool]
- pyproject_toml, # type: str
- setup_py, # type: str
- req_name # type: str
-):
- # type: (...) -> Optional[BuildSystemDetails]
+ use_pep517: Optional[bool], pyproject_toml: str, setup_py: str, req_name: str
+) -> Optional[BuildSystemDetails]:
"""Load the pyproject.toml file.
Parameters:
@@ -57,9 +52,15 @@ def load_pyproject_toml(
has_pyproject = os.path.isfile(pyproject_toml)
has_setup = os.path.isfile(setup_py)
+ if not has_pyproject and not has_setup:
+ raise InstallationError(
+ f"{req_name} does not appear to be a Python project: "
+ f"neither 'setup.py' nor 'pyproject.toml' found."
+ )
+
if has_pyproject:
with open(pyproject_toml, encoding="utf-8") as f:
- pp_toml = tomli.load(f)
+ pp_toml = tomli.loads(f.read())
build_system = pp_toml.get("build-system")
else:
build_system = None
@@ -82,9 +83,7 @@ def load_pyproject_toml(
raise InstallationError(
"Disabling PEP 517 processing is invalid: "
"project specifies a build backend of {} "
- "in pyproject.toml".format(
- build_system["build-backend"]
- )
+ "in pyproject.toml".format(build_system["build-backend"])
)
use_pep517 = True
@@ -124,46 +123,32 @@ def load_pyproject_toml(
# Ensure that the build-system section in pyproject.toml conforms
# to PEP 518.
- error_template = (
- "{package} has a pyproject.toml file that does not comply "
- "with PEP 518: {reason}"
- )
# Specifying the build-system table but not the requires key is invalid
if "requires" not in build_system:
- raise InstallationError(
- error_template.format(package=req_name, reason=(
- "it has a 'build-system' table but not "
- "'build-system.requires' which is mandatory in the table"
- ))
- )
+ raise MissingPyProjectBuildRequires(package=req_name)
# Error out if requires is not a list of strings
requires = build_system["requires"]
if not _is_list_of_str(requires):
- raise InstallationError(error_template.format(
+ raise InvalidPyProjectBuildRequires(
package=req_name,
- reason="'build-system.requires' is not a list of strings.",
- ))
+ reason="It is not a list of strings.",
+ )
# Each requirement must be valid as per PEP 508
for requirement in requires:
try:
Requirement(requirement)
- except InvalidRequirement:
- raise InstallationError(
- error_template.format(
- package=req_name,
- reason=(
- "'build-system.requires' contains an invalid "
- "requirement: {!r}".format(requirement)
- ),
- )
- )
+ except InvalidRequirement as error:
+ raise InvalidPyProjectBuildRequires(
+ package=req_name,
+ reason=f"It contains an invalid requirement: {requirement!r}",
+ ) from error
backend = build_system.get("build-backend")
backend_path = build_system.get("backend-path", [])
- check = [] # type: List[str]
+ check: List[str] = []
if backend is None:
# If the user didn't specify a backend, we assume they want to use
# the setuptools backend. But we can't be sure they have included
diff --git a/pipenv/patched/notpip/_internal/req/__init__.py b/pipenv/patched/notpip/_internal/req/__init__.py
index dcc9bcaf44..0f68f7e3c6 100644
--- a/pipenv/patched/notpip/_internal/req/__init__.py
+++ b/pipenv/patched/notpip/_internal/req/__init__.py
@@ -9,8 +9,10 @@
from .req_set import RequirementSet
__all__ = [
- "RequirementSet", "InstallRequirement",
- "parse_requirements", "install_given_reqs",
+ "RequirementSet",
+ "InstallRequirement",
+ "parse_requirements",
+ "install_given_reqs",
]
logger = logging.getLogger(__name__)
@@ -52,8 +54,8 @@ def install_given_reqs(
if to_install:
logger.info(
- 'Installing collected packages: %s',
- ', '.join(to_install.keys()),
+ "Installing collected packages: %s",
+ ", ".join(to_install.keys()),
)
installed = []
@@ -61,11 +63,9 @@ def install_given_reqs(
with indent_log():
for req_name, requirement in to_install.items():
if requirement.should_reinstall:
- logger.info('Attempting uninstall: %s', req_name)
+ logger.info("Attempting uninstall: %s", req_name)
with indent_log():
- uninstalled_pathset = requirement.uninstall(
- auto_confirm=True
- )
+ uninstalled_pathset = requirement.uninstall(auto_confirm=True)
else:
uninstalled_pathset = None
diff --git a/pipenv/patched/notpip/_internal/req/constructors.py b/pipenv/patched/notpip/_internal/req/constructors.py
index 16739d9c43..cef8ed1313 100644
--- a/pipenv/patched/notpip/_internal/req/constructors.py
+++ b/pipenv/patched/notpip/_internal/req/constructors.py
@@ -16,23 +16,23 @@
from pipenv.patched.notpip._vendor.packaging.markers import Marker
from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pipenv.patched.notpip._vendor.packaging.specifiers import Specifier
-from pipenv.patched.notpip._vendor.pkg_resources import RequirementParseError, parse_requirements
from pipenv.patched.notpip._internal.exceptions import InstallationError
from pipenv.patched.notpip._internal.models.index import PyPI, TestPyPI
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.models.wheel import Wheel
-from pipenv.patched.notpip._internal.pyproject import make_pyproject_path
from pipenv.patched.notpip._internal.req.req_file import ParsedRequirement
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.utils.filetypes import is_archive_file
from pipenv.patched.notpip._internal.utils.misc import is_installable_dir
+from pipenv.patched.notpip._internal.utils.packaging import get_requirement
from pipenv.patched.notpip._internal.utils.urls import path_to_url
from pipenv.patched.notpip._internal.vcs import is_url, vcs
__all__ = [
- "install_req_from_editable", "install_req_from_line",
- "parse_editable"
+ "install_req_from_editable",
+ "install_req_from_line",
+ "parse_editable",
]
logger = logging.getLogger(__name__)
@@ -40,7 +40,7 @@
def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
- m = re.match(r'^(.+)(\[[^\]]+\])$', path)
+ m = re.match(r"^(.+)(\[[^\]]+\])$", path)
extras = None
if m:
path_no_extras = m.group(1)
@@ -54,7 +54,7 @@ def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
def convert_extras(extras: Optional[str]) -> Set[str]:
if not extras:
return set()
- return Requirement("placeholder" + extras.lower()).extras
+ return get_requirement("placeholder" + extras.lower()).extras
def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
@@ -74,39 +74,23 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
url_no_extras, extras = _strip_extras(url)
if os.path.isdir(url_no_extras):
- setup_py = os.path.join(url_no_extras, 'setup.py')
- setup_cfg = os.path.join(url_no_extras, 'setup.cfg')
- if not os.path.exists(setup_py) and not os.path.exists(setup_cfg):
- msg = (
- 'File "setup.py" or "setup.cfg" not found. Directory cannot be '
- 'installed in editable mode: {}'
- .format(os.path.abspath(url_no_extras))
- )
- pyproject_path = make_pyproject_path(url_no_extras)
- if os.path.isfile(pyproject_path):
- msg += (
- '\n(A "pyproject.toml" file was found, but editable '
- 'mode currently requires a setuptools-based build.)'
- )
- raise InstallationError(msg)
-
# Treating it as code that has already been checked out
url_no_extras = path_to_url(url_no_extras)
- if url_no_extras.lower().startswith('file:'):
+ if url_no_extras.lower().startswith("file:"):
package_name = Link(url_no_extras).egg_fragment
if extras:
return (
package_name,
url_no_extras,
- Requirement("placeholder" + extras.lower()).extras,
+ get_requirement("placeholder" + extras.lower()).extras,
)
else:
return package_name, url_no_extras, set()
for version_control in vcs:
- if url.lower().startswith(f'{version_control}:'):
- url = f'{version_control}+{url}'
+ if url.lower().startswith(f"{version_control}:"):
+ url = f"{version_control}+{url}"
break
link = Link(url)
@@ -114,9 +98,9 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
if not link.is_vcs:
backends = ", ".join(vcs.all_schemes)
raise InstallationError(
- f'{editable_req} is not a valid editable requirement. '
- f'It should either be a path to a local project or a VCS URL '
- f'(beginning with {backends}).'
+ f"{editable_req} is not a valid editable requirement. "
+ f"It should either be a path to a local project or a VCS URL "
+ f"(beginning with {backends})."
)
package_name = link.egg_fragment
@@ -128,43 +112,66 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
return package_name, url, set()
+def check_first_requirement_in_file(filename: str) -> None:
+ """Check if file is parsable as a requirements file.
+
+ This is heavily based on ``pkg_resources.parse_requirements``, but
+ simplified to just check the first meaningful line.
+
+ :raises InvalidRequirement: If the first meaningful line cannot be parsed
+ as an requirement.
+ """
+ with open(filename, encoding="utf-8", errors="ignore") as f:
+ # Create a steppable iterator, so we can handle \-continuations.
+ lines = (
+ line
+ for line in (line.strip() for line in f)
+ if line and not line.startswith("#") # Skip blank lines/comments.
+ )
+
+ for line in lines:
+ # Drop comments -- a hash without a space may be in a URL.
+ if " #" in line:
+ line = line[: line.find(" #")]
+ # If there is a line continuation, drop it, and append the next line.
+ if line.endswith("\\"):
+ line = line[:-2].strip() + next(lines, "")
+ Requirement(line)
+ return
+
+
def deduce_helpful_msg(req: str) -> str:
"""Returns helpful msg in case requirements file does not exist,
or cannot be parsed.
:params req: Requirements file path
"""
- msg = ""
- if os.path.exists(req):
- msg = " The path does exist. "
- # Try to parse and check if it is a requirements file.
- try:
- with open(req) as fp:
- # parse first line only
- next(parse_requirements(fp.read()))
- msg += (
- "The argument you provided "
- "({}) appears to be a"
- " requirements file. If that is the"
- " case, use the '-r' flag to install"
- " the packages specified within it."
- ).format(req)
- except RequirementParseError:
- logger.debug(
- "Cannot parse '%s' as requirements file", req, exc_info=True
- )
+ if not os.path.exists(req):
+ return f" File '{req}' does not exist."
+ msg = " The path does exist. "
+ # Try to parse and check if it is a requirements file.
+ try:
+ check_first_requirement_in_file(req)
+ except InvalidRequirement:
+ logger.debug("Cannot parse '%s' as requirements file", req)
else:
- msg += f" File '{req}' does not exist."
+ msg += (
+ f"The argument you provided "
+ f"({req}) appears to be a"
+ f" requirements file. If that is the"
+ f" case, use the '-r' flag to install"
+ f" the packages specified within it."
+ )
return msg
class RequirementParts:
def __init__(
- self,
- requirement: Optional[Requirement],
- link: Optional[Link],
- markers: Optional[Marker],
- extras: Set[str],
+ self,
+ requirement: Optional[Requirement],
+ link: Optional[Link],
+ markers: Optional[Marker],
+ extras: Set[str],
):
self.requirement = requirement
self.link = link
@@ -199,6 +206,7 @@ def install_req_from_editable(
options: Optional[Dict[str, Any]] = None,
constraint: bool = False,
user_supplied: bool = False,
+ permit_editable_wheels: bool = False,
) -> InstallRequirement:
parts = parse_req_from_editable(editable_req)
@@ -208,6 +216,7 @@ def install_req_from_editable(
comes_from=comes_from,
user_supplied=user_supplied,
editable=True,
+ permit_editable_wheels=permit_editable_wheels,
link=parts.link,
constraint=constraint,
use_pep517=use_pep517,
@@ -250,6 +259,8 @@ def _get_url_from_path(path: str, name: str) -> Optional[str]:
if _looks_like_path(name) and os.path.isdir(path):
if is_installable_dir(path):
return path_to_url(path)
+ # TODO: The is_installable_dir test here might not be necessary
+ # now that it is done in load_pyproject_toml too.
raise InstallationError(
f"Directory {name!r} is not installable. Neither 'setup.py' "
"nor 'pyproject.toml' found."
@@ -258,24 +269,23 @@ def _get_url_from_path(path: str, name: str) -> Optional[str]:
return None
if os.path.isfile(path):
return path_to_url(path)
- urlreq_parts = name.split('@', 1)
+ urlreq_parts = name.split("@", 1)
if len(urlreq_parts) >= 2 and not _looks_like_path(urlreq_parts[0]):
# If the path contains '@' and the part before it does not look
# like a path, try to treat it as a PEP 440 URL req instead.
return None
logger.warning(
- 'Requirement %r looks like a filename, but the '
- 'file does not exist',
- name
+ "Requirement %r looks like a filename, but the file does not exist",
+ name,
)
return path_to_url(path)
def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementParts:
if is_url(name):
- marker_sep = '; '
+ marker_sep = "; "
else:
- marker_sep = ';'
+ marker_sep = ";"
if marker_sep in name:
name, markers_as_string = name.split(marker_sep, 1)
markers_as_string = markers_as_string.strip()
@@ -302,9 +312,8 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
# it's a local file, dir, or url
if link:
# Handle relative file URLs
- if link.scheme == 'file' and re.search(r'\.\./', link.url):
- link = Link(
- path_to_url(os.path.normpath(os.path.abspath(link.path))))
+ if link.scheme == "file" and re.search(r"\.\./", link.url):
+ link = Link(path_to_url(os.path.normpath(os.path.abspath(link.path))))
# wheel file
if link.is_wheel:
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
@@ -323,25 +332,24 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
def with_source(text: str) -> str:
if not line_source:
return text
- return f'{text} (from {line_source})'
+ return f"{text} (from {line_source})"
def _parse_req_string(req_as_string: str) -> Requirement:
try:
- req = Requirement(req_as_string)
+ req = get_requirement(req_as_string)
except InvalidRequirement:
if os.path.sep in req_as_string:
add_msg = "It looks like a path."
add_msg += deduce_helpful_msg(req_as_string)
- elif ('=' in req_as_string and
- not any(op in req_as_string for op in operators)):
+ elif "=" in req_as_string and not any(
+ op in req_as_string for op in operators
+ ):
add_msg = "= is not a valid operator. Did you mean == ?"
else:
- add_msg = ''
- msg = with_source(
- f'Invalid requirement: {req_as_string!r}'
- )
+ add_msg = ""
+ msg = with_source(f"Invalid requirement: {req_as_string!r}")
if add_msg:
- msg += f'\nHint: {add_msg}'
+ msg += f"\nHint: {add_msg}"
raise InstallationError(msg)
else:
# Deprecate extras after specifiers: "name>=1.0[extras]"
@@ -350,7 +358,7 @@ def _parse_req_string(req_as_string: str) -> Requirement:
# RequirementParts
for spec in req.specifier:
spec_str = str(spec)
- if spec_str.endswith(']'):
+ if spec_str.endswith("]"):
msg = f"Extras after version '{spec_str}'."
raise InstallationError(msg)
return req
@@ -382,8 +390,12 @@ def install_req_from_line(
parts = parse_req_from_line(name, line_source)
return InstallRequirement(
- parts.requirement, comes_from, link=parts.link, markers=parts.markers,
- use_pep517=use_pep517, isolated=isolated,
+ parts.requirement,
+ comes_from,
+ link=parts.link,
+ markers=parts.markers,
+ use_pep517=use_pep517,
+ isolated=isolated,
install_options=options.get("install_options", []) if options else [],
global_options=options.get("global_options", []) if options else [],
hash_options=options.get("hashes", {}) if options else {},
@@ -401,7 +413,7 @@ def install_req_from_req_string(
user_supplied: bool = False,
) -> InstallRequirement:
try:
- req = Requirement(req_string)
+ req = get_requirement(req_string)
except InvalidRequirement:
raise InstallationError(f"Invalid requirement: '{req_string}'")
@@ -409,8 +421,12 @@ def install_req_from_req_string(
PyPI.file_storage_domain,
TestPyPI.file_storage_domain,
]
- if (req.url and comes_from and comes_from.link and
- comes_from.link.netloc in domains_not_allowed):
+ if (
+ req.url
+ and comes_from
+ and comes_from.link
+ and comes_from.link.netloc in domains_not_allowed
+ ):
# Explicitly disallow pypi packages that depend on external urls
raise InstallationError(
"Packages installed from PyPI cannot depend on packages "
diff --git a/pipenv/patched/notpip/_internal/req/req_file.py b/pipenv/patched/notpip/_internal/req/req_file.py
index 2ea94f64c9..83ce8b8f4a 100644
--- a/pipenv/patched/notpip/_internal/req/req_file.py
+++ b/pipenv/patched/notpip/_internal/req/req_file.py
@@ -8,7 +8,17 @@
import shlex
import urllib.parse
from optparse import Values
-from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Tuple
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ Optional,
+ Tuple,
+)
from pipenv.patched.notpip._internal.cli import cmdoptions
from pipenv.patched.notpip._internal.exceptions import InstallationError, RequirementsFileParseError
@@ -25,20 +35,20 @@
from pipenv.patched.notpip._internal.index.package_finder import PackageFinder
-__all__ = ['parse_requirements']
+__all__ = ["parse_requirements"]
-ReqFileLines = Iterator[Tuple[int, str]]
+ReqFileLines = Iterable[Tuple[int, str]]
LineParser = Callable[[str], Tuple[str, Values]]
-SCHEME_RE = re.compile(r'^(http|https|file):', re.I)
-COMMENT_RE = re.compile(r'(^|\s+)#.*$')
+SCHEME_RE = re.compile(r"^(http|https|file):", re.I)
+COMMENT_RE = re.compile(r"(^|\s+)#.*$")
# Matches environment variable-style values in '${MY_VARIABLE_1}' with the
# variable name consisting of only uppercase letters, digits or the '_'
# (underscore). This follows the POSIX standard defined in IEEE Std 1003.1,
# 2013 Edition.
-ENV_VAR_RE = re.compile(r'(?P\$\{(?P[A-Z0-9_]+)\})')
+ENV_VAR_RE = re.compile(r"(?P\$\{(?P[A-Z0-9_]+)\})")
SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [
cmdoptions.index_url,
@@ -134,10 +144,7 @@ def parse_requirements(
for parsed_line in parser.parse(filename, constraint):
parsed_req = handle_line(
- parsed_line,
- options=options,
- finder=finder,
- session=session
+ parsed_line, options=options, finder=finder, session=session
)
if parsed_req is not None:
yield parsed_req
@@ -161,8 +168,10 @@ def handle_requirement_line(
) -> ParsedRequirement:
# preserve for the nested code path
- line_comes_from = '{} {} (line {})'.format(
- '-c' if line.constraint else '-r', line.filename, line.lineno,
+ line_comes_from = "{} {} (line {})".format(
+ "-c" if line.constraint else "-r",
+ line.filename,
+ line.lineno,
)
assert line.is_requirement
@@ -187,7 +196,7 @@ def handle_requirement_line(
if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
req_options[dest] = line.opts.__dict__[dest]
- line_source = f'line {line.lineno} of {line.filename}'
+ line_source = f"line {line.lineno} of {line.filename}"
return ParsedRequirement(
requirement=line.requirement,
is_editable=line.is_editable,
@@ -213,8 +222,7 @@ def handle_option_line(
options.require_hashes = opts.require_hashes
if opts.features_enabled:
options.features_enabled.extend(
- f for f in opts.features_enabled
- if f not in options.features_enabled
+ f for f in opts.features_enabled if f not in options.features_enabled
)
# set finder options
@@ -256,7 +264,7 @@ def handle_option_line(
if session:
for host in opts.trusted_hosts or []:
- source = f'line {lineno} of {filename}'
+ source = f"line {lineno} of {filename}"
session.add_trusted_host(host, source=source)
@@ -314,17 +322,15 @@ def __init__(
self._line_parser = line_parser
def parse(self, filename: str, constraint: bool) -> Iterator[ParsedLine]:
- """Parse a given file, yielding parsed lines.
- """
+ """Parse a given file, yielding parsed lines."""
yield from self._parse_and_recurse(filename, constraint)
def _parse_and_recurse(
self, filename: str, constraint: bool
) -> Iterator[ParsedLine]:
for line in self._parse_file(filename, constraint):
- if (
- not line.is_requirement and
- (line.opts.requirements or line.opts.constraints)
+ if not line.is_requirement and (
+ line.opts.requirements or line.opts.constraints
):
# parse a nested requirements file
if line.opts.requirements:
@@ -342,7 +348,8 @@ def _parse_and_recurse(
elif not SCHEME_RE.search(req_path):
# do a join so relative paths work
req_path = os.path.join(
- os.path.dirname(filename), req_path,
+ os.path.dirname(filename),
+ req_path,
)
yield from self._parse_and_recurse(req_path, nested_constraint)
@@ -359,7 +366,7 @@ def _parse_file(self, filename: str, constraint: bool) -> Iterator[ParsedLine]:
args_str, opts = self._line_parser(line)
except OptionParsingError as e:
# add offending line
- msg = f'Invalid requirement: {line}\n{e.msg}'
+ msg = f"Invalid requirement: {line}\n{e.msg}"
raise RequirementsFileParseError(msg)
yield ParsedLine(
@@ -395,16 +402,16 @@ def break_args_options(line: str) -> Tuple[str, str]:
(and then optparse) the options, not the args. args can contain markers
which are corrupted by shlex.
"""
- tokens = line.split(' ')
+ tokens = line.split(" ")
args = []
options = tokens[:]
for token in tokens:
- if token.startswith('-') or token.startswith('--'):
+ if token.startswith("-") or token.startswith("--"):
break
else:
args.append(token)
options.pop(0)
- return ' '.join(args), ' '.join(options)
+ return " ".join(args), " ".join(options)
class OptionParsingError(Exception):
@@ -427,6 +434,7 @@ def build_parser() -> optparse.OptionParser:
# that in our own exception.
def parser_exit(self: Any, msg: str) -> "NoReturn":
raise OptionParsingError(msg)
+
# NOTE: mypy disallows assigning to a method
# https://github.com/python/mypy/issues/2427
parser.exit = parser_exit # type: ignore
@@ -441,26 +449,26 @@ def join_lines(lines_enum: ReqFileLines) -> ReqFileLines:
primary_line_number = None
new_line: List[str] = []
for line_number, line in lines_enum:
- if not line.endswith('\\') or COMMENT_RE.match(line):
+ if not line.endswith("\\") or COMMENT_RE.match(line):
if COMMENT_RE.match(line):
# this ensures comments are always matched later
- line = ' ' + line
+ line = " " + line
if new_line:
new_line.append(line)
assert primary_line_number is not None
- yield primary_line_number, ''.join(new_line)
+ yield primary_line_number, "".join(new_line)
new_line = []
else:
yield line_number, line
else:
if not new_line:
primary_line_number = line_number
- new_line.append(line.strip('\\'))
+ new_line.append(line.strip("\\"))
# last line contains \
if new_line:
assert primary_line_number is not None
- yield primary_line_number, ''.join(new_line)
+ yield primary_line_number, "".join(new_line)
# TODO: handle space after '\'.
@@ -470,7 +478,7 @@ def ignore_comments(lines_enum: ReqFileLines) -> ReqFileLines:
Strips comments and filter empty lines.
"""
for line_number, line in lines_enum:
- line = COMMENT_RE.sub('', line)
+ line = COMMENT_RE.sub("", line)
line = line.strip()
if line:
yield line_number, line
@@ -514,15 +522,15 @@ def get_file_content(url: str, session: PipSession) -> Tuple[str, str]:
scheme = get_url_scheme(url)
# Pip has special support for file:// URLs (LocalFSAdapter).
- if scheme in ['http', 'https', 'file']:
+ if scheme in ["http", "https", "file"]:
resp = session.get(url)
raise_for_status(resp)
return resp.url, resp.text
# Assume this is a bare path.
try:
- with open(url, 'rb') as f:
+ with open(url, "rb") as f:
content = auto_decode(f.read())
except OSError as exc:
- raise InstallationError(f'Could not open requirements file: {exc}')
+ raise InstallationError(f"Could not open requirements file: {exc}")
return url, content
diff --git a/pipenv/patched/notpip/_internal/req/req_install.py b/pipenv/patched/notpip/_internal/req/req_install.py
index 149ecc4424..37f6fa570e 100644
--- a/pipenv/patched/notpip/_internal/req/req_install.py
+++ b/pipenv/patched/notpip/_internal/req/req_install.py
@@ -1,15 +1,15 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
+import functools
import logging
import os
import shutil
import sys
import uuid
import zipfile
-from typing import Any, Dict, Iterable, List, Optional, Sequence, Union
+from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union
-from pipenv.patched.notpip._vendor import pkg_resources, six
from pipenv.patched.notpip._vendor.packaging.markers import Marker
from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet
@@ -17,39 +17,43 @@
from pipenv.patched.notpip._vendor.packaging.version import Version
from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
from pipenv.patched.notpip._vendor.pep517.wrappers import Pep517HookCaller
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
from pipenv.patched.notpip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
-from pipenv.patched.notpip._internal.exceptions import InstallationError
+from pipenv.patched.notpip._internal.exceptions import InstallationError, LegacyInstallFailure
from pipenv.patched.notpip._internal.locations import get_scheme
+from pipenv.patched.notpip._internal.metadata import (
+ BaseDistribution,
+ get_default_environment,
+ get_directory_distribution,
+)
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.operations.build.metadata import generate_metadata
+from pipenv.patched.notpip._internal.operations.build.metadata_editable import generate_editable_metadata
from pipenv.patched.notpip._internal.operations.build.metadata_legacy import (
generate_metadata as generate_metadata_legacy,
)
from pipenv.patched.notpip._internal.operations.install.editable_legacy import (
install_editable as install_editable_legacy,
)
-from pipenv.patched.notpip._internal.operations.install.legacy import LegacyInstallFailure
from pipenv.patched.notpip._internal.operations.install.legacy import install as install_legacy
from pipenv.patched.notpip._internal.operations.install.wheel import install_wheel
from pipenv.patched.notpip._internal.pyproject import load_pyproject_toml, make_pyproject_path
from pipenv.patched.notpip._internal.req.req_uninstall import UninstallPathSet
from pipenv.patched.notpip._internal.utils.deprecation import deprecated
-from pipenv.patched.notpip._internal.utils.direct_url_helpers import direct_url_from_link
+from pipenv.patched.notpip._internal.utils.direct_url_helpers import (
+ direct_url_for_editable,
+ direct_url_from_link,
+)
from pipenv.patched.notpip._internal.utils.hashes import Hashes
-from pipenv.patched.notpip._internal.utils.logging import indent_log
from pipenv.patched.notpip._internal.utils.misc import (
ask_path_exists,
backup_dir,
display_path,
- dist_in_site_packages,
- dist_in_usersite,
- get_distribution,
hide_url,
redact_auth_from_url,
)
-from pipenv.patched.notpip._internal.utils.packaging import get_metadata
+from pipenv.patched.notpip._internal.utils.packaging import safe_extra
+from pipenv.patched.notpip._internal.utils.subprocess import runner_with_spinner_message
from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv
from pipenv.patched.notpip._internal.vcs import vcs
@@ -57,32 +61,6 @@
logger = logging.getLogger(__name__)
-def _get_dist(metadata_directory: str) -> Distribution:
- """Return a pkg_resources.Distribution for the provided
- metadata directory.
- """
- dist_dir = metadata_directory.rstrip(os.sep)
-
- # Build a PathMetadata object, from path to metadata. :wink:
- base_dir, dist_dir_name = os.path.split(dist_dir)
- metadata = pkg_resources.PathMetadata(base_dir, dist_dir)
-
- # Determine the correct Distribution object type.
- if dist_dir.endswith(".egg-info"):
- dist_cls = pkg_resources.Distribution
- dist_name = os.path.splitext(dist_dir_name)[0]
- else:
- assert dist_dir.endswith(".dist-info")
- dist_cls = pkg_resources.DistInfoDistribution
- dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0]
-
- return dist_cls(
- base_dir,
- project_name=dist_name,
- metadata=metadata,
- )
-
-
class InstallRequirement:
"""
Represents something that may be installed later on, may have information
@@ -103,14 +81,16 @@ def __init__(
global_options: Optional[List[str]] = None,
hash_options: Optional[Dict[str, List[str]]] = None,
constraint: bool = False,
- extras: Iterable[str] = (),
+ extras: Collection[str] = (),
user_supplied: bool = False,
+ permit_editable_wheels: bool = False,
) -> None:
assert req is None or isinstance(req, Requirement), req
self.req = req
self.comes_from = comes_from
self.constraint = constraint
self.editable = editable
+ self.permit_editable_wheels = permit_editable_wheels
self.legacy_install_reason: Optional[int] = None
# source_dir is the local directory where the linked requirement is
@@ -122,9 +102,7 @@ def __init__(
if self.editable:
assert link
if link.is_file:
- self.source_dir = os.path.normpath(
- os.path.abspath(link.file_path)
- )
+ self.source_dir = os.path.normpath(os.path.abspath(link.file_path))
if link is None and req and req.url:
# PEP 508 URL requirement
@@ -140,18 +118,15 @@ def __init__(
if extras:
self.extras = extras
elif req:
- self.extras = {
- pkg_resources.safe_extra(extra) for extra in req.extras
- }
+ self.extras = {safe_extra(extra) for extra in req.extras}
else:
self.extras = set()
if markers is None and req:
markers = req.marker
self.markers = markers
- # This holds the pkg_resources.Distribution object if this requirement
- # is already available:
- self.satisfied_by: Optional[Distribution] = None
+ # This holds the Distribution object if this requirement is already installed.
+ self.satisfied_by: Optional[BaseDistribution] = None
# Whether the installation process should try to uninstall an existing
# distribution before installing this requirement.
self.should_reinstall = False
@@ -202,36 +177,34 @@ def __str__(self) -> str:
if self.req:
s = str(self.req)
if self.link:
- s += ' from {}'.format(redact_auth_from_url(self.link.url))
+ s += " from {}".format(redact_auth_from_url(self.link.url))
elif self.link:
s = redact_auth_from_url(self.link.url)
else:
- s = ''
+ s = ""
if self.satisfied_by is not None:
- s += ' in {}'.format(display_path(self.satisfied_by.location))
+ s += " in {}".format(display_path(self.satisfied_by.location))
if self.comes_from:
if isinstance(self.comes_from, str):
comes_from: Optional[str] = self.comes_from
else:
comes_from = self.comes_from.from_path()
if comes_from:
- s += f' (from {comes_from})'
+ s += f" (from {comes_from})"
return s
def __repr__(self) -> str:
- return '<{} object: {} editable={!r}>'.format(
- self.__class__.__name__, str(self), self.editable)
+ return "<{} object: {} editable={!r}>".format(
+ self.__class__.__name__, str(self), self.editable
+ )
def format_debug(self) -> str:
- """An un-tested helper for getting state, for debugging.
- """
+ """An un-tested helper for getting state, for debugging."""
attributes = vars(self)
names = sorted(attributes)
- state = (
- "{}={!r}".format(attr, attributes[attr]) for attr in sorted(names)
- )
- return '<{name} object: {{{state}}}>'.format(
+ state = ("{}={!r}".format(attr, attributes[attr]) for attr in sorted(names))
+ return "<{name} object: {{{state}}}>".format(
name=self.__class__.__name__,
state=", ".join(state),
)
@@ -241,7 +214,19 @@ def format_debug(self) -> str:
def name(self) -> Optional[str]:
if self.req is None:
return None
- return pkg_resources.safe_name(self.req.name)
+ return self.req.name
+
+ @functools.lru_cache() # use cached_property in python 3.8+
+ def supports_pyproject_editable(self) -> bool:
+ if not self.use_pep517:
+ return False
+ assert self.pep517_backend
+ with self.build_env:
+ runner = runner_with_spinner_message(
+ "Checking if build backend supports build_editable"
+ )
+ with self.pep517_backend.subprocess_runner(runner):
+ return "build_editable" in self.pep517_backend._supported_features()
@property
def specifier(self) -> SpecifierSet:
@@ -254,18 +239,17 @@ def is_pinned(self) -> bool:
For example, some-package==1.2 is pinned; some-package>1.2 is not.
"""
specifiers = self.specifier
- return (len(specifiers) == 1 and
- next(iter(specifiers)).operator in {'==', '==='})
+ return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="}
def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool:
if not extras_requested:
# Provide an extra to safely evaluate the markers
# without matching any extra
- extras_requested = ('',)
+ extras_requested = ("",)
if self.markers is not None:
return any(
- self.markers.evaluate({'extra': extra})
- for extra in extras_requested)
+ self.markers.evaluate({"extra": extra}) for extra in extras_requested
+ )
else:
return True
@@ -301,8 +285,7 @@ def hashes(self, trust_internet: bool = True) -> Hashes:
return Hashes(good_hashes)
def from_path(self) -> Optional[str]:
- """Format a nice indicator to show where this "comes from"
- """
+ """Format a nice indicator to show where this "comes from" """
if self.req is None:
return None
s = str(self.req)
@@ -312,7 +295,7 @@ def from_path(self) -> Optional[str]:
else:
comes_from = self.comes_from.from_path()
if comes_from:
- s += '->' + comes_from
+ s += "->" + comes_from
return s
def ensure_build_location(
@@ -345,7 +328,7 @@ def ensure_build_location(
# FIXME: Is there a better place to create the build_dir? (hg and bzr
# need this)
if not os.path.exists(build_dir):
- logger.debug('Creating directory %s', build_dir)
+ logger.debug("Creating directory %s", build_dir)
os.makedirs(build_dir)
actual_build_dir = os.path.join(build_dir, dir_name)
# `None` indicates that we respect the globally-configured deletion
@@ -359,8 +342,7 @@ def ensure_build_location(
).path
def _set_requirement(self) -> None:
- """Set requirement after generating metadata.
- """
+ """Set requirement after generating metadata."""
assert self.req is None
assert self.metadata is not None
assert self.source_dir is not None
@@ -372,11 +354,13 @@ def _set_requirement(self) -> None:
op = "==="
self.req = Requirement(
- "".join([
- self.metadata["Name"],
- op,
- self.metadata["Version"],
- ])
+ "".join(
+ [
+ self.metadata["Name"],
+ op,
+ self.metadata["Version"],
+ ]
+ )
)
def warn_on_mismatching_name(self) -> None:
@@ -387,10 +371,12 @@ def warn_on_mismatching_name(self) -> None:
# If we're here, there's a mismatch. Log a warning about it.
logger.warning(
- 'Generating metadata for package %s '
- 'produced metadata for project name %s. Fix your '
- '#egg=%s fragments.',
- self.name, metadata_name, self.name
+ "Generating metadata for package %s "
+ "produced metadata for project name %s. Fix your "
+ "#egg=%s fragments.",
+ self.name,
+ metadata_name,
+ self.name,
)
self.req = Requirement(metadata_name)
@@ -401,30 +387,24 @@ def check_if_exists(self, use_user_site: bool) -> None:
"""
if self.req is None:
return
- existing_dist = get_distribution(self.req.name)
+ existing_dist = get_default_environment().get_distribution(self.req.name)
if not existing_dist:
return
- # pkg_resouces may contain a different copy of packaging.version from
- # pip in if the downstream distributor does a poor job debundling pip.
- # We avoid existing_dist.parsed_version and let SpecifierSet.contains
- # parses the version instead.
- existing_version = existing_dist.version
- version_compatible = (
- existing_version is not None and
- self.req.specifier.contains(existing_version, prereleases=True)
+ version_compatible = self.req.specifier.contains(
+ existing_dist.version,
+ prereleases=True,
)
if not version_compatible:
self.satisfied_by = None
if use_user_site:
- if dist_in_usersite(existing_dist):
+ if existing_dist.in_usersite:
self.should_reinstall = True
- elif (running_under_virtualenv() and
- dist_in_site_packages(existing_dist)):
+ elif running_under_virtualenv() and existing_dist.in_site_packages:
raise InstallationError(
- "Will not install to the user site because it will "
- "lack sys.path precedence to {} in {}".format(
- existing_dist.project_name, existing_dist.location)
+ f"Will not install to the user site because it will "
+ f"lack sys.path precedence to {existing_dist.raw_name} "
+ f"in {existing_dist.location}"
)
else:
self.should_reinstall = True
@@ -448,16 +428,23 @@ def is_wheel(self) -> bool:
@property
def unpacked_source_directory(self) -> str:
return os.path.join(
- self.source_dir,
- self.link and self.link.subdirectory_fragment or '')
+ self.source_dir, self.link and self.link.subdirectory_fragment or ""
+ )
@property
def setup_py_path(self) -> str:
assert self.source_dir, f"No source dir for {self}"
- setup_py = os.path.join(self.unpacked_source_directory, 'setup.py')
+ setup_py = os.path.join(self.unpacked_source_directory, "setup.py")
return setup_py
+ @property
+ def setup_cfg_path(self) -> str:
+ assert self.source_dir, f"No source dir for {self}"
+ setup_cfg = os.path.join(self.unpacked_source_directory, "setup.cfg")
+
+ return setup_cfg
+
@property
def pyproject_toml_path(self) -> str:
assert self.source_dir, f"No source dir for {self}"
@@ -472,10 +459,7 @@ def load_pyproject_toml(self) -> None:
follow the PEP 517 or legacy (setup.py) code path.
"""
pyproject_toml_data = load_pyproject_toml(
- self.use_pep517,
- self.pyproject_toml_path,
- self.setup_py_path,
- str(self)
+ self.use_pep517, self.pyproject_toml_path, self.setup_py_path, str(self)
)
if pyproject_toml_data is None:
@@ -487,46 +471,67 @@ def load_pyproject_toml(self) -> None:
self.requirements_to_check = check
self.pyproject_requires = requires
self.pep517_backend = Pep517HookCaller(
- self.unpacked_source_directory, backend, backend_path=backend_path,
- python_executable=os.getenv('PIP_PYTHON_PATH', sys.executable)
+ self.unpacked_source_directory,
+ backend,
+ backend_path=backend_path,
)
- def _generate_metadata(self) -> str:
- """Invokes metadata generator functions, with the required arguments.
- """
- if not self.use_pep517:
- assert self.unpacked_source_directory
+ def isolated_editable_sanity_check(self) -> None:
+ """Check that an editable requirement if valid for use with PEP 517/518.
- if not os.path.exists(self.setup_py_path):
- raise InstallationError(
- f'File "setup.py" not found for legacy project {self}.'
- )
-
- return generate_metadata_legacy(
- build_env=self.build_env,
- setup_py_path=self.setup_py_path,
- source_dir=self.unpacked_source_directory,
- isolated=self.isolated,
- details=self.name or f"from {self.link}"
+ This verifies that an editable that has a pyproject.toml either supports PEP 660
+ or as a setup.py or a setup.cfg
+ """
+ if (
+ self.editable
+ and self.use_pep517
+ and not self.supports_pyproject_editable()
+ and not os.path.isfile(self.setup_py_path)
+ and not os.path.isfile(self.setup_cfg_path)
+ ):
+ raise InstallationError(
+ f"Project {self} has a 'pyproject.toml' and its build "
+ f"backend is missing the 'build_editable' hook. Since it does not "
+ f"have a 'setup.py' nor a 'setup.cfg', "
+ f"it cannot be installed in editable mode. "
+ f"Consider using a build backend that supports PEP 660."
)
- assert self.pep517_backend is not None
-
- return generate_metadata(
- build_env=self.build_env,
- backend=self.pep517_backend,
- )
-
def prepare_metadata(self) -> None:
"""Ensure that project metadata is available.
- Under PEP 517, call the backend hook to prepare the metadata.
+ Under PEP 517 and PEP 660, call the backend hook to prepare the metadata.
Under legacy processing, call setup.py egg-info.
"""
assert self.source_dir
-
- with indent_log():
- self.metadata_directory = self._generate_metadata()
+ details = self.name or f"from {self.link}"
+
+ if self.use_pep517:
+ assert self.pep517_backend is not None
+ if (
+ self.editable
+ and self.permit_editable_wheels
+ and self.supports_pyproject_editable()
+ ):
+ self.metadata_directory = generate_editable_metadata(
+ build_env=self.build_env,
+ backend=self.pep517_backend,
+ details=details,
+ )
+ else:
+ self.metadata_directory = generate_metadata(
+ build_env=self.build_env,
+ backend=self.pep517_backend,
+ details=details,
+ )
+ else:
+ self.metadata_directory = generate_metadata_legacy(
+ build_env=self.build_env,
+ setup_py_path=self.setup_py_path,
+ source_dir=self.unpacked_source_directory,
+ isolated=self.isolated,
+ details=details,
+ )
# Act on the newly generated metadata, based on the name and version.
if not self.name:
@@ -538,26 +543,26 @@ def prepare_metadata(self) -> None:
@property
def metadata(self) -> Any:
- if not hasattr(self, '_metadata'):
- self._metadata = get_metadata(self.get_dist())
+ if not hasattr(self, "_metadata"):
+ self._metadata = self.get_dist().metadata
return self._metadata
- def get_dist(self) -> Distribution:
- return _get_dist(self.metadata_directory)
+ def get_dist(self) -> BaseDistribution:
+ return get_directory_distribution(self.metadata_directory)
def assert_source_matches_version(self) -> None:
assert self.source_dir
- version = self.metadata['version']
+ version = self.metadata["version"]
if self.req.specifier and version not in self.req.specifier:
logger.warning(
- 'Requested %s, but installing version %s',
+ "Requested %s, but installing version %s",
self,
version,
)
else:
logger.debug(
- 'Source in %s has version %s, which satisfies requirement %s',
+ "Source in %s has version %s, which satisfies requirement %s",
display_path(self.source_dir),
version,
self,
@@ -590,14 +595,13 @@ def ensure_has_source_dir(
def update_editable(self) -> None:
if not self.link:
logger.debug(
- "Cannot update repository at %s; repository location is "
- "unknown",
+ "Cannot update repository at %s; repository location is unknown",
self.source_dir,
)
return
assert self.editable
assert self.source_dir
- if self.link.scheme == 'file':
+ if self.link.scheme == "file":
# Static paths don't get updated
return
vcs_backend = vcs.get_backend_for_scheme(self.link.scheme)
@@ -605,7 +609,7 @@ def update_editable(self) -> None:
# So here, if it's neither a path nor a valid VCS URL, it's a bug.
assert vcs_backend, f"Unsupported VCS URL {self.link.url}"
hidden_url = hide_url(self.link.url)
- vcs_backend.obtain(self.source_dir, url=hidden_url)
+ vcs_backend.obtain(self.source_dir, url=hidden_url, verbosity=0)
# Top-level Actions
def uninstall(
@@ -624,29 +628,28 @@ def uninstall(
"""
assert self.req
- dist = get_distribution(self.req.name)
+ dist = get_default_environment().get_distribution(self.req.name)
if not dist:
logger.warning("Skipping %s as it is not installed.", self.name)
return None
- logger.info('Found existing installation: %s', dist)
+ logger.info("Found existing installation: %s", dist)
uninstalled_pathset = UninstallPathSet.from_dist(dist)
uninstalled_pathset.remove(auto_confirm, verbose)
return uninstalled_pathset
def _get_archive_name(self, path: str, parentdir: str, rootdir: str) -> str:
-
def _clean_zip_name(name: str, prefix: str) -> str:
- assert name.startswith(prefix + os.path.sep), (
- f"name {name!r} doesn't start with prefix {prefix!r}"
- )
- name = name[len(prefix) + 1:]
- name = name.replace(os.path.sep, '/')
+ assert name.startswith(
+ prefix + os.path.sep
+ ), f"name {name!r} doesn't start with prefix {prefix!r}"
+ name = name[len(prefix) + 1 :]
+ name = name.replace(os.path.sep, "/")
return name
path = os.path.join(parentdir, path)
name = _clean_zip_name(path, rootdir)
- return self.name + '/' + name
+ return self.name + "/" + name
def archive(self, build_dir: Optional[str]) -> None:
"""Saves archive to provided build_dir.
@@ -658,57 +661,62 @@ def archive(self, build_dir: Optional[str]) -> None:
return
create_archive = True
- archive_name = '{}-{}.zip'.format(self.name, self.metadata["version"])
+ archive_name = "{}-{}.zip".format(self.name, self.metadata["version"])
archive_path = os.path.join(build_dir, archive_name)
if os.path.exists(archive_path):
response = ask_path_exists(
- 'The file {} exists. (i)gnore, (w)ipe, '
- '(b)ackup, (a)bort '.format(
- display_path(archive_path)),
- ('i', 'w', 'b', 'a'))
- if response == 'i':
+ "The file {} exists. (i)gnore, (w)ipe, "
+ "(b)ackup, (a)bort ".format(display_path(archive_path)),
+ ("i", "w", "b", "a"),
+ )
+ if response == "i":
create_archive = False
- elif response == 'w':
- logger.warning('Deleting %s', display_path(archive_path))
+ elif response == "w":
+ logger.warning("Deleting %s", display_path(archive_path))
os.remove(archive_path)
- elif response == 'b':
+ elif response == "b":
dest_file = backup_dir(archive_path)
logger.warning(
- 'Backing up %s to %s',
+ "Backing up %s to %s",
display_path(archive_path),
display_path(dest_file),
)
shutil.move(archive_path, dest_file)
- elif response == 'a':
+ elif response == "a":
sys.exit(-1)
if not create_archive:
return
zip_output = zipfile.ZipFile(
- archive_path, 'w', zipfile.ZIP_DEFLATED, allowZip64=True,
+ archive_path,
+ "w",
+ zipfile.ZIP_DEFLATED,
+ allowZip64=True,
)
with zip_output:
- dir = os.path.normcase(
- os.path.abspath(self.unpacked_source_directory)
- )
+ dir = os.path.normcase(os.path.abspath(self.unpacked_source_directory))
for dirpath, dirnames, filenames in os.walk(dir):
for dirname in dirnames:
dir_arcname = self._get_archive_name(
- dirname, parentdir=dirpath, rootdir=dir,
+ dirname,
+ parentdir=dirpath,
+ rootdir=dir,
)
- zipdir = zipfile.ZipInfo(dir_arcname + '/')
+ zipdir = zipfile.ZipInfo(dir_arcname + "/")
zipdir.external_attr = 0x1ED << 16 # 0o755
- zip_output.writestr(zipdir, '')
+ zip_output.writestr(zipdir, "")
for filename in filenames:
file_arcname = self._get_archive_name(
- filename, parentdir=dirpath, rootdir=dir,
+ filename,
+ parentdir=dirpath,
+ rootdir=dir,
)
filename = os.path.join(dirpath, filename)
zip_output.write(filename, file_arcname)
- logger.info('Saved %s', display_path(archive_path))
+ logger.info("Saved %s", display_path(archive_path))
def install(
self,
@@ -719,7 +727,7 @@ def install(
prefix: Optional[str] = None,
warn_script_location: bool = True,
use_user_site: bool = False,
- pycompile: bool = True
+ pycompile: bool = True,
) -> None:
scheme = get_scheme(
self.name,
@@ -731,7 +739,7 @@ def install(
)
global_options = global_options if global_options is not None else []
- if self.editable:
+ if self.editable and not self.is_wheel:
install_editable_legacy(
install_options,
global_options,
@@ -750,7 +758,9 @@ def install(
if self.is_wheel:
assert self.local_file_path
direct_url = None
- if self.original_link:
+ if self.editable:
+ direct_url = direct_url_for_editable(self.unpacked_source_directory)
+ elif self.original_link:
direct_url = direct_url_from_link(
self.original_link,
self.source_dir,
@@ -798,7 +808,7 @@ def install(
)
except LegacyInstallFailure as exc:
self.install_succeeded = False
- six.reraise(*exc.parent)
+ raise exc
except Exception:
self.install_succeeded = True
raise
@@ -809,8 +819,9 @@ def install(
deprecated(
reason=(
"{} was installed using the legacy 'setup.py install' "
- "method, because a wheel could not be built for it.".
- format(self.name)
+ "method, because a wheel could not be built for it.".format(
+ self.name
+ )
),
replacement="to fix the wheel build issue reported above",
gone_in=None,
@@ -838,12 +849,10 @@ def check_invalid_constraint_type(req: InstallRequirement) -> str:
"undocumented. The new implementation of the resolver no "
"longer supports these forms."
),
- replacement=(
- "replacing the constraint with a requirement."
- ),
+ replacement="replacing the constraint with a requirement",
# No plan yet for when the new resolver becomes default
gone_in=None,
- issue=8210
+ issue=8210,
)
return problem
diff --git a/pipenv/patched/notpip/_internal/req/req_set.py b/pipenv/patched/notpip/_internal/req/req_set.py
index a9cde8004c..697ee8483b 100644
--- a/pipenv/patched/notpip/_internal/req/req_set.py
+++ b/pipenv/patched/notpip/_internal/req/req_set.py
@@ -13,10 +13,8 @@
class RequirementSet:
-
def __init__(self, check_supported_wheels: bool = True) -> None:
- """Create a RequirementSet.
- """
+ """Create a RequirementSet."""
self.requirements: Dict[str, InstallRequirement] = OrderedDict()
self.check_supported_wheels = check_supported_wheels
@@ -28,7 +26,7 @@ def __str__(self) -> str:
(req for req in self.requirements.values() if not req.comes_from),
key=lambda req: canonicalize_name(req.name or ""),
)
- return ' '.join(str(req.req) for req in requirements)
+ return " ".join(str(req.req) for req in requirements)
def __repr__(self) -> str:
requirements = sorted(
@@ -36,11 +34,11 @@ def __repr__(self) -> str:
key=lambda req: canonicalize_name(req.name or ""),
)
- format_string = '<{classname} object; {count} requirement(s): {reqs}>'
+ format_string = "<{classname} object; {count} requirement(s): {reqs}>"
return format_string.format(
classname=self.__class__.__name__,
count=len(requirements),
- reqs=', '.join(str(req.req) for req in requirements),
+ reqs=", ".join(str(req.req) for req in requirements),
)
def add_unnamed_requirement(self, install_req: InstallRequirement) -> None:
@@ -57,7 +55,7 @@ def add_requirement(
self,
install_req: InstallRequirement,
parent_req_name: Optional[str] = None,
- extras_requested: Optional[Iterable[str]] = None
+ extras_requested: Optional[Iterable[str]] = None,
) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
"""Add install_req as a requirement to install.
@@ -77,7 +75,8 @@ def add_requirement(
if not install_req.match_markers(extras_requested):
logger.info(
"Ignoring %s: markers '%s' don't match your environment",
- install_req.name, install_req.markers,
+ install_req.name,
+ install_req.markers,
)
return [], None
@@ -88,16 +87,17 @@ def add_requirement(
if install_req.link and install_req.link.is_wheel:
wheel = Wheel(install_req.link.filename)
tags = compatibility_tags.get_supported()
- if (self.check_supported_wheels and not wheel.supported(tags)):
+ if self.check_supported_wheels and not wheel.supported(tags):
raise InstallationError(
"{} is not a supported wheel on this platform.".format(
- wheel.filename)
+ wheel.filename
+ )
)
# This next bit is really a sanity check.
- assert not install_req.user_supplied or parent_req_name is None, (
- "a user supplied req shouldn't have a parent"
- )
+ assert (
+ not install_req.user_supplied or parent_req_name is None
+ ), "a user supplied req shouldn't have a parent"
# Unnamed requirements are scanned again and the requirement won't be
# added as a dependency until after scanning.
@@ -107,23 +107,25 @@ def add_requirement(
try:
existing_req: Optional[InstallRequirement] = self.get_requirement(
- install_req.name)
+ install_req.name
+ )
except KeyError:
existing_req = None
has_conflicting_requirement = (
- parent_req_name is None and
- existing_req and
- not existing_req.constraint and
- existing_req.extras == install_req.extras and
- existing_req.req and
- install_req.req and
- existing_req.req.specifier != install_req.req.specifier
+ parent_req_name is None
+ and existing_req
+ and not existing_req.constraint
+ and existing_req.extras == install_req.extras
+ and existing_req.req
+ and install_req.req
+ and existing_req.req.specifier != install_req.req.specifier
)
if has_conflicting_requirement:
raise InstallationError(
- "Double requirement given: {} (already in {}, name={!r})"
- .format(install_req, existing_req, install_req.name)
+ "Double requirement given: {} (already in {}, name={!r})".format(
+ install_req, existing_req, install_req.name
+ )
)
# When no existing requirement exists, add the requirement as a
@@ -138,12 +140,8 @@ def add_requirement(
if install_req.constraint or not existing_req.constraint:
return [], existing_req
- does_not_satisfy_constraint = (
- install_req.link and
- not (
- existing_req.link and
- install_req.link.path == existing_req.link.path
- )
+ does_not_satisfy_constraint = install_req.link and not (
+ existing_req.link and install_req.link.path == existing_req.link.path
)
if does_not_satisfy_constraint:
raise InstallationError(
@@ -158,12 +156,13 @@ def add_requirement(
# mark the existing object as such.
if install_req.user_supplied:
existing_req.user_supplied = True
- existing_req.extras = tuple(sorted(
- set(existing_req.extras) | set(install_req.extras)
- ))
+ existing_req.extras = tuple(
+ sorted(set(existing_req.extras) | set(install_req.extras))
+ )
logger.debug(
"Setting %s extras to: %s",
- existing_req, existing_req.extras,
+ existing_req,
+ existing_req.extras,
)
# Return the existing requirement for addition to the parent and
# scanning again.
@@ -173,8 +172,8 @@ def has_requirement(self, name: str) -> bool:
project_name = canonicalize_name(name)
return (
- project_name in self.requirements and
- not self.requirements[project_name].constraint
+ project_name in self.requirements
+ and not self.requirements[project_name].constraint
)
def get_requirement(self, name: str) -> InstallRequirement:
diff --git a/pipenv/patched/notpip/_internal/req/req_tracker.py b/pipenv/patched/notpip/_internal/req/req_tracker.py
index cee440a469..f0d9d92c9e 100644
--- a/pipenv/patched/notpip/_internal/req/req_tracker.py
+++ b/pipenv/patched/notpip/_internal/req/req_tracker.py
@@ -40,12 +40,10 @@ def update_env_context_manager(**changes: str) -> Iterator[None]:
@contextlib.contextmanager
def get_requirement_tracker() -> Iterator["RequirementTracker"]:
- root = os.environ.get('PIP_REQ_TRACKER')
+ root = os.environ.get("PIP_REQ_TRACKER")
with contextlib.ExitStack() as ctx:
if root is None:
- root = ctx.enter_context(
- TempDirectory(kind='req-tracker')
- ).path
+ root = ctx.enter_context(TempDirectory(kind="req-tracker")).path
ctx.enter_context(update_env_context_manager(PIP_REQ_TRACKER=root))
logger.debug("Initialized build tracking at %s", root)
@@ -54,7 +52,6 @@ def get_requirement_tracker() -> Iterator["RequirementTracker"]:
class RequirementTracker:
-
def __init__(self, root: str) -> None:
self._root = root
self._entries: Set[InstallRequirement] = set()
@@ -68,7 +65,7 @@ def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
- exc_tb: Optional[TracebackType]
+ exc_tb: Optional[TracebackType],
) -> None:
self.cleanup()
@@ -77,8 +74,7 @@ def _entry_path(self, link: Link) -> str:
return os.path.join(self._root, hashed)
def add(self, req: InstallRequirement) -> None:
- """Add an InstallRequirement to build tracking.
- """
+ """Add an InstallRequirement to build tracking."""
assert req.link
# Get the file to write information about this requirement.
@@ -92,30 +88,28 @@ def add(self, req: InstallRequirement) -> None:
except FileNotFoundError:
pass
else:
- message = '{} is already being built: {}'.format(
- req.link, contents)
+ message = "{} is already being built: {}".format(req.link, contents)
raise LookupError(message)
# If we're here, req should really not be building already.
assert req not in self._entries
# Start tracking this requirement.
- with open(entry_path, 'w', encoding="utf-8") as fp:
+ with open(entry_path, "w", encoding="utf-8") as fp:
fp.write(str(req))
self._entries.add(req)
- logger.debug('Added %s to build tracker %r', req, self._root)
+ logger.debug("Added %s to build tracker %r", req, self._root)
def remove(self, req: InstallRequirement) -> None:
- """Remove an InstallRequirement from build tracking.
- """
+ """Remove an InstallRequirement from build tracking."""
assert req.link
# Delete the created file and the corresponding entries.
os.unlink(self._entry_path(req.link))
self._entries.remove(req)
- logger.debug('Removed %s from build tracker %r', req, self._root)
+ logger.debug("Removed %s from build tracker %r", req, self._root)
def cleanup(self) -> None:
for req in set(self._entries):
diff --git a/pipenv/patched/notpip/_internal/req/req_uninstall.py b/pipenv/patched/notpip/_internal/req/req_uninstall.py
index 22ce30689d..4dd6561b98 100644
--- a/pipenv/patched/notpip/_internal/req/req_uninstall.py
+++ b/pipenv/patched/notpip/_internal/req/req_uninstall.py
@@ -1,4 +1,3 @@
-import csv
import functools
import os
import sys
@@ -6,47 +5,33 @@
from importlib.util import cache_from_source
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple
-from pipenv.patched.notpip._vendor import pkg_resources
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
-
from pipenv.patched.notpip._internal.exceptions import UninstallationError
from pipenv.patched.notpip._internal.locations import get_bin_prefix, get_bin_user
+from pipenv.patched.notpip._internal.metadata import BaseDistribution
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
+from pipenv.patched.notpip._internal.utils.egg_link import egg_link_path_from_location
from pipenv.patched.notpip._internal.utils.logging import getLogger, indent_log
-from pipenv.patched.notpip._internal.utils.misc import (
- ask,
- dist_in_usersite,
- dist_is_local,
- egg_link_path,
- is_local,
- normalize_path,
- renames,
- rmtree,
-)
+from pipenv.patched.notpip._internal.utils.misc import ask, is_local, normalize_path, renames, rmtree
from pipenv.patched.notpip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory
logger = getLogger(__name__)
-def _script_names(dist: Distribution, script_name: str, is_gui: bool) -> List[str]:
+def _script_names(bin_dir: str, script_name: str, is_gui: bool) -> Iterator[str]:
"""Create the fully qualified name of the files created by
{console,gui}_scripts for the given ``dist``.
Returns the list of file names
"""
- if dist_in_usersite(dist):
- bin_dir = get_bin_user()
- else:
- bin_dir = get_bin_prefix()
exe_name = os.path.join(bin_dir, script_name)
- paths_to_remove = [exe_name]
- if WINDOWS:
- paths_to_remove.append(exe_name + '.exe')
- paths_to_remove.append(exe_name + '.exe.manifest')
- if is_gui:
- paths_to_remove.append(exe_name + '-script.pyw')
- else:
- paths_to_remove.append(exe_name + '-script.py')
- return paths_to_remove
+ yield exe_name
+ if not WINDOWS:
+ return
+ yield f"{exe_name}.exe"
+ yield f"{exe_name}.exe.manifest"
+ if is_gui:
+ yield f"{exe_name}-script.pyw"
+ else:
+ yield f"{exe_name}-script.py"
def _unique(fn: Callable[..., Iterator[Any]]) -> Callable[..., Iterator[Any]]:
@@ -57,11 +42,12 @@ def unique(*args: Any, **kw: Any) -> Iterator[Any]:
if item not in seen:
seen.add(item)
yield item
+
return unique
@_unique
-def uninstallation_paths(dist: Distribution) -> Iterator[str]:
+def uninstallation_paths(dist: BaseDistribution) -> Iterator[str]:
"""
Yield all the uninstallation paths for dist based on RECORD-without-.py[co]
@@ -75,30 +61,32 @@ def uninstallation_paths(dist: Distribution) -> Iterator[str]:
https://packaging.python.org/specifications/recording-installed-packages/
"""
- try:
- r = csv.reader(dist.get_metadata_lines('RECORD'))
- except FileNotFoundError as missing_record_exception:
- msg = 'Cannot uninstall {dist}, RECORD file not found.'.format(dist=dist)
- try:
- installer = next(dist.get_metadata_lines('INSTALLER'))
- if not installer or installer == 'pip':
- raise ValueError()
- except (OSError, StopIteration, ValueError):
- dep = '{}=={}'.format(dist.project_name, dist.version)
- msg += (" You might be able to recover from this via: "
- "'pip install --force-reinstall --no-deps {}'.".format(dep))
+ location = dist.location
+ assert location is not None, "not installed"
+
+ entries = dist.iter_declared_entries()
+ if entries is None:
+ msg = "Cannot uninstall {dist}, RECORD file not found.".format(dist=dist)
+ installer = dist.installer
+ if not installer or installer == "pip":
+ dep = "{}=={}".format(dist.raw_name, dist.version)
+ msg += (
+ " You might be able to recover from this via: "
+ "'pip install --force-reinstall --no-deps {}'.".format(dep)
+ )
else:
- msg += ' Hint: The package was installed by {}.'.format(installer)
- raise UninstallationError(msg) from missing_record_exception
- for row in r:
- path = os.path.join(dist.location, row[0])
+ msg += " Hint: The package was installed by {}.".format(installer)
+ raise UninstallationError(msg)
+
+ for entry in entries:
+ path = os.path.join(location, entry)
yield path
- if path.endswith('.py'):
+ if path.endswith(".py"):
dn, fn = os.path.split(path)
base = fn[:-3]
- path = os.path.join(dn, base + '.pyc')
+ path = os.path.join(dn, base + ".pyc")
yield path
- path = os.path.join(dn, base + '.pyo')
+ path = os.path.join(dn, base + ".pyo")
yield path
@@ -112,8 +100,8 @@ def compact(paths: Iterable[str]) -> Set[str]:
short_paths: Set[str] = set()
for path in sorted(paths, key=len):
should_skip = any(
- path.startswith(shortpath.rstrip("*")) and
- path[len(shortpath.rstrip("*").rstrip(sep))] == sep
+ path.startswith(shortpath.rstrip("*"))
+ and path[len(shortpath.rstrip("*").rstrip(sep))] == sep
for shortpath in short_paths
)
if not should_skip:
@@ -136,18 +124,15 @@ def norm_join(*a: str) -> str:
return os.path.normcase(os.path.join(*a))
for root in unchecked:
- if any(os.path.normcase(root).startswith(w)
- for w in wildcards):
+ if any(os.path.normcase(root).startswith(w) for w in wildcards):
# This directory has already been handled.
continue
all_files: Set[str] = set()
all_subdirs: Set[str] = set()
for dirname, subdirs, files in os.walk(root):
- all_subdirs.update(norm_join(root, dirname, d)
- for d in subdirs)
- all_files.update(norm_join(root, dirname, f)
- for f in files)
+ all_subdirs.update(norm_join(root, dirname, d) for d in subdirs)
+ all_files.update(norm_join(root, dirname, f) for f in files)
# If all the files we found are in our remaining set of files to
# remove, then remove them from the latter set and add a wildcard
# for the directory.
@@ -196,14 +181,14 @@ def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str
continue
file_ = os.path.join(dirpath, fname)
- if (os.path.isfile(file_) and
- os.path.normcase(file_) not in _normcased_files):
+ if (
+ os.path.isfile(file_)
+ and os.path.normcase(file_) not in _normcased_files
+ ):
# We are skipping this file. Add it to the set.
will_skip.add(file_)
- will_remove = files | {
- os.path.join(folder, "*") for folder in folders
- }
+ will_remove = files | {os.path.join(folder, "*") for folder in folders}
return will_remove, will_skip
@@ -211,6 +196,7 @@ def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str
class StashedUninstallPathSet:
"""A set of file rename operations to stash files while
tentatively uninstalling them."""
+
def __init__(self) -> None:
# Mapping from source file root to [Adjacent]TempDirectory
# for files under that directory.
@@ -252,7 +238,7 @@ def _get_file_stash(self, path: str) -> str:
else:
# Did not find any suitable root
head = os.path.dirname(path)
- save_dir = TempDirectory(kind='uninstall')
+ save_dir = TempDirectory(kind="uninstall")
self._save_dirs[head] = save_dir
relpath = os.path.relpath(path, head)
@@ -271,7 +257,7 @@ def stash(self, path: str) -> str:
new_path = self._get_file_stash(path)
self._moves.append((path, new_path))
- if (path_is_dir and os.path.isdir(new_path)):
+ if path_is_dir and os.path.isdir(new_path):
# If we're moving a directory, we need to
# remove the destination first or else it will be
# moved to inside the existing directory.
@@ -295,7 +281,7 @@ def rollback(self) -> None:
for new_path, path in self._moves:
try:
- logger.debug('Replacing %s from %s', new_path, path)
+ logger.debug("Replacing %s from %s", new_path, path)
if os.path.isfile(new_path) or os.path.islink(new_path):
os.unlink(new_path)
elif os.path.isdir(new_path):
@@ -315,11 +301,12 @@ def can_rollback(self) -> bool:
class UninstallPathSet:
"""A set of file paths to be removed in the uninstallation of a
requirement."""
- def __init__(self, dist: Distribution) -> None:
- self.paths: Set[str] = set()
+
+ def __init__(self, dist: BaseDistribution) -> None:
+ self._paths: Set[str] = set()
self._refuse: Set[str] = set()
- self.pth: Dict[str, UninstallPthEntries] = {}
- self.dist = dist
+ self._pth: Dict[str, UninstallPthEntries] = {}
+ self._dist = dist
self._moved_paths = StashedUninstallPathSet()
def _permitted(self, path: str) -> bool:
@@ -340,58 +327,55 @@ def add(self, path: str) -> None:
if not os.path.exists(path):
return
if self._permitted(path):
- self.paths.add(path)
+ self._paths.add(path)
else:
self._refuse.add(path)
# __pycache__ files can show up after 'installed-files.txt' is created,
# due to imports
- if os.path.splitext(path)[1] == '.py':
+ if os.path.splitext(path)[1] == ".py":
self.add(cache_from_source(path))
def add_pth(self, pth_file: str, entry: str) -> None:
pth_file = normalize_path(pth_file)
if self._permitted(pth_file):
- if pth_file not in self.pth:
- self.pth[pth_file] = UninstallPthEntries(pth_file)
- self.pth[pth_file].add(entry)
+ if pth_file not in self._pth:
+ self._pth[pth_file] = UninstallPthEntries(pth_file)
+ self._pth[pth_file].add(entry)
else:
self._refuse.add(pth_file)
def remove(self, auto_confirm: bool = False, verbose: bool = False) -> None:
- """Remove paths in ``self.paths`` with confirmation (unless
+ """Remove paths in ``self._paths`` with confirmation (unless
``auto_confirm`` is True)."""
- if not self.paths:
+ if not self._paths:
logger.info(
"Can't uninstall '%s'. No files were found to uninstall.",
- self.dist.project_name,
+ self._dist.raw_name,
)
return
- dist_name_version = (
- self.dist.project_name + "-" + self.dist.version
- )
- logger.info('Uninstalling %s:', dist_name_version)
+ dist_name_version = f"{self._dist.raw_name}-{self._dist.version}"
+ logger.info("Uninstalling %s:", dist_name_version)
with indent_log():
if auto_confirm or self._allowed_to_proceed(verbose):
moved = self._moved_paths
- for_rename = compress_for_rename(self.paths)
+ for_rename = compress_for_rename(self._paths)
for path in sorted(compact(for_rename)):
moved.stash(path)
- logger.verbose('Removing file or directory %s', path)
+ logger.verbose("Removing file or directory %s", path)
- for pth in self.pth.values():
+ for pth in self._pth.values():
pth.remove()
- logger.info('Successfully uninstalled %s', dist_name_version)
+ logger.info("Successfully uninstalled %s", dist_name_version)
def _allowed_to_proceed(self, verbose: bool) -> bool:
- """Display which files would be deleted and prompt for confirmation
- """
+ """Display which files would be deleted and prompt for confirmation"""
def _display(msg: str, paths: Iterable[str]) -> None:
if not paths:
@@ -403,32 +387,32 @@ def _display(msg: str, paths: Iterable[str]) -> None:
logger.info(path)
if not verbose:
- will_remove, will_skip = compress_for_output_listing(self.paths)
+ will_remove, will_skip = compress_for_output_listing(self._paths)
else:
# In verbose mode, display all the files that are going to be
# deleted.
- will_remove = set(self.paths)
+ will_remove = set(self._paths)
will_skip = set()
- _display('Would remove:', will_remove)
- _display('Would not remove (might be manually added):', will_skip)
- _display('Would not remove (outside of prefix):', self._refuse)
+ _display("Would remove:", will_remove)
+ _display("Would not remove (might be manually added):", will_skip)
+ _display("Would not remove (outside of prefix):", self._refuse)
if verbose:
- _display('Will actually move:', compress_for_rename(self.paths))
+ _display("Will actually move:", compress_for_rename(self._paths))
- return ask('Proceed (Y/n)? ', ('y', 'n', '')) != 'n'
+ return ask("Proceed (Y/n)? ", ("y", "n", "")) != "n"
def rollback(self) -> None:
"""Rollback the changes previously made by remove()."""
if not self._moved_paths.can_rollback:
logger.error(
"Can't roll back %s; was not uninstalled",
- self.dist.project_name,
+ self._dist.raw_name,
)
return
- logger.info('Rolling back uninstall of %s', self.dist.project_name)
+ logger.info("Rolling back uninstall of %s", self._dist.raw_name)
self._moved_paths.rollback()
- for pth in self.pth.values():
+ for pth in self._pth.values():
pth.rollback()
def commit(self) -> None:
@@ -436,132 +420,156 @@ def commit(self) -> None:
self._moved_paths.commit()
@classmethod
- def from_dist(cls, dist: Distribution) -> "UninstallPathSet":
- dist_path = normalize_path(dist.location)
- if not dist_is_local(dist):
+ def from_dist(cls, dist: BaseDistribution) -> "UninstallPathSet":
+ dist_location = dist.location
+ info_location = dist.info_location
+ if dist_location is None:
+ logger.info(
+ "Not uninstalling %s since it is not installed",
+ dist.canonical_name,
+ )
+ return cls(dist)
+
+ normalized_dist_location = normalize_path(dist_location)
+ if not dist.local:
logger.info(
"Not uninstalling %s at %s, outside environment %s",
- dist.key,
- dist_path,
+ dist.canonical_name,
+ normalized_dist_location,
sys.prefix,
)
return cls(dist)
- if dist_path in {p for p in {sysconfig.get_path("stdlib"),
- sysconfig.get_path("platstdlib")}
- if p}:
+ if normalized_dist_location in {
+ p
+ for p in {sysconfig.get_path("stdlib"), sysconfig.get_path("platstdlib")}
+ if p
+ }:
logger.info(
"Not uninstalling %s at %s, as it is in the standard library.",
- dist.key,
- dist_path,
+ dist.canonical_name,
+ normalized_dist_location,
)
return cls(dist)
paths_to_remove = cls(dist)
- develop_egg_link = egg_link_path(dist)
- develop_egg_link_egg_info = '{}.egg-info'.format(
- pkg_resources.to_filename(dist.project_name))
- egg_info_exists = dist.egg_info and os.path.exists(dist.egg_info)
- # Special case for distutils installed package
- distutils_egg_info = getattr(dist._provider, 'path', None)
+ develop_egg_link = egg_link_path_from_location(dist.raw_name)
+
+ # Distribution is installed with metadata in a "flat" .egg-info
+ # directory. This means it is not a modern .dist-info installation, an
+ # egg, or legacy editable.
+ setuptools_flat_installation = (
+ dist.installed_with_setuptools_egg_info
+ and info_location is not None
+ and os.path.exists(info_location)
+ # If dist is editable and the location points to a ``.egg-info``,
+ # we are in fact in the legacy editable case.
+ and not info_location.endswith(f"{dist.setuptools_filename}.egg-info")
+ )
# Uninstall cases order do matter as in the case of 2 installs of the
# same package, pip needs to uninstall the currently detected version
- if (egg_info_exists and dist.egg_info.endswith('.egg-info') and
- not dist.egg_info.endswith(develop_egg_link_egg_info)):
- # if dist.egg_info.endswith(develop_egg_link_egg_info), we
- # are in fact in the develop_egg_link case
- paths_to_remove.add(dist.egg_info)
- if dist.has_metadata('installed-files.txt'):
- for installed_file in dist.get_metadata(
- 'installed-files.txt').splitlines():
- path = os.path.normpath(
- os.path.join(dist.egg_info, installed_file)
- )
- paths_to_remove.add(path)
+ if setuptools_flat_installation:
+ if info_location is not None:
+ paths_to_remove.add(info_location)
+ installed_files = dist.iter_declared_entries()
+ if installed_files is not None:
+ for installed_file in installed_files:
+ paths_to_remove.add(os.path.join(dist_location, installed_file))
# FIXME: need a test for this elif block
# occurs with --single-version-externally-managed/--record outside
# of pip
- elif dist.has_metadata('top_level.txt'):
- if dist.has_metadata('namespace_packages.txt'):
- namespaces = dist.get_metadata('namespace_packages.txt')
- else:
+ elif dist.is_file("top_level.txt"):
+ try:
+ namespace_packages = dist.read_text("namespace_packages.txt")
+ except FileNotFoundError:
namespaces = []
+ else:
+ namespaces = namespace_packages.splitlines(keepends=False)
for top_level_pkg in [
- p for p
- in dist.get_metadata('top_level.txt').splitlines()
- if p and p not in namespaces]:
- path = os.path.join(dist.location, top_level_pkg)
+ p
+ for p in dist.read_text("top_level.txt").splitlines()
+ if p and p not in namespaces
+ ]:
+ path = os.path.join(dist_location, top_level_pkg)
paths_to_remove.add(path)
- paths_to_remove.add(path + '.py')
- paths_to_remove.add(path + '.pyc')
- paths_to_remove.add(path + '.pyo')
+ paths_to_remove.add(f"{path}.py")
+ paths_to_remove.add(f"{path}.pyc")
+ paths_to_remove.add(f"{path}.pyo")
- elif distutils_egg_info:
+ elif dist.installed_by_distutils:
raise UninstallationError(
"Cannot uninstall {!r}. It is a distutils installed project "
"and thus we cannot accurately determine which files belong "
"to it which would lead to only a partial uninstall.".format(
- dist.project_name,
+ dist.raw_name,
)
)
- elif dist.location.endswith('.egg'):
+ elif dist.installed_as_egg:
# package installed by easy_install
# We cannot match on dist.egg_name because it can slightly vary
# i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
- paths_to_remove.add(dist.location)
- easy_install_egg = os.path.split(dist.location)[1]
- easy_install_pth = os.path.join(os.path.dirname(dist.location),
- 'easy-install.pth')
- paths_to_remove.add_pth(easy_install_pth, './' + easy_install_egg)
+ paths_to_remove.add(dist_location)
+ easy_install_egg = os.path.split(dist_location)[1]
+ easy_install_pth = os.path.join(
+ os.path.dirname(dist_location),
+ "easy-install.pth",
+ )
+ paths_to_remove.add_pth(easy_install_pth, "./" + easy_install_egg)
- elif egg_info_exists and dist.egg_info.endswith('.dist-info'):
+ elif dist.installed_with_dist_info:
for path in uninstallation_paths(dist):
paths_to_remove.add(path)
elif develop_egg_link:
- # develop egg
+ # PEP 660 modern editable is handled in the ``.dist-info`` case
+ # above, so this only covers the setuptools-style editable.
with open(develop_egg_link) as fh:
link_pointer = os.path.normcase(fh.readline().strip())
- assert (link_pointer == dist.location), (
- 'Egg-link {} does not match installed location of {} '
- '(at {})'.format(
- link_pointer, dist.project_name, dist.location)
+ assert link_pointer == dist_location, (
+ f"Egg-link {link_pointer} does not match installed location of "
+ f"{dist.raw_name} (at {dist_location})"
)
paths_to_remove.add(develop_egg_link)
- easy_install_pth = os.path.join(os.path.dirname(develop_egg_link),
- 'easy-install.pth')
- paths_to_remove.add_pth(easy_install_pth, dist.location)
+ easy_install_pth = os.path.join(
+ os.path.dirname(develop_egg_link), "easy-install.pth"
+ )
+ paths_to_remove.add_pth(easy_install_pth, dist_location)
else:
logger.debug(
- 'Not sure how to uninstall: %s - Check: %s',
- dist, dist.location,
+ "Not sure how to uninstall: %s - Check: %s",
+ dist,
+ dist_location,
)
+ if dist.in_usersite:
+ bin_dir = get_bin_user()
+ else:
+ bin_dir = get_bin_prefix()
+
# find distutils scripts= scripts
- if dist.has_metadata('scripts') and dist.metadata_isdir('scripts'):
- for script in dist.metadata_listdir('scripts'):
- if dist_in_usersite(dist):
- bin_dir = get_bin_user()
- else:
- bin_dir = get_bin_prefix()
- paths_to_remove.add(os.path.join(bin_dir, script))
+ try:
+ for script in dist.iterdir("scripts"):
+ paths_to_remove.add(os.path.join(bin_dir, script.name))
if WINDOWS:
- paths_to_remove.add(os.path.join(bin_dir, script) + '.bat')
-
- # find console_scripts
- _scripts_to_remove = []
- console_scripts = dist.get_entry_map(group='console_scripts')
- for name in console_scripts.keys():
- _scripts_to_remove.extend(_script_names(dist, name, False))
- # find gui_scripts
- gui_scripts = dist.get_entry_map(group='gui_scripts')
- for name in gui_scripts.keys():
- _scripts_to_remove.extend(_script_names(dist, name, True))
-
- for s in _scripts_to_remove:
+ paths_to_remove.add(os.path.join(bin_dir, f"{script.name}.bat"))
+ except (FileNotFoundError, NotADirectoryError):
+ pass
+
+ # find console_scripts and gui_scripts
+ def iter_scripts_to_remove(
+ dist: BaseDistribution,
+ bin_dir: str,
+ ) -> Iterator[str]:
+ for entry_point in dist.iter_entry_points():
+ if entry_point.group == "console_scripts":
+ yield from _script_names(bin_dir, entry_point.name, False)
+ elif entry_point.group == "gui_scripts":
+ yield from _script_names(bin_dir, entry_point.name, True)
+
+ for s in iter_scripts_to_remove(dist, bin_dir):
paths_to_remove.add(s)
return paths_to_remove
@@ -585,45 +593,41 @@ def add(self, entry: str) -> None:
# have more than "\\sever\share". Valid examples: "\\server\share\" or
# "\\server\share\folder".
if WINDOWS and not os.path.splitdrive(entry)[0]:
- entry = entry.replace('\\', '/')
+ entry = entry.replace("\\", "/")
self.entries.add(entry)
def remove(self) -> None:
- logger.verbose('Removing pth entries from %s:', self.file)
+ logger.verbose("Removing pth entries from %s:", self.file)
# If the file doesn't exist, log a warning and return
if not os.path.isfile(self.file):
- logger.warning(
- "Cannot remove entries from nonexistent file %s", self.file
- )
+ logger.warning("Cannot remove entries from nonexistent file %s", self.file)
return
- with open(self.file, 'rb') as fh:
+ with open(self.file, "rb") as fh:
# windows uses '\r\n' with py3k, but uses '\n' with py2.x
lines = fh.readlines()
self._saved_lines = lines
- if any(b'\r\n' in line for line in lines):
- endline = '\r\n'
+ if any(b"\r\n" in line for line in lines):
+ endline = "\r\n"
else:
- endline = '\n'
+ endline = "\n"
# handle missing trailing newline
if lines and not lines[-1].endswith(endline.encode("utf-8")):
lines[-1] = lines[-1] + endline.encode("utf-8")
for entry in self.entries:
try:
- logger.verbose('Removing entry: %s', entry)
+ logger.verbose("Removing entry: %s", entry)
lines.remove((entry + endline).encode("utf-8"))
except ValueError:
pass
- with open(self.file, 'wb') as fh:
+ with open(self.file, "wb") as fh:
fh.writelines(lines)
def rollback(self) -> bool:
if self._saved_lines is None:
- logger.error(
- 'Cannot roll back changes to %s, none were made', self.file
- )
+ logger.error("Cannot roll back changes to %s, none were made", self.file)
return False
- logger.debug('Rolling %s back to previous state', self.file)
- with open(self.file, 'wb') as fh:
+ logger.debug("Rolling %s back to previous state", self.file)
+ with open(self.file, "wb") as fh:
fh.writelines(self._saved_lines)
return True
diff --git a/pipenv/patched/notpip/_internal/resolution/base.py b/pipenv/patched/notpip/_internal/resolution/base.py
index fa43475a60..d912b22dca 100644
--- a/pipenv/patched/notpip/_internal/resolution/base.py
+++ b/pipenv/patched/notpip/_internal/resolution/base.py
@@ -1,9 +1,11 @@
-from typing import Callable, List
+from typing import Callable, List, Optional
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.req.req_set import RequirementSet
-InstallRequirementProvider = Callable[[str, InstallRequirement], InstallRequirement]
+InstallRequirementProvider = Callable[
+ [str, Optional[InstallRequirement]], InstallRequirement
+]
class BaseResolver:
diff --git a/pipenv/patched/notpip/_internal/resolution/legacy/resolver.py b/pipenv/patched/notpip/_internal/resolution/legacy/resolver.py
index 8e52bcb085..a434e16d47 100644
--- a/pipenv/patched/notpip/_internal/resolution/legacy/resolver.py
+++ b/pipenv/patched/notpip/_internal/resolution/legacy/resolver.py
@@ -20,7 +20,7 @@
from typing import DefaultDict, Iterable, List, Optional, Set, Tuple
from pipenv.patched.notpip._vendor.packaging import specifiers
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
+from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
from pipenv.patched.notpip._internal.cache import WheelCache
from pipenv.patched.notpip._internal.exceptions import (
@@ -28,9 +28,11 @@
DistributionNotFound,
HashError,
HashErrors,
+ NoneMetadataError,
UnsupportedPythonVersion,
)
from pipenv.patched.notpip._internal.index.package_finder import PackageFinder
+from pipenv.patched.notpip._internal.metadata import BaseDistribution
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.operations.prepare import RequirementPreparer
from pipenv.patched.notpip._internal.req.req_install import (
@@ -41,8 +43,8 @@
from pipenv.patched.notpip._internal.resolution.base import BaseResolver, InstallRequirementProvider
from pipenv.patched.notpip._internal.utils.compatibility_tags import get_supported
from pipenv.patched.notpip._internal.utils.logging import indent_log
-from pipenv.patched.notpip._internal.utils.misc import dist_in_usersite, normalize_version_info
-from pipenv.patched.notpip._internal.utils.packaging import check_requires_python, get_requires_python
+from pipenv.patched.notpip._internal.utils.misc import normalize_version_info
+from pipenv.patched.notpip._internal.utils.packaging import check_requires_python
logger = logging.getLogger(__name__)
@@ -50,7 +52,7 @@
def _check_dist_requires_python(
- dist: Distribution,
+ dist: BaseDistribution,
version_info: Tuple[int, int, int],
ignore_requires_python: bool = False,
) -> None:
@@ -66,14 +68,21 @@ def _check_dist_requires_python(
:raises UnsupportedPythonVersion: When the given Python version isn't
compatible.
"""
- requires_python = get_requires_python(dist)
+ # This idiosyncratically converts the SpecifierSet to str and let
+ # check_requires_python then parse it again into SpecifierSet. But this
+ # is the legacy resolver so I'm just not going to bother refactoring.
+ try:
+ requires_python = str(dist.requires_python)
+ except FileNotFoundError as e:
+ raise NoneMetadataError(dist, str(e))
try:
is_compatible = check_requires_python(
- requires_python, version_info=version_info
+ requires_python,
+ version_info=version_info,
)
except specifiers.InvalidSpecifier as exc:
logger.warning(
- "Package %r has an invalid Requires-Python: %s", dist.project_name, exc
+ "Package %r has an invalid Requires-Python: %s", dist.raw_name, exc
)
return
@@ -84,7 +93,7 @@ def _check_dist_requires_python(
if ignore_requires_python:
logger.debug(
"Ignoring failed Requires-Python check for package %r: %s not in %r",
- dist.project_name,
+ dist.raw_name,
version,
requires_python,
)
@@ -92,7 +101,7 @@ def _check_dist_requires_python(
raise UnsupportedPythonVersion(
"Package {!r} requires a different Python: {} not in {!r}".format(
- dist.project_name, version, requires_python
+ dist.raw_name, version, requires_python
)
)
@@ -194,7 +203,7 @@ def _set_req_to_reinstall(self, req: InstallRequirement) -> None:
"""
# Don't uninstall the conflict if doing a user install and the
# conflict is not a user install.
- if not self.use_user_site or dist_in_usersite(req.satisfied_by):
+ if not self.use_user_site or req.satisfied_by.in_usersite:
req.should_reinstall = True
req.satisfied_by = None
@@ -303,7 +312,7 @@ def _populate_link(self, req: InstallRequirement) -> None:
req.original_link_is_in_wheel_cache = True
req.link = cache_entry.link
- def _get_dist_for(self, req: InstallRequirement) -> Distribution:
+ def _get_dist_for(self, req: InstallRequirement) -> BaseDistribution:
"""Takes a InstallRequirement and returns a single AbstractDist \
representing a prepared variant of the same.
"""
@@ -378,11 +387,11 @@ def _resolve_one(
more_reqs: List[InstallRequirement] = []
- def add_req(subreq: Distribution, extras_requested: Iterable[str]) -> None:
- sub_install_req = self._make_install_req(
- str(subreq),
- req_to_install,
- )
+ def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None:
+ # This idiosyncratically converts the Requirement to str and let
+ # make_install_req then parse it again into Requirement. But this is
+ # the legacy resolver so I'm just not going to bother refactoring.
+ sub_install_req = self._make_install_req(str(subreq), req_to_install)
parent_req_name = req_to_install.name
to_scan_again, add_to_parent = requirement_set.add_requirement(
sub_install_req,
@@ -410,15 +419,20 @@ def add_req(subreq: Distribution, extras_requested: Iterable[str]) -> None:
",".join(req_to_install.extras),
)
missing_requested = sorted(
- set(req_to_install.extras) - set(dist.extras)
+ set(req_to_install.extras) - set(dist.iter_provided_extras())
)
for missing in missing_requested:
- logger.warning("%s does not provide the extra '%s'", dist, missing)
+ logger.warning(
+ "%s %s does not provide the extra '%s'",
+ dist.raw_name,
+ dist.version,
+ missing,
+ )
available_requested = sorted(
- set(dist.extras) & set(req_to_install.extras)
+ set(dist.iter_provided_extras()) & set(req_to_install.extras)
)
- for subreq in dist.requires(available_requested):
+ for subreq in dist.iter_dependencies(available_requested):
add_req(subreq, extras_requested=available_requested)
return more_reqs
diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/base.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/base.py
index 3a2ad094d9..b21725ff3b 100644
--- a/pipenv/patched/notpip/_internal/resolution/resolvelib/base.py
+++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/base.py
@@ -36,11 +36,8 @@ def from_ireq(cls, ireq: InstallRequirement) -> "Constraint":
links = frozenset([ireq.link]) if ireq.link else frozenset()
return Constraint(ireq.specifier, ireq.hashes(trust_internet=False), links)
- def __nonzero__(self) -> bool:
- return bool(self.specifier) or bool(self.hashes) or bool(self.links)
-
def __bool__(self) -> bool:
- return self.__nonzero__()
+ return bool(self.specifier) or bool(self.hashes) or bool(self.links)
def __and__(self, other: InstallRequirement) -> "Constraint":
if not isinstance(other, InstallRequirement):
diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py
index 6fdb59b711..9cc6a0d63b 100644
--- a/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py
+++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/candidates.py
@@ -2,13 +2,15 @@
import sys
from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast
-from pipenv.patched.notpip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pipenv.patched.notpip._vendor.packaging.version import Version
-from pipenv.patched.notpip._vendor.packaging.version import parse as parse_version
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
-from pipenv.patched.notpip._internal.exceptions import HashError, MetadataInconsistent
+from pipenv.patched.notpip._internal.exceptions import (
+ HashError,
+ InstallationSubprocessError,
+ MetadataInconsistent,
+)
+from pipenv.patched.notpip._internal.metadata import BaseDistribution
from pipenv.patched.notpip._internal.models.link import Link, links_equivalent
from pipenv.patched.notpip._internal.models.wheel import Wheel
from pipenv.patched.notpip._internal.req.constructors import (
@@ -16,8 +18,7 @@
install_req_from_line,
)
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
-from pipenv.patched.notpip._internal.utils.misc import dist_is_editable, normalize_version_info
-from pipenv.patched.notpip._internal.utils.packaging import get_requires_python
+from pipenv.patched.notpip._internal.utils.misc import normalize_version_info
from .base import Candidate, CandidateVersion, Requirement, format_name
@@ -85,6 +86,7 @@ def make_install_req_from_editable(
use_pep517=template.use_pep517,
isolated=template.isolated,
constraint=template.constraint,
+ permit_editable_wheels=template.permit_editable_wheels,
options=dict(
install_options=template.install_options,
global_options=template.global_options,
@@ -93,16 +95,15 @@ def make_install_req_from_editable(
)
-def make_install_req_from_dist(
- dist: Distribution, template: InstallRequirement
+def _make_install_req_from_dist(
+ dist: BaseDistribution, template: InstallRequirement
) -> InstallRequirement:
- project_name = canonicalize_name(dist.project_name)
if template.req:
line = str(template.req)
elif template.link:
- line = f"{project_name} @ {template.link.url}"
+ line = f"{dist.canonical_name} @ {template.link.url}"
else:
- line = f"{project_name}=={dist.parsed_version}"
+ line = f"{dist.canonical_name}=={dist.version}"
ireq = install_req_from_line(
line,
user_supplied=template.user_supplied,
@@ -136,6 +137,7 @@ class exposes appropriate information to the resolver.
found remote link (e.g. from pypi.org).
"""
+ dist: BaseDistribution
is_installed = False
def __init__(
@@ -180,7 +182,7 @@ def source_link(self) -> Optional[Link]:
def project_name(self) -> NormalizedName:
"""The normalised name of the project the candidate refers to"""
if self._name is None:
- self._name = canonicalize_name(self.dist.project_name)
+ self._name = self.dist.canonical_name
return self._name
@property
@@ -190,7 +192,7 @@ def name(self) -> str:
@property
def version(self) -> CandidateVersion:
if self._version is None:
- self._version = parse_version(self.dist.version)
+ self._version = self.dist.version
return self._version
def format_for_error(self) -> str:
@@ -200,29 +202,27 @@ def format_for_error(self) -> str:
self._link.file_path if self._link.is_file else self._link,
)
- def _prepare_distribution(self) -> Distribution:
+ def _prepare_distribution(self) -> BaseDistribution:
raise NotImplementedError("Override in subclass")
- def _check_metadata_consistency(self, dist: Distribution) -> None:
+ def _check_metadata_consistency(self, dist: BaseDistribution) -> None:
"""Check for consistency of project name and version of dist."""
- canonical_name = canonicalize_name(dist.project_name)
- if self._name is not None and self._name != canonical_name:
+ if self._name is not None and self._name != dist.canonical_name:
raise MetadataInconsistent(
self._ireq,
"name",
self._name,
- dist.project_name,
+ dist.canonical_name,
)
- parsed_version = parse_version(dist.version)
- if self._version is not None and self._version != parsed_version:
+ if self._version is not None and self._version != dist.version:
raise MetadataInconsistent(
self._ireq,
"version",
str(self._version),
- dist.version,
+ str(dist.version),
)
- def _prepare(self) -> Distribution:
+ def _prepare(self) -> BaseDistribution:
try:
dist = self._prepare_distribution()
except HashError as e:
@@ -231,32 +231,22 @@ def _prepare(self) -> Distribution:
# offending line to the user.
e.req = self._ireq
raise
+ except InstallationSubprocessError as exc:
+ # The output has been presented already, so don't duplicate it.
+ exc.context = "See above for output."
+ raise
+
self._check_metadata_consistency(dist)
return dist
- def _get_requires_python_dependency(self) -> Optional[Requirement]:
- requires_python = get_requires_python(self.dist)
- if requires_python is None:
- return None
- try:
- spec = SpecifierSet(requires_python)
- except InvalidSpecifier as e:
- message = "Package %r has an invalid Requires-Python: %s"
- logger.warning(message, self.name, e)
- return None
- return self._factory.make_requires_python_requirement(spec)
-
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
- requires = self.dist.requires() if with_requires else ()
+ requires = self.dist.iter_dependencies() if with_requires else ()
for r in requires:
yield self._factory.make_requirement_from_spec(str(r), self._ireq)
- yield self._get_requires_python_dependency()
+ yield self._factory.make_requires_python_requirement(self.dist.requires_python)
def get_install_requirement(self) -> Optional[InstallRequirement]:
- ireq = self._ireq
- if self._version and ireq.req and not ireq.req.url:
- ireq.req.specifier = SpecifierSet(f"=={self._version}")
- return ireq
+ return self._ireq
class LinkCandidate(_InstallRequirementBackedCandidate):
@@ -304,10 +294,9 @@ def __init__(
version=version,
)
- def _prepare_distribution(self) -> Distribution:
- return self._factory.preparer.prepare_linked_requirement(
- self._ireq, parallel_builds=True
- )
+ def _prepare_distribution(self) -> BaseDistribution:
+ preparer = self._factory.preparer
+ return preparer.prepare_linked_requirement(self._ireq, parallel_builds=True)
class EditableCandidate(_InstallRequirementBackedCandidate):
@@ -330,7 +319,7 @@ def __init__(
version=version,
)
- def _prepare_distribution(self) -> Distribution:
+ def _prepare_distribution(self) -> BaseDistribution:
return self._factory.preparer.prepare_editable_requirement(self._ireq)
@@ -340,17 +329,17 @@ class AlreadyInstalledCandidate(Candidate):
def __init__(
self,
- dist: Distribution,
+ dist: BaseDistribution,
template: InstallRequirement,
factory: "Factory",
) -> None:
self.dist = dist
- self._ireq = make_install_req_from_dist(dist, template)
+ self._ireq = _make_install_req_from_dist(dist, template)
self._factory = factory
# This is just logging some messages, so we can do it eagerly.
# The returned dist would be exactly the same as self.dist because we
- # set satisfied_by in make_install_req_from_dist.
+ # set satisfied_by in _make_install_req_from_dist.
# TODO: Supply reason based on force_reinstall and upgrade_strategy.
skip_reason = "already satisfied"
factory.preparer.prepare_installed_requirement(self._ireq, skip_reason)
@@ -374,7 +363,7 @@ def __eq__(self, other: Any) -> bool:
@property
def project_name(self) -> NormalizedName:
- return canonicalize_name(self.dist.project_name)
+ return self.dist.canonical_name
@property
def name(self) -> str:
@@ -382,11 +371,11 @@ def name(self) -> str:
@property
def version(self) -> CandidateVersion:
- return parse_version(self.dist.version)
+ return self.dist.version
@property
def is_editable(self) -> bool:
- return dist_is_editable(self.dist)
+ return self.dist.editable
def format_for_error(self) -> str:
return f"{self.name} {self.version} (Installed)"
@@ -394,7 +383,7 @@ def format_for_error(self) -> str:
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
if not with_requires:
return
- for r in self.dist.requires():
+ for r in self.dist.iter_dependencies():
yield self._factory.make_requirement_from_spec(str(r), self._ireq)
def get_install_requirement(self) -> Optional[InstallRequirement]:
@@ -494,8 +483,8 @@ def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requiremen
# The user may have specified extras that the candidate doesn't
# support. We ignore any unsupported extras here.
- valid_extras = self.extras.intersection(self.base.dist.extras)
- invalid_extras = self.extras.difference(self.base.dist.extras)
+ valid_extras = self.extras.intersection(self.base.dist.iter_provided_extras())
+ invalid_extras = self.extras.difference(self.base.dist.iter_provided_extras())
for extra in sorted(invalid_extras):
logger.warning(
"%s %s does not provide the extra '%s'",
@@ -504,7 +493,7 @@ def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requiremen
extra,
)
- for r in self.base.dist.requires(valid_extras):
+ for r in self.base.dist.iter_dependencies(valid_extras):
requirement = factory.make_requirement_from_spec(
str(r), self.base._ireq, valid_extras
)
diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/factory.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/factory.py
index 9b216a1ba5..c2b17cf9a8 100644
--- a/pipenv/patched/notpip/_internal/resolution/resolvelib/factory.py
+++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/factory.py
@@ -19,7 +19,6 @@
)
from pipenv.patched.notpip._vendor.packaging.requirements import InvalidRequirement
-from pipenv.patched.notpip._vendor.packaging.requirements import Requirement as PackagingRequirement
from pipenv.patched.notpip._vendor.packaging.specifiers import SpecifierSet
from pipenv.patched.notpip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pipenv.patched.notpip._vendor.resolvelib import ResolutionImpossible
@@ -46,6 +45,7 @@
from pipenv.patched.notpip._internal.resolution.base import InstallRequirementProvider
from pipenv.patched.notpip._internal.utils.compatibility_tags import get_supported
from pipenv.patched.notpip._internal.utils.hashes import Hashes
+from pipenv.patched.notpip._internal.utils.packaging import get_requirement
from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv
from .base import Candidate, CandidateVersion, Constraint, Requirement
@@ -97,6 +97,7 @@ def __init__(
force_reinstall: bool,
ignore_installed: bool,
ignore_requires_python: bool,
+ suppress_build_failures: bool,
py_version_info: Optional[Tuple[int, ...]] = None,
) -> None:
self._finder = finder
@@ -107,6 +108,7 @@ def __init__(
self._use_user_site = use_user_site
self._force_reinstall = force_reinstall
self._ignore_requires_python = ignore_requires_python
+ self._suppress_build_failures = suppress_build_failures
self._build_failures: Cache[InstallationError] = {}
self._link_candidate_cache: Cache[LinkCandidate] = {}
@@ -158,10 +160,7 @@ def _make_candidate_from_dist(
try:
base = self._installed_candidate_cache[dist.canonical_name]
except KeyError:
- from pipenv.patched.notpip._internal.metadata.pkg_resources import Distribution as _Dist
-
- compat_dist = cast(_Dist, dist)._dist
- base = AlreadyInstalledCandidate(compat_dist, template, factory=self)
+ base = AlreadyInstalledCandidate(dist, template, factory=self)
self._installed_candidate_cache[dist.canonical_name] = base
if not extras:
return base
@@ -193,10 +192,22 @@ def _make_candidate_from_link(
name=name,
version=version,
)
- except (InstallationSubprocessError, MetadataInconsistent) as e:
- logger.warning("Discarding %s. %s", link, e)
+ except MetadataInconsistent as e:
+ logger.info(
+ "Discarding [blue underline]%s[/]: [yellow]%s[reset]",
+ link,
+ e,
+ extra={"markup": True},
+ )
+ self._build_failures[link] = e
+ return None
+ except InstallationSubprocessError as e:
+ if not self._suppress_build_failures:
+ raise
+ logger.warning("Discarding %s due to build failure: %s", link, e)
self._build_failures[link] = e
return None
+
base: BaseCandidate = self._editable_candidate_cache[link]
else:
if link not in self._link_candidate_cache:
@@ -208,8 +219,19 @@ def _make_candidate_from_link(
name=name,
version=version,
)
- except (InstallationSubprocessError, MetadataInconsistent) as e:
- logger.warning("Discarding %s. %s", link, e)
+ except MetadataInconsistent as e:
+ logger.info(
+ "Discarding [blue underline]%s[/]: [yellow]%s[reset]",
+ link,
+ e,
+ extra={"markup": True},
+ )
+ self._build_failures[link] = e
+ return None
+ except InstallationSubprocessError as e:
+ if not self._suppress_build_failures:
+ raise
+ logger.warning("Discarding %s due to build failure: %s", link, e)
self._build_failures[link] = e
return None
base = self._link_candidate_cache[link]
@@ -263,7 +285,7 @@ def _get_installed_candidate() -> Optional[Candidate]:
extras=extras,
template=template,
)
- # The candidate is a known incompatiblity. Don't use it.
+ # The candidate is a known incompatibility. Don't use it.
if id(candidate) in incompatible_ids:
return None
return candidate
@@ -276,14 +298,27 @@ def iter_index_candidate_infos() -> Iterator[IndexCandidateInfo]:
)
icans = list(result.iter_applicable())
- # PEP 592: Yanked releases must be ignored unless only yanked
- # releases can satisfy the version range. So if this is false,
- # all yanked icans need to be skipped.
+ # PEP 592: Yanked releases are ignored unless the specifier
+ # explicitly pins a version (via '==' or '===') that can be
+ # solely satisfied by a yanked release.
all_yanked = all(ican.link.is_yanked for ican in icans)
+ def is_pinned(specifier: SpecifierSet) -> bool:
+ for sp in specifier:
+ if sp.operator == "===":
+ return True
+ if sp.operator != "==":
+ continue
+ if sp.version.endswith(".*"):
+ continue
+ return True
+ return False
+
+ pinned = is_pinned(specifier)
+
# PackageFinder returns earlier versions first, so we reverse.
for ican in reversed(icans):
- if not all_yanked and ican.link.is_yanked:
+ if not (all_yanked and pinned) and ican.link.is_yanked:
continue
func = functools.partial(
self._make_candidate_from_link,
@@ -350,7 +385,7 @@ def _iter_candidates_from_constraints(
def find_candidates(
self,
identifier: str,
- requirements: Mapping[str, Iterator[Requirement]],
+ requirements: Mapping[str, Iterable[Requirement]],
incompatibilities: Mapping[str, Iterator[Candidate]],
constraint: Constraint,
prefers_installed: bool,
@@ -368,7 +403,7 @@ def find_candidates(
# If the current identifier contains extras, add explicit candidates
# from entries from extra-less identifier.
with contextlib.suppress(InvalidRequirement):
- parsed_requirement = PackagingRequirement(identifier)
+ parsed_requirement = get_requirement(identifier)
explicit_candidates.update(
self._iter_explicit_candidates_from_base(
requirements.get(parsed_requirement.name, ()),
@@ -377,7 +412,7 @@ def find_candidates(
)
# Add explicit candidates from constraints. We only do this if there are
- # kown ireqs, which represent requirements not already explicit. If
+ # known ireqs, which represent requirements not already explicit. If
# there are no ireqs, we're constraining already-explicit requirements,
# which is handled later when we return the explicit candidates.
if ireqs:
@@ -487,16 +522,20 @@ def make_requirement_from_candidate(
def make_requirement_from_spec(
self,
specifier: str,
- comes_from: InstallRequirement,
+ comes_from: Optional[InstallRequirement],
requested_extras: Iterable[str] = (),
) -> Optional[Requirement]:
ireq = self._make_install_req_from_spec(specifier, comes_from)
return self._make_requirement_from_install_req(ireq, requested_extras)
def make_requires_python_requirement(
- self, specifier: Optional[SpecifierSet]
+ self,
+ specifier: SpecifierSet,
) -> Optional[Requirement]:
- if self._ignore_requires_python or specifier is None:
+ if self._ignore_requires_python:
+ return None
+ # Don't bother creating a dependency for an empty Requires-Python.
+ if not str(specifier):
return None
return RequiresPythonRequirement(specifier, self._python_candidate)
@@ -614,7 +653,7 @@ def get_installation_error(
]
if requires_python_causes:
# The comprehension above makes sure all Requirement instances are
- # RequiresPythonRequirement, so let's cast for convinience.
+ # RequiresPythonRequirement, so let's cast for convenience.
return self._report_requires_python_error(
cast("Sequence[ConflictCause]", requires_python_causes),
)
@@ -695,6 +734,6 @@ def describe_trigger(parent: Candidate) -> str:
return DistributionNotFound(
"ResolutionImpossible: for help visit "
- "https://pip.pypa.io/en/latest/user_guide/"
- "#fixing-conflicting-dependencies"
+ "https://pip.pypa.io/en/latest/topics/dependency-resolution/"
+ "#dealing-with-dependency-conflicts"
)
diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/found_candidates.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/found_candidates.py
index e0514d97ef..da284004a3 100644
--- a/pipenv/patched/notpip/_internal/resolution/resolvelib/found_candidates.py
+++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/found_candidates.py
@@ -9,15 +9,30 @@
"""
import functools
-from typing import Callable, Iterator, Optional, Set, Tuple
+from collections.abc import Sequence
+from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Set, Tuple
from pipenv.patched.notpip._vendor.packaging.version import _BaseVersion
-from pipenv.patched.notpip._vendor.six.moves import collections_abc # type: ignore
from .base import Candidate
IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]]
+if TYPE_CHECKING:
+ SequenceCandidate = Sequence[Candidate]
+else:
+ # For compatibility: Python before 3.9 does not support using [] on the
+ # Sequence class.
+ #
+ # >>> from collections.abc import Sequence
+ # >>> Sequence[str]
+ # Traceback (most recent call last):
+ # File "", line 1, in
+ # TypeError: 'ABCMeta' object is not subscriptable
+ #
+ # TODO: Remove this block after dropping Python 3.8 support.
+ SequenceCandidate = Sequence
+
def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
"""Iterator for ``FoundCandidates``.
@@ -90,7 +105,7 @@ def _iter_built_with_inserted(
yield installed
-class FoundCandidates(collections_abc.Sequence):
+class FoundCandidates(SequenceCandidate):
"""A lazy sequence to provide candidates to the resolver.
The intended usage is to return this from `find_matches()` so the resolver
@@ -111,7 +126,7 @@ def __init__(
self._prefers_installed = prefers_installed
self._incompatible_ids = incompatible_ids
- def __getitem__(self, index: int) -> Candidate:
+ def __getitem__(self, index: Any) -> Any:
# Implemented to satisfy the ABC check. This is not needed by the
# resolver, and should not be used by the provider either (for
# performance reasons).
@@ -138,5 +153,3 @@ def __bool__(self) -> bool:
if self._prefers_installed and self._installed:
return True
return any(self)
-
- __nonzero__ = __bool__ # XXX: Python 2.
diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/provider.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/provider.py
index daaa437aec..12fae1f089 100644
--- a/pipenv/patched/notpip/_internal/resolution/resolvelib/provider.py
+++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/provider.py
@@ -1,6 +1,15 @@
import collections
import math
-from typing import TYPE_CHECKING, Dict, Iterable, Iterator, Mapping, Sequence, Union
+from typing import (
+ TYPE_CHECKING,
+ Dict,
+ Iterable,
+ Iterator,
+ Mapping,
+ Sequence,
+ TypeVar,
+ Union,
+)
from pipenv.patched.notpip._vendor.resolvelib.providers import AbstractProvider
@@ -37,6 +46,35 @@
# services to those objects (access to pip's finder and preparer).
+D = TypeVar("D")
+V = TypeVar("V")
+
+
+def _get_with_identifier(
+ mapping: Mapping[str, V],
+ identifier: str,
+ default: D,
+) -> Union[D, V]:
+ """Get item from a package name lookup mapping with a resolver identifier.
+
+ This extra logic is needed when the target mapping is keyed by package
+ name, which cannot be directly looked up with an identifier (which may
+ contain requested extras). Additional logic is added to also look up a value
+ by "cleaning up" the extras from the identifier.
+ """
+ if identifier in mapping:
+ return mapping[identifier]
+ # HACK: Theoretically we should check whether this identifier is a valid
+ # "NAME[EXTRAS]" format, and parse out the name part with packaging or
+ # some regular expression. But since pip's resolver only spits out three
+ # kinds of identifiers: normalized PEP 503 names, normalized names plus
+ # extras, and Requires-Python, we can cheat a bit here.
+ name, open_bracket, _ = identifier.partition("[")
+ if open_bracket and name in mapping:
+ return mapping[name]
+ return default
+
+
class PipProvider(_ProviderBase):
"""Pip's provider implementation for resolvelib.
@@ -66,12 +104,13 @@ def __init__(
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
return requirement_or_candidate.name
- def get_preference(
+ def get_preference( # type: ignore
self,
identifier: str,
resolutions: Mapping[str, Candidate],
candidates: Mapping[str, Iterator[Candidate]],
- information: Mapping[str, Iterator["PreferenceInformation"]],
+ information: Mapping[str, Iterable["PreferenceInformation"]],
+ backtrack_causes: Sequence["PreferenceInformation"],
) -> "Preference":
"""Produce a sort key for given requirement based on preference.
@@ -112,9 +151,9 @@ def get_preference(
for _, parent in information[identifier]
)
inferred_depth = min(d for d in parent_depths) + 1.0
- self._known_depths[identifier] = inferred_depth
else:
inferred_depth = 1.0
+ self._known_depths[identifier] = inferred_depth
requested_order = self._user_requested.get(identifier, math.inf)
@@ -128,43 +167,34 @@ def get_preference(
# (Most projects specify it only to request for an installer feature,
# which does not work, but that's another topic.) Intentionally
# delaying Setuptools helps reduce branches the resolver has to check.
- # This serves as a temporary fix for issues like "apache-airlfow[all]"
+ # This serves as a temporary fix for issues like "apache-airflow[all]"
# while we work on "proper" branch pruning techniques.
delay_this = identifier == "setuptools"
+ # Prefer the causes of backtracking on the assumption that the problem
+ # resolving the dependency tree is related to the failures that caused
+ # the backtracking
+ backtrack_cause = self.is_backtrack_cause(identifier, backtrack_causes)
+
return (
not requires_python,
delay_this,
not direct,
not pinned,
+ not backtrack_cause,
inferred_depth,
requested_order,
not unfree,
identifier,
)
- def _get_constraint(self, identifier: str) -> Constraint:
- if identifier in self._constraints:
- return self._constraints[identifier]
-
- # HACK: Theoratically we should check whether this identifier is a valid
- # "NAME[EXTRAS]" format, and parse out the name part with packaging or
- # some regular expression. But since pip's resolver only spits out
- # three kinds of identifiers: normalized PEP 503 names, normalized names
- # plus extras, and Requires-Python, we can cheat a bit here.
- name, open_bracket, _ = identifier.partition("[")
- if open_bracket and name in self._constraints:
- return self._constraints[name]
-
- return Constraint.empty()
-
def find_matches(
self,
identifier: str,
requirements: Mapping[str, Iterator[Requirement]],
incompatibilities: Mapping[str, Iterator[Candidate]],
) -> Iterable[Candidate]:
- def _eligible_for_upgrade(name: str) -> bool:
+ def _eligible_for_upgrade(identifier: str) -> bool:
"""Are upgrades allowed for this project?
This checks the upgrade strategy, and whether the project was one
@@ -178,13 +208,23 @@ def _eligible_for_upgrade(name: str) -> bool:
if self._upgrade_strategy == "eager":
return True
elif self._upgrade_strategy == "only-if-needed":
- return name in self._user_requested
+ user_order = _get_with_identifier(
+ self._user_requested,
+ identifier,
+ default=None,
+ )
+ return user_order is not None
return False
+ constraint = _get_with_identifier(
+ self._constraints,
+ identifier,
+ default=Constraint.empty(),
+ )
return self._factory.find_candidates(
identifier=identifier,
requirements=requirements,
- constraint=self._get_constraint(identifier),
+ constraint=constraint,
prefers_installed=(not _eligible_for_upgrade(identifier)),
incompatibilities=incompatibilities,
)
@@ -195,3 +235,14 @@ def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> boo
def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]:
with_requires = not self._ignore_dependencies
return [r for r in candidate.iter_dependencies(with_requires) if r is not None]
+
+ @staticmethod
+ def is_backtrack_cause(
+ identifier: str, backtrack_causes: Sequence["PreferenceInformation"]
+ ) -> bool:
+ for backtrack_cause in backtrack_causes:
+ if identifier == backtrack_cause.requirement.name:
+ return True
+ if backtrack_cause.parent and identifier == backtrack_cause.parent.name:
+ return True
+ return False
diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/reporter.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/reporter.py
index e0155df043..f01d6535a9 100644
--- a/pipenv/patched/notpip/_internal/resolution/resolvelib/reporter.py
+++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/reporter.py
@@ -27,9 +27,8 @@ def __init__(self) -> None:
13: (
"This is taking longer than usual. You might need to provide "
"the dependency resolver with stricter constraints to reduce "
- "runtime. If you want to abort this run, you can press "
- "Ctrl + C to do so. To improve how pip performs, tell us what "
- "happened here: https://pip.pypa.io/surveys/backtracking"
+ "runtime. See https://pip.pypa.io/warnings/backtracking for "
+ "guidance. If you want to abort this run, press Ctrl + C."
),
}
diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/requirements.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/requirements.py
index 3579570665..cfd7bd68bb 100644
--- a/pipenv/patched/notpip/_internal/resolution/resolvelib/requirements.py
+++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/requirements.py
@@ -21,12 +21,12 @@ def __repr__(self) -> str:
@property
def project_name(self) -> NormalizedName:
- # No need to canonicalise - the candidate did this
+ # No need to canonicalize - the candidate did this
return self.candidate.project_name
@property
def name(self) -> str:
- # No need to canonicalise - the candidate did this
+ # No need to canonicalize - the candidate did this
return self.candidate.name
def format_for_error(self) -> str:
diff --git a/pipenv/patched/notpip/_internal/resolution/resolvelib/resolver.py b/pipenv/patched/notpip/_internal/resolution/resolvelib/resolver.py
index 99d5984f41..ec64dcc555 100644
--- a/pipenv/patched/notpip/_internal/resolution/resolvelib/resolver.py
+++ b/pipenv/patched/notpip/_internal/resolution/resolvelib/resolver.py
@@ -19,8 +19,6 @@
PipDebuggingReporter,
PipReporter,
)
-from pipenv.patched.notpip._internal.utils.deprecation import deprecated
-from pipenv.patched.notpip._internal.utils.filetypes import is_archive_file
from .base import Candidate, Requirement
from .factory import Factory
@@ -49,6 +47,7 @@ def __init__(
ignore_requires_python: bool,
force_reinstall: bool,
upgrade_strategy: str,
+ suppress_build_failures: bool,
py_version_info: Optional[Tuple[int, ...]] = None,
):
super().__init__()
@@ -63,6 +62,7 @@ def __init__(
force_reinstall=force_reinstall,
ignore_installed=ignore_installed,
ignore_requires_python=ignore_requires_python,
+ suppress_build_failures=suppress_build_failures,
py_version_info=py_version_info,
)
self.ignore_dependencies = ignore_dependencies
@@ -136,25 +136,6 @@ def resolve(
)
continue
- looks_like_sdist = (
- is_archive_file(candidate.source_link.file_path)
- and candidate.source_link.ext != ".zip"
- )
- if looks_like_sdist:
- # is a local sdist -- show a deprecation warning!
- reason = (
- "Source distribution is being reinstalled despite an "
- "installed package having the same name and version as "
- "the installed package."
- )
- replacement = "use --force-reinstall"
- deprecated(
- reason=reason,
- replacement=replacement,
- gone_in="21.3",
- issue=8711,
- )
-
# is a local sdist or path -- reinstall
ireq.should_reinstall = True
else:
@@ -192,17 +173,19 @@ def get_installation_order(
get installed one-by-one.
The current implementation creates a topological ordering of the
- dependency graph, while breaking any cycles in the graph at arbitrary
- points. We make no guarantees about where the cycle would be broken,
- other than they would be broken.
+ dependency graph, giving more weight to packages with less
+ or no dependencies, while breaking any cycles in the graph at
+ arbitrary points. We make no guarantees about where the cycle
+ would be broken, other than it *would* be broken.
"""
assert self._result is not None, "must call resolve() first"
+ if not req_set.requirements:
+ # Nothing is left to install, so we do not need an order.
+ return []
+
graph = self._result.graph
- weights = get_topological_weights(
- graph,
- expected_node_count=len(self._result.mapping) + 1,
- )
+ weights = get_topological_weights(graph, set(req_set.requirements.keys()))
sorted_items = sorted(
req_set.requirements.items(),
@@ -213,23 +196,32 @@ def get_installation_order(
def get_topological_weights(
- graph: "DirectedGraph[Optional[str]]", expected_node_count: int
+ graph: "DirectedGraph[Optional[str]]", requirement_keys: Set[str]
) -> Dict[Optional[str], int]:
"""Assign weights to each node based on how "deep" they are.
This implementation may change at any point in the future without prior
notice.
- We take the length for the longest path to any node from root, ignoring any
- paths that contain a single node twice (i.e. cycles). This is done through
- a depth-first search through the graph, while keeping track of the path to
- the node.
+ We first simplify the dependency graph by pruning any leaves and giving them
+ the highest weight: a package without any dependencies should be installed
+ first. This is done again and again in the same way, giving ever less weight
+ to the newly found leaves. The loop stops when no leaves are left: all
+ remaining packages have at least one dependency left in the graph.
+
+ Then we continue with the remaining graph, by taking the length for the
+ longest path to any node from root, ignoring any paths that contain a single
+ node twice (i.e. cycles). This is done through a depth-first search through
+ the graph, while keeping track of the path to the node.
Cycles in the graph result would result in node being revisited while also
- being it's own path. In this case, take no action. This helps ensure we
+ being on its own path. In this case, take no action. This helps ensure we
don't get stuck in a cycle.
When assigning weight, the longer path (i.e. larger length) is preferred.
+
+ We are only interested in the weights of packages that are in the
+ requirement_keys.
"""
path: Set[Optional[str]] = set()
weights: Dict[Optional[str], int] = {}
@@ -245,15 +237,49 @@ def visit(node: Optional[str]) -> None:
visit(child)
path.remove(node)
+ if node not in requirement_keys:
+ return
+
last_known_parent_count = weights.get(node, 0)
weights[node] = max(last_known_parent_count, len(path))
+ # Simplify the graph, pruning leaves that have no dependencies.
+ # This is needed for large graphs (say over 200 packages) because the
+ # `visit` function is exponentially slower then, taking minutes.
+ # See https://github.com/pypa/pip/issues/10557
+ # We will loop until we explicitly break the loop.
+ while True:
+ leaves = set()
+ for key in graph:
+ if key is None:
+ continue
+ for _child in graph.iter_children(key):
+ # This means we have at least one child
+ break
+ else:
+ # No child.
+ leaves.add(key)
+ if not leaves:
+ # We are done simplifying.
+ break
+ # Calculate the weight for the leaves.
+ weight = len(graph) - 1
+ for leaf in leaves:
+ if leaf not in requirement_keys:
+ continue
+ weights[leaf] = weight
+ # Remove the leaves from the graph, making it simpler.
+ for leaf in leaves:
+ graph.remove(leaf)
+
+ # Visit the remaining graph.
# `None` is guaranteed to be the root node by resolvelib.
visit(None)
- # Sanity checks
- assert weights[None] == 0
- assert len(weights) == expected_node_count
+ # Sanity check: all requirement keys should be in the weights,
+ # and no other keys should be in the weights.
+ difference = set(weights.keys()).difference(requirement_keys)
+ assert not difference, difference
return weights
diff --git a/pipenv/patched/notpip/_internal/self_outdated_check.py b/pipenv/patched/notpip/_internal/self_outdated_check.py
index acc8d78168..3376186cf0 100644
--- a/pipenv/patched/notpip/_internal/self_outdated_check.py
+++ b/pipenv/patched/notpip/_internal/self_outdated_check.py
@@ -23,17 +23,15 @@
logger = logging.getLogger(__name__)
-def _get_statefile_name(key):
- # type: (str) -> str
+def _get_statefile_name(key: str) -> str:
key_bytes = key.encode()
name = hashlib.sha224(key_bytes).hexdigest()
return name
class SelfCheckState:
- def __init__(self, cache_dir):
- # type: (str) -> None
- self.state = {} # type: Dict[str, Any]
+ def __init__(self, cache_dir: str) -> None:
+ self.state: Dict[str, Any] = {}
self.statefile_path = None
# Try to load the existing state
@@ -50,12 +48,10 @@ def __init__(self, cache_dir):
pass
@property
- def key(self):
- # type: () -> str
+ def key(self) -> str:
return sys.prefix
- def save(self, pypi_version, current_time):
- # type: (str, datetime.datetime) -> None
+ def save(self, pypi_version: str, current_time: datetime.datetime) -> None:
# If we do not have a path to cache in, don't bother saving.
if not self.statefile_path:
return
@@ -90,8 +86,7 @@ def save(self, pypi_version, current_time):
pass
-def was_installed_by_pip(pkg):
- # type: (str) -> bool
+def was_installed_by_pip(pkg: str) -> bool:
"""Checks whether pkg was installed by pip
This is used not to display the upgrade message when pip is in fact
@@ -101,8 +96,7 @@ def was_installed_by_pip(pkg):
return dist is not None and "pip" == dist.installer
-def pip_self_version_check(session, options):
- # type: (PipSession, optparse.Values) -> None
+def pip_self_version_check(session: PipSession, options: optparse.Values) -> None:
"""Check for an update for pip.
Limit the frequency of checks to once per week. State is stored either in
@@ -123,8 +117,7 @@ def pip_self_version_check(session, options):
# Determine if we need to refresh the state
if "last_check" in state.state and "pypi_version" in state.state:
last_check = datetime.datetime.strptime(
- state.state["last_check"],
- SELFCHECK_DATE_FMT
+ state.state["last_check"], SELFCHECK_DATE_FMT
)
if (current_time - last_check).total_seconds() < 7 * 24 * 60 * 60:
pypi_version = state.state["pypi_version"]
@@ -148,6 +141,9 @@ def pip_self_version_check(session, options):
finder = PackageFinder.create(
link_collector=link_collector,
selection_prefs=selection_prefs,
+ use_deprecated_html5lib=(
+ "html5lib" in options.deprecated_features_enabled
+ ),
)
best_candidate = finder.find_best_candidate("pip").best_candidate
if best_candidate is None:
@@ -160,9 +156,9 @@ def pip_self_version_check(session, options):
remote_version = parse_version(pypi_version)
local_version_is_older = (
- pip_version < remote_version and
- pip_version.base_version != remote_version.base_version and
- was_installed_by_pip('pip')
+ pip_version < remote_version
+ and pip_version.base_version != remote_version.base_version
+ and was_installed_by_pip("pip")
)
# Determine if our pypi_version is older
@@ -172,13 +168,19 @@ def pip_self_version_check(session, options):
# We cannot tell how the current pip is available in the current
# command context, so be pragmatic here and suggest the command
# that's always available. This does not accommodate spaces in
- # `sys.executable`.
+ # `sys.executable` on purpose as it is not possible to do it
+ # correctly without knowing the user's shell. Thus,
+ # it won't be done until possible through the standard library.
+ # Do not be tempted to use the undocumented subprocess.list2cmdline.
+ # It is considered an internal implementation detail for a reason.
pip_cmd = f"{sys.executable} -m pip"
logger.warning(
"You are using pip version %s; however, version %s is "
"available.\nYou should consider upgrading via the "
"'%s install --upgrade pip' command.",
- pip_version, pypi_version, pip_cmd
+ pip_version,
+ pypi_version,
+ pip_cmd,
)
except Exception:
logger.debug(
diff --git a/pipenv/patched/notpip/_internal/utils/appdirs.py b/pipenv/patched/notpip/_internal/utils/appdirs.py
index 1e9983654b..e2cffb316f 100644
--- a/pipenv/patched/notpip/_internal/utils/appdirs.py
+++ b/pipenv/patched/notpip/_internal/utils/appdirs.py
@@ -7,29 +7,46 @@
"""
import os
+import sys
from typing import List
-from pipenv.patched.notpip._vendor import appdirs as _appdirs
+from pipenv.patched.notpip._vendor import platformdirs as _appdirs
def user_cache_dir(appname: str) -> str:
return _appdirs.user_cache_dir(appname, appauthor=False)
+def _macos_user_config_dir(appname: str, roaming: bool = True) -> str:
+ # Use ~/Application Support/pip, if the directory exists.
+ path = _appdirs.user_data_dir(appname, appauthor=False, roaming=roaming)
+ if os.path.isdir(path):
+ return path
+
+ # Use a Linux-like ~/.config/pip, by default.
+ linux_like_path = "~/.config/"
+ if appname:
+ linux_like_path = os.path.join(linux_like_path, appname)
+
+ return os.path.expanduser(linux_like_path)
+
+
def user_config_dir(appname: str, roaming: bool = True) -> str:
- path = _appdirs.user_config_dir(appname, appauthor=False, roaming=roaming)
- if _appdirs.system == "darwin" and not os.path.isdir(path):
- path = os.path.expanduser("~/.config/")
- if appname:
- path = os.path.join(path, appname)
- return path
+ if sys.platform == "darwin":
+ return _macos_user_config_dir(appname, roaming)
+
+ return _appdirs.user_config_dir(appname, appauthor=False, roaming=roaming)
# for the discussion regarding site_config_dir locations
# see
def site_config_dirs(appname: str) -> List[str]:
+ if sys.platform == "darwin":
+ return [_appdirs.site_data_dir(appname, appauthor=False, multipath=True)]
+
dirval = _appdirs.site_config_dir(appname, appauthor=False, multipath=True)
- if _appdirs.system not in ["win32", "darwin"]:
- # always look in /etc directly as well
- return dirval.split(os.pathsep) + ["/etc"]
- return [dirval]
+ if sys.platform == "win32":
+ return [dirval]
+
+ # Unix-y system. Look in /etc as well.
+ return dirval.split(os.pathsep) + ["/etc"]
diff --git a/pipenv/patched/notpip/_internal/utils/compatibility_tags.py b/pipenv/patched/notpip/_internal/utils/compatibility_tags.py
index f1badcc04d..dd1e7cddab 100644
--- a/pipenv/patched/notpip/_internal/utils/compatibility_tags.py
+++ b/pipenv/patched/notpip/_internal/utils/compatibility_tags.py
@@ -2,9 +2,10 @@
"""
import re
-from typing import TYPE_CHECKING, List, Optional, Tuple
+from typing import List, Optional, Tuple
from pipenv.patched.notpip._vendor.packaging.tags import (
+ PythonVersion,
Tag,
compatible_tags,
cpython_tags,
@@ -14,10 +15,6 @@
mac_platforms,
)
-if TYPE_CHECKING:
- from pipenv.patched.notpip._vendor.packaging.tags import PythonVersion
-
-
_osx_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)")
@@ -95,7 +92,7 @@ def _expand_allowed_platforms(platforms: Optional[List[str]]) -> Optional[List[s
return result
-def _get_python_version(version: str) -> "PythonVersion":
+def _get_python_version(version: str) -> PythonVersion:
if len(version) > 1:
return int(version[0]), int(version[1:])
else:
@@ -132,7 +129,7 @@ def get_supported(
"""
supported: List[Tag] = []
- python_version: Optional["PythonVersion"] = None
+ python_version: Optional[PythonVersion] = None
if version is not None:
python_version = _get_python_version(version)
diff --git a/pipenv/patched/notpip/_internal/utils/deprecation.py b/pipenv/patched/notpip/_internal/utils/deprecation.py
index c9cd17ebb9..a3a0888210 100644
--- a/pipenv/patched/notpip/_internal/utils/deprecation.py
+++ b/pipenv/patched/notpip/_internal/utils/deprecation.py
@@ -8,7 +8,7 @@
from pipenv.patched.notpip._vendor.packaging.version import parse
-from pipenv.patched.notpip import __version__ as current_version
+from pipenv.patched.notpip import __version__ as current_version # NOTE: tests patch this name.
DEPRECATION_MSG_PREFIX = "DEPRECATION: "
@@ -53,52 +53,68 @@ def install_warning_logger() -> None:
def deprecated(
+ *,
reason: str,
replacement: Optional[str],
gone_in: Optional[str],
+ feature_flag: Optional[str] = None,
issue: Optional[int] = None,
) -> None:
"""Helper to deprecate existing functionality.
reason:
Textual reason shown to the user about why this functionality has
- been deprecated.
+ been deprecated. Should be a complete sentence.
replacement:
Textual suggestion shown to the user about what alternative
functionality they can use.
gone_in:
The version of pip does this functionality should get removed in.
- Raises errors if pip's current version is greater than or equal to
+ Raises an error if pip's current version is greater than or equal to
this.
+ feature_flag:
+ Command-line flag of the form --use-feature={feature_flag} for testing
+ upcoming functionality.
issue:
Issue number on the tracker that would serve as a useful place for
users to find related discussion and provide feedback.
-
- Always pass replacement, gone_in and issue as keyword arguments for clarity
- at the call site.
"""
- # Construct a nice message.
- # This is eagerly formatted as we want it to get logged as if someone
- # typed this entire message out.
- sentences = [
- (reason, DEPRECATION_MSG_PREFIX + "{}"),
- (gone_in, "pip {} will remove support for this functionality."),
- (replacement, "A possible replacement is {}."),
+ # Determine whether or not the feature is already gone in this version.
+ is_gone = gone_in is not None and parse(current_version) >= parse(gone_in)
+
+ message_parts = [
+ (reason, f"{DEPRECATION_MSG_PREFIX}{{}}"),
+ (
+ gone_in,
+ "pip {} will enforce this behaviour change."
+ if not is_gone
+ else "Since pip {}, this is no longer supported.",
+ ),
+ (
+ replacement,
+ "A possible replacement is {}.",
+ ),
+ (
+ feature_flag,
+ "You can use the flag --use-feature={} to test the upcoming behaviour."
+ if not is_gone
+ else None,
+ ),
(
issue,
- (
- "You can find discussion regarding this at "
- "https://github.com/pypa/pip/issues/{}."
- ),
+ "Discussion can be found at https://github.com/pypa/pip/issues/{}",
),
]
+
message = " ".join(
- template.format(val) for val, template in sentences if val is not None
+ format_str.format(value)
+ for value, format_str in message_parts
+ if format_str is not None and value is not None
)
- # Raise as an error if it has to be removed.
- if gone_in is not None and parse(current_version) >= parse(gone_in):
+ # Raise as an error if this behaviour is deprecated.
+ if is_gone:
raise PipDeprecationWarning(message)
warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)
diff --git a/pipenv/patched/notpip/_internal/utils/direct_url_helpers.py b/pipenv/patched/notpip/_internal/utils/direct_url_helpers.py
index 8729cc1b97..34504d3e02 100644
--- a/pipenv/patched/notpip/_internal/utils/direct_url_helpers.py
+++ b/pipenv/patched/notpip/_internal/utils/direct_url_helpers.py
@@ -2,6 +2,7 @@
from pipenv.patched.notpip._internal.models.direct_url import ArchiveInfo, DirectUrl, DirInfo, VcsInfo
from pipenv.patched.notpip._internal.models.link import Link
+from pipenv.patched.notpip._internal.utils.urls import path_to_url
from pipenv.patched.notpip._internal.vcs import vcs
@@ -28,6 +29,13 @@ def direct_url_as_pep440_direct_reference(direct_url: DirectUrl, name: str) -> s
return requirement
+def direct_url_for_editable(source_dir: str) -> DirectUrl:
+ return DirectUrl(
+ url=path_to_url(source_dir),
+ info=DirInfo(editable=True),
+ )
+
+
def direct_url_from_link(
link: Link, source_dir: Optional[str] = None, link_is_in_wheel_cache: bool = False
) -> DirectUrl:
diff --git a/pipenv/patched/notpip/_internal/utils/egg_link.py b/pipenv/patched/notpip/_internal/utils/egg_link.py
new file mode 100644
index 0000000000..3c1a44b627
--- /dev/null
+++ b/pipenv/patched/notpip/_internal/utils/egg_link.py
@@ -0,0 +1,75 @@
+# The following comment should be removed at some point in the future.
+# mypy: strict-optional=False
+
+import os
+import re
+import sys
+from typing import Optional
+
+from pipenv.patched.notpip._internal.locations import site_packages, user_site
+from pipenv.patched.notpip._internal.utils.virtualenv import (
+ running_under_virtualenv,
+ virtualenv_no_global,
+)
+
+__all__ = [
+ "egg_link_path_from_sys_path",
+ "egg_link_path_from_location",
+]
+
+
+def _egg_link_name(raw_name: str) -> str:
+ """
+ Convert a Name metadata value to a .egg-link name, by applying
+ the same substitution as pkg_resources's safe_name function.
+ Note: we cannot use canonicalize_name because it has a different logic.
+ """
+ return re.sub("[^A-Za-z0-9.]+", "-", raw_name) + ".egg-link"
+
+
+def egg_link_path_from_sys_path(raw_name: str) -> Optional[str]:
+ """
+ Look for a .egg-link file for project name, by walking sys.path.
+ """
+ egg_link_name = _egg_link_name(raw_name)
+ for path_item in sys.path:
+ egg_link = os.path.join(path_item, egg_link_name)
+ if os.path.isfile(egg_link):
+ return egg_link
+ return None
+
+
+def egg_link_path_from_location(raw_name: str) -> Optional[str]:
+ """
+ Return the path for the .egg-link file if it exists, otherwise, None.
+
+ There's 3 scenarios:
+ 1) not in a virtualenv
+ try to find in site.USER_SITE, then site_packages
+ 2) in a no-global virtualenv
+ try to find in site_packages
+ 3) in a yes-global virtualenv
+ try to find in site_packages, then site.USER_SITE
+ (don't look in global location)
+
+ For #1 and #3, there could be odd cases, where there's an egg-link in 2
+ locations.
+
+ This method will just return the first one found.
+ """
+ sites = []
+ if running_under_virtualenv():
+ sites.append(site_packages)
+ if not virtualenv_no_global() and user_site:
+ sites.append(user_site)
+ else:
+ if user_site:
+ sites.append(user_site)
+ sites.append(site_packages)
+
+ egg_link_name = _egg_link_name(raw_name)
+ for site in sites:
+ egglink = os.path.join(site, egg_link_name)
+ if os.path.isfile(egglink):
+ return egglink
+ return None
diff --git a/pipenv/patched/notpip/_internal/utils/filetypes.py b/pipenv/patched/notpip/_internal/utils/filetypes.py
index 287473697a..b7c3adc48a 100644
--- a/pipenv/patched/notpip/_internal/utils/filetypes.py
+++ b/pipenv/patched/notpip/_internal/utils/filetypes.py
@@ -6,21 +6,20 @@
from pipenv.patched.notpip._internal.utils.misc import splitext
WHEEL_EXTENSION = ".whl"
-BZ2_EXTENSIONS = (".tar.bz2", ".tbz") # type: Tuple[str, ...]
-XZ_EXTENSIONS = (
+BZ2_EXTENSIONS: Tuple[str, ...] = (".tar.bz2", ".tbz")
+XZ_EXTENSIONS: Tuple[str, ...] = (
".tar.xz",
".txz",
".tlz",
".tar.lz",
".tar.lzma",
-) # type: Tuple[str, ...]
-ZIP_EXTENSIONS = (".zip", WHEEL_EXTENSION) # type: Tuple[str, ...]
-TAR_EXTENSIONS = (".tar.gz", ".tgz", ".tar") # type: Tuple[str, ...]
+)
+ZIP_EXTENSIONS: Tuple[str, ...] = (".zip", WHEEL_EXTENSION)
+TAR_EXTENSIONS: Tuple[str, ...] = (".tar.gz", ".tgz", ".tar")
ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS
-def is_archive_file(name):
- # type: (str) -> bool
+def is_archive_file(name: str) -> bool:
"""Return True if `name` is a considered as an archive file."""
ext = splitext(name)[1].lower()
if ext in ARCHIVE_EXTENSIONS:
diff --git a/pipenv/patched/notpip/_internal/utils/glibc.py b/pipenv/patched/notpip/_internal/utils/glibc.py
index 1c9ff35446..7bd3c20681 100644
--- a/pipenv/patched/notpip/_internal/utils/glibc.py
+++ b/pipenv/patched/notpip/_internal/utils/glibc.py
@@ -6,14 +6,12 @@
from typing import Optional, Tuple
-def glibc_version_string():
- # type: () -> Optional[str]
+def glibc_version_string() -> Optional[str]:
"Returns glibc version string, or None if not using glibc."
return glibc_version_string_confstr() or glibc_version_string_ctypes()
-def glibc_version_string_confstr():
- # type: () -> Optional[str]
+def glibc_version_string_confstr() -> Optional[str]:
"Primary implementation of glibc_version_string using os.confstr."
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
# to be broken or missing. This strategy is used in the standard library
@@ -30,8 +28,7 @@ def glibc_version_string_confstr():
return version
-def glibc_version_string_ctypes():
- # type: () -> Optional[str]
+def glibc_version_string_ctypes() -> Optional[str]:
"Fallback implementation of glibc_version_string using ctypes."
try:
@@ -78,8 +75,7 @@ def glibc_version_string_ctypes():
# versions that was generated by pip 8.1.2 and earlier is useless and
# misleading. Solution: instead of using platform, use our code that actually
# works.
-def libc_ver():
- # type: () -> Tuple[str, str]
+def libc_ver() -> Tuple[str, str]:
"""Try to determine the glibc version
Returns a tuple of strings (lib, version) which default to empty strings
diff --git a/pipenv/patched/notpip/_internal/utils/hashes.py b/pipenv/patched/notpip/_internal/utils/hashes.py
index 9730e55669..88692c4412 100644
--- a/pipenv/patched/notpip/_internal/utils/hashes.py
+++ b/pipenv/patched/notpip/_internal/utils/hashes.py
@@ -28,8 +28,7 @@ class Hashes:
"""
- def __init__(self, hashes=None):
- # type: (Dict[str, List[str]]) -> None
+ def __init__(self, hashes: Dict[str, List[str]] = None) -> None:
"""
:param hashes: A dict of algorithm names pointing to lists of allowed
hex digests
@@ -41,8 +40,7 @@ def __init__(self, hashes=None):
allowed[alg] = sorted(keys)
self._allowed = allowed
- def __and__(self, other):
- # type: (Hashes) -> Hashes
+ def __and__(self, other: "Hashes") -> "Hashes":
if not isinstance(other, Hashes):
return NotImplemented
@@ -62,21 +60,14 @@ def __and__(self, other):
return Hashes(new)
@property
- def digest_count(self):
- # type: () -> int
+ def digest_count(self) -> int:
return sum(len(digests) for digests in self._allowed.values())
- def is_hash_allowed(
- self,
- hash_name, # type: str
- hex_digest, # type: str
- ):
- # type: (...) -> bool
+ def is_hash_allowed(self, hash_name: str, hex_digest: str) -> bool:
"""Return whether the given hex digest is allowed."""
return hex_digest in self._allowed.get(hash_name, [])
- def check_against_chunks(self, chunks):
- # type: (Iterator[bytes]) -> None
+ def check_against_chunks(self, chunks: Iterator[bytes]) -> None:
"""Check good hashes against ones built from iterable of chunks of
data.
@@ -99,12 +90,10 @@ def check_against_chunks(self, chunks):
return
self._raise(gots)
- def _raise(self, gots):
- # type: (Dict[str, _Hash]) -> NoReturn
+ def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
raise HashMismatch(self._allowed, gots)
- def check_against_file(self, file):
- # type: (BinaryIO) -> None
+ def check_against_file(self, file: BinaryIO) -> None:
"""Check good hashes against a file-like object
Raise HashMismatch if none match.
@@ -112,28 +101,20 @@ def check_against_file(self, file):
"""
return self.check_against_chunks(read_chunks(file))
- def check_against_path(self, path):
- # type: (str) -> None
+ def check_against_path(self, path: str) -> None:
with open(path, "rb") as file:
return self.check_against_file(file)
- def __nonzero__(self):
- # type: () -> bool
+ def __bool__(self) -> bool:
"""Return whether I know any known-good hashes."""
return bool(self._allowed)
- def __bool__(self):
- # type: () -> bool
- return self.__nonzero__()
-
- def __eq__(self, other):
- # type: (object) -> bool
+ def __eq__(self, other: object) -> bool:
if not isinstance(other, Hashes):
return NotImplemented
return self._allowed == other._allowed
- def __hash__(self):
- # type: () -> int
+ def __hash__(self) -> int:
return hash(
",".join(
sorted(
@@ -153,13 +134,11 @@ class MissingHashes(Hashes):
"""
- def __init__(self):
- # type: () -> None
+ def __init__(self) -> None:
"""Don't offer the ``hashes`` kwarg."""
# Pass our favorite hash in to generate a "gotten hash". With the
# empty list, it will never match, so an error will always raise.
super().__init__(hashes={FAVORITE_HASH: []})
- def _raise(self, gots):
- # type: (Dict[str, _Hash]) -> NoReturn
+ def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
raise HashMissing(gots[FAVORITE_HASH].hexdigest())
diff --git a/pipenv/patched/notpip/_internal/utils/inject_securetransport.py b/pipenv/patched/notpip/_internal/utils/inject_securetransport.py
index d1a6256a05..c98e5ad04f 100644
--- a/pipenv/patched/notpip/_internal/utils/inject_securetransport.py
+++ b/pipenv/patched/notpip/_internal/utils/inject_securetransport.py
@@ -10,8 +10,7 @@
import sys
-def inject_securetransport():
- # type: () -> None
+def inject_securetransport() -> None:
# Only relevant on macOS
if sys.platform != "darwin":
return
diff --git a/pipenv/patched/notpip/_internal/utils/logging.py b/pipenv/patched/notpip/_internal/utils/logging.py
index c8f31e575d..cc9a6abe78 100644
--- a/pipenv/patched/notpip/_internal/utils/logging.py
+++ b/pipenv/patched/notpip/_internal/utils/logging.py
@@ -4,28 +4,28 @@
import logging.handlers
import os
import sys
+import threading
+from dataclasses import dataclass
from logging import Filter
-from typing import IO, Any, Callable, Iterator, Optional, TextIO, Type, cast
-
+from typing import IO, Any, ClassVar, Iterator, List, Optional, TextIO, Type
+
+from pipenv.patched.notpip._vendor.rich.console import (
+ Console,
+ ConsoleOptions,
+ ConsoleRenderable,
+ RenderResult,
+)
+from pipenv.patched.notpip._vendor.rich.highlighter import NullHighlighter
+from pipenv.patched.notpip._vendor.rich.logging import RichHandler
+from pipenv.patched.notpip._vendor.rich.segment import Segment
+from pipenv.patched.notpip._vendor.rich.style import Style
+
+from pipenv.patched.notpip._internal.exceptions import DiagnosticPipError
from pipenv.patched.notpip._internal.utils._log import VERBOSE, getLogger
from pipenv.patched.notpip._internal.utils.compat import WINDOWS
from pipenv.patched.notpip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX
from pipenv.patched.notpip._internal.utils.misc import ensure_dir
-try:
- import threading
-except ImportError:
- import dummy_threading as threading # type: ignore
-
-
-try:
- from pipenv.patched.notpip._vendor import colorama
-# Lots of different errors can come from this, including SystemError and
-# ImportError.
-except Exception:
- colorama = None
-
-
_log_state = threading.local()
subprocess_logger = getLogger("pip.subprocessor")
@@ -35,39 +35,22 @@ class BrokenStdoutLoggingError(Exception):
Raised if BrokenPipeError occurs for the stdout stream while logging.
"""
- pass
+def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) -> bool:
+ if exc_class is BrokenPipeError:
+ return True
-# BrokenPipeError manifests differently in Windows and non-Windows.
-if WINDOWS:
- # In Windows, a broken pipe can show up as EINVAL rather than EPIPE:
+ # On Windows, a broken pipe can show up as EINVAL rather than EPIPE:
# https://bugs.python.org/issue19612
# https://bugs.python.org/issue30418
- def _is_broken_pipe_error(exc_class, exc):
- # type: (Type[BaseException], BaseException) -> bool
- """See the docstring for non-Windows below."""
- return (exc_class is BrokenPipeError) or (
- isinstance(exc, OSError) and exc.errno in (errno.EINVAL, errno.EPIPE)
- )
-
-
-else:
- # Then we are in the non-Windows case.
- def _is_broken_pipe_error(exc_class, exc):
- # type: (Type[BaseException], BaseException) -> bool
- """
- Return whether an exception is a broken pipe error.
+ if not WINDOWS:
+ return False
- Args:
- exc_class: an exception class.
- exc: an exception instance.
- """
- return exc_class is BrokenPipeError
+ return isinstance(exc, OSError) and exc.errno in (errno.EINVAL, errno.EPIPE)
@contextlib.contextmanager
-def indent_log(num=2):
- # type: (int) -> Iterator[None]
+def indent_log(num: int = 2) -> Iterator[None]:
"""
A context manager which will cause the log output to be indented for any
log messages emitted inside it.
@@ -81,8 +64,7 @@ def indent_log(num=2):
_log_state.indentation -= num
-def get_indentation():
- # type: () -> int
+def get_indentation() -> int:
return getattr(_log_state, "indentation", 0)
@@ -91,11 +73,10 @@ class IndentingFormatter(logging.Formatter):
def __init__(
self,
- *args, # type: Any
- add_timestamp=False, # type: bool
- **kwargs, # type: Any
- ):
- # type: (...) -> None
+ *args: Any,
+ add_timestamp: bool = False,
+ **kwargs: Any,
+ ) -> None:
"""
A logging.Formatter that obeys the indent_log() context manager.
@@ -105,8 +86,7 @@ def __init__(
self.add_timestamp = add_timestamp
super().__init__(*args, **kwargs)
- def get_message_start(self, formatted, levelno):
- # type: (str, int) -> str
+ def get_message_start(self, formatted: str, levelno: int) -> str:
"""
Return the start of the formatted log message (not counting the
prefix to add to each line).
@@ -122,8 +102,7 @@ def get_message_start(self, formatted, levelno):
return "ERROR: "
- def format(self, record):
- # type: (logging.LogRecord) -> str
+ def format(self, record: logging.LogRecord) -> str:
"""
Calls the standard formatter, but will indent all of the log message
lines by our current indentation level.
@@ -140,85 +119,63 @@ def format(self, record):
return formatted
-def _color_wrap(*colors):
- # type: (*str) -> Callable[[str], str]
- def wrapped(inp):
- # type: (str) -> str
- return "".join(list(colors) + [inp, colorama.Style.RESET_ALL])
-
- return wrapped
-
-
-class ColorizedStreamHandler(logging.StreamHandler):
-
- # Don't build up a list of colors if we don't have colorama
- if colorama:
- COLORS = [
- # This needs to be in order from highest logging level to lowest.
- (logging.ERROR, _color_wrap(colorama.Fore.RED)),
- (logging.WARNING, _color_wrap(colorama.Fore.YELLOW)),
- ]
- else:
- COLORS = []
-
- def __init__(self, stream=None, no_color=None):
- # type: (Optional[TextIO], bool) -> None
- super().__init__(stream)
- self._no_color = no_color
-
- if WINDOWS and colorama:
- self.stream = colorama.AnsiToWin32(self.stream)
-
- def _using_stdout(self):
- # type: () -> bool
- """
- Return whether the handler is using sys.stdout.
- """
- if WINDOWS and colorama:
- # Then self.stream is an AnsiToWin32 object.
- stream = cast(colorama.AnsiToWin32, self.stream)
- return stream.wrapped is sys.stdout
-
- return self.stream is sys.stdout
-
- def should_color(self):
- # type: () -> bool
- # Don't colorize things if we do not have colorama or if told not to
- if not colorama or self._no_color:
- return False
-
- real_stream = (
- self.stream
- if not isinstance(self.stream, colorama.AnsiToWin32)
- else self.stream.wrapped
+@dataclass
+class IndentedRenderable:
+ renderable: ConsoleRenderable
+ indent: int
+
+ def __rich_console__(
+ self, console: Console, options: ConsoleOptions
+ ) -> RenderResult:
+ segments = console.render(self.renderable, options)
+ lines = Segment.split_lines(segments)
+ for line in lines:
+ yield Segment(" " * self.indent)
+ yield from line
+ yield Segment("\n")
+
+
+class RichPipStreamHandler(RichHandler):
+ KEYWORDS: ClassVar[Optional[List[str]]] = []
+
+ def __init__(self, stream: Optional[TextIO], no_color: bool) -> None:
+ super().__init__(
+ console=Console(file=stream, no_color=no_color, soft_wrap=True),
+ show_time=False,
+ show_level=False,
+ show_path=False,
+ highlighter=NullHighlighter(),
)
- # If the stream is a tty we should color it
- if hasattr(real_stream, "isatty") and real_stream.isatty():
- return True
-
- # If we have an ANSI term we should color it
- if os.environ.get("TERM") == "ANSI":
- return True
+ # Our custom override on Rich's logger, to make things work as we need them to.
+ def emit(self, record: logging.LogRecord) -> None:
+ style: Optional[Style] = None
+
+ # If we are given a diagnostic error to present, present it with indentation.
+ if record.msg == "[present-diagnostic] %s" and len(record.args) == 1:
+ diagnostic_error: DiagnosticPipError = record.args[0] # type: ignore[index]
+ assert isinstance(diagnostic_error, DiagnosticPipError)
+
+ renderable: ConsoleRenderable = IndentedRenderable(
+ diagnostic_error, indent=get_indentation()
+ )
+ else:
+ message = self.format(record)
+ renderable = self.render_message(record, message)
+ if record.levelno is not None:
+ if record.levelno >= logging.ERROR:
+ style = Style(color="red")
+ elif record.levelno >= logging.WARNING:
+ style = Style(color="yellow")
+
+ try:
+ self.console.print(renderable, overflow="ignore", crop=False, style=style)
+ except Exception:
+ self.handleError(record)
+
+ def handleError(self, record: logging.LogRecord) -> None:
+ """Called when logging is unable to log some output."""
- # If anything else we should not color it
- return False
-
- def format(self, record):
- # type: (logging.LogRecord) -> str
- msg = super().format(record)
-
- if self.should_color():
- for level, color in self.COLORS:
- if record.levelno >= level:
- msg = color(msg)
- break
-
- return msg
-
- # The logging module says handleError() can be customized.
- def handleError(self, record):
- # type: (logging.LogRecord) -> None
exc_class, exc = sys.exc_info()[:2]
# If a broken pipe occurred while calling write() or flush() on the
# stdout stream in logging's Handler.emit(), then raise our special
@@ -227,7 +184,7 @@ def handleError(self, record):
if (
exc_class
and exc
- and self._using_stdout()
+ and self.console.file is sys.stdout
and _is_broken_pipe_error(exc_class, exc)
):
raise BrokenStdoutLoggingError()
@@ -236,19 +193,16 @@ def handleError(self, record):
class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler):
- def _open(self):
- # type: () -> IO[Any]
+ def _open(self) -> IO[Any]:
ensure_dir(os.path.dirname(self.baseFilename))
return super()._open()
class MaxLevelFilter(Filter):
- def __init__(self, level):
- # type: (int) -> None
+ def __init__(self, level: int) -> None:
self.level = level
- def filter(self, record):
- # type: (logging.LogRecord) -> bool
+ def filter(self, record: logging.LogRecord) -> bool:
return record.levelno < self.level
@@ -258,15 +212,13 @@ class ExcludeLoggerFilter(Filter):
A logging Filter that excludes records from a logger (or its children).
"""
- def filter(self, record):
- # type: (logging.LogRecord) -> bool
+ def filter(self, record: logging.LogRecord) -> bool:
# The base Filter class allows only records from a logger (or its
# children).
return not super().filter(record)
-def setup_logging(verbosity, no_color, user_log_file):
- # type: (int, bool, Optional[str]) -> int
+def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str]) -> int:
"""Configures and sets up all of the logging
Returns the requested logging level, as its integer value.
@@ -308,7 +260,7 @@ def setup_logging(verbosity, no_color, user_log_file):
"stderr": "ext://sys.stderr",
}
handler_classes = {
- "stream": "pipenv.patched.notpip._internal.utils.logging.ColorizedStreamHandler",
+ "stream": "pipenv.patched.notpip._internal.utils.logging.RichPipStreamHandler",
"file": "pipenv.patched.notpip._internal.utils.logging.BetterRotatingFileHandler",
}
handlers = ["console", "console_errors", "console_subprocess"] + (
@@ -366,8 +318,8 @@ def setup_logging(verbosity, no_color, user_log_file):
"console_subprocess": {
"level": level,
"class": handler_classes["stream"],
- "no_color": no_color,
"stream": log_streams["stderr"],
+ "no_color": no_color,
"filters": ["restrict_to_subprocess"],
"formatter": "indent",
},
diff --git a/pipenv/patched/notpip/_internal/utils/misc.py b/pipenv/patched/notpip/_internal/utils/misc.py
index 4224909d8e..0c79796f1a 100644
--- a/pipenv/patched/notpip/_internal/utils/misc.py
+++ b/pipenv/patched/notpip/_internal/utils/misc.py
@@ -18,10 +18,8 @@
from types import TracebackType
from typing import (
Any,
- AnyStr,
BinaryIO,
Callable,
- Container,
ContextManager,
Iterable,
Iterator,
@@ -34,17 +32,13 @@
cast,
)
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
from pipenv.patched.notpip._vendor.tenacity import retry, stop_after_delay, wait_fixed
from pipenv.patched.notpip import __version__
from pipenv.patched.notpip._internal.exceptions import CommandError
-from pipenv.patched.notpip._internal.locations import get_major_minor_version, site_packages, user_site
-from pipenv.patched.notpip._internal.utils.compat import WINDOWS, stdlib_pkgs
-from pipenv.patched.notpip._internal.utils.virtualenv import (
- running_under_virtualenv,
- virtualenv_no_global,
-)
+from pipenv.patched.notpip._internal.locations import get_major_minor_version
+from pipenv.patched.notpip._internal.utils.compat import WINDOWS
+from pipenv.patched.notpip._internal.utils.virtualenv import running_under_virtualenv
__all__ = [
"rmtree",
@@ -71,8 +65,7 @@
NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]]
-def get_pip_version():
- # type: () -> str
+def get_pip_version() -> str:
pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..")
pip_pkg_dir = os.path.abspath(pip_pkg_dir)
@@ -83,8 +76,7 @@ def get_pip_version():
)
-def normalize_version_info(py_version_info):
- # type: (Tuple[int, ...]) -> Tuple[int, int, int]
+def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int, int]:
"""
Convert a tuple of ints representing a Python version to one of length
three.
@@ -103,8 +95,7 @@ def normalize_version_info(py_version_info):
return cast("VersionInfo", py_version_info)
-def ensure_dir(path):
- # type: (AnyStr) -> None
+def ensure_dir(path: str) -> None:
"""os.path.makedirs without EEXIST."""
try:
os.makedirs(path)
@@ -114,8 +105,7 @@ def ensure_dir(path):
raise
-def get_prog():
- # type: () -> str
+def get_prog() -> str:
try:
prog = os.path.basename(sys.argv[0])
if prog in ("__main__.py", "-c"):
@@ -130,13 +120,11 @@ def get_prog():
# Retry every half second for up to 3 seconds
# Tenacity raises RetryError by default, explicitly raise the original exception
@retry(reraise=True, stop=stop_after_delay(3), wait=wait_fixed(0.5))
-def rmtree(dir, ignore_errors=False):
- # type: (AnyStr, bool) -> None
+def rmtree(dir: str, ignore_errors: bool = False) -> None:
shutil.rmtree(dir, ignore_errors=ignore_errors, onerror=rmtree_errorhandler)
-def rmtree_errorhandler(func, path, exc_info):
- # type: (Callable[..., Any], str, ExcInfo) -> None
+def rmtree_errorhandler(func: Callable[..., Any], path: str, exc_info: ExcInfo) -> None:
"""On Windows, the files in .svn are read-only, so when rmtree() tries to
remove them, an exception is thrown. We catch that here, remove the
read-only attribute, and hopefully continue without problems."""
@@ -156,8 +144,7 @@ def rmtree_errorhandler(func, path, exc_info):
raise
-def display_path(path):
- # type: (str) -> str
+def display_path(path: str) -> str:
"""Gives the display value for a given path, making it relative to cwd
if possible."""
path = os.path.normcase(os.path.abspath(path))
@@ -166,8 +153,7 @@ def display_path(path):
return path
-def backup_dir(dir, ext=".bak"):
- # type: (str, str) -> str
+def backup_dir(dir: str, ext: str = ".bak") -> str:
"""Figure out the name of a directory to back up the given dir to
(adding .bak, .bak2, etc)"""
n = 1
@@ -178,16 +164,14 @@ def backup_dir(dir, ext=".bak"):
return dir + extension
-def ask_path_exists(message, options):
- # type: (str, Iterable[str]) -> str
+def ask_path_exists(message: str, options: Iterable[str]) -> str:
for action in os.environ.get("PIP_EXISTS_ACTION", "").split():
if action in options:
return action
return ask(message, options)
-def _check_no_input(message):
- # type: (str) -> None
+def _check_no_input(message: str) -> None:
"""Raise an error if no input is allowed."""
if os.environ.get("PIP_NO_INPUT"):
raise Exception(
@@ -195,8 +179,7 @@ def _check_no_input(message):
)
-def ask(message, options):
- # type: (str, Iterable[str]) -> str
+def ask(message: str, options: Iterable[str]) -> str:
"""Ask the message interactively, with the given possible responses"""
while 1:
_check_no_input(message)
@@ -211,22 +194,19 @@ def ask(message, options):
return response
-def ask_input(message):
- # type: (str) -> str
+def ask_input(message: str) -> str:
"""Ask for input interactively."""
_check_no_input(message)
return input(message)
-def ask_password(message):
- # type: (str) -> str
+def ask_password(message: str) -> str:
"""Ask for a password interactively."""
_check_no_input(message)
return getpass.getpass(message)
-def strtobool(val):
- # type: (str) -> int
+def strtobool(val: str) -> int:
"""Convert a string representation of truth to true (1) or false (0).
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
@@ -242,8 +222,7 @@ def strtobool(val):
raise ValueError(f"invalid truth value {val!r}")
-def format_size(bytes):
- # type: (float) -> str
+def format_size(bytes: float) -> str:
if bytes > 1000 * 1000:
return "{:.1f} MB".format(bytes / 1000.0 / 1000)
elif bytes > 10 * 1000:
@@ -254,8 +233,7 @@ def format_size(bytes):
return "{} bytes".format(int(bytes))
-def tabulate(rows):
- # type: (Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]
+def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]:
"""Return a list of formatted rows and a list of column sizes.
For example::
@@ -286,8 +264,7 @@ def is_installable_dir(path: str) -> bool:
return False
-def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE):
- # type: (BinaryIO, int) -> Iterator[bytes]
+def read_chunks(file: BinaryIO, size: int = io.DEFAULT_BUFFER_SIZE) -> Iterator[bytes]:
"""Yield pieces of data from a file-like object until EOF."""
while True:
chunk = file.read(size)
@@ -296,8 +273,7 @@ def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE):
yield chunk
-def normalize_path(path, resolve_symlinks=True):
- # type: (str, bool) -> str
+def normalize_path(path: str, resolve_symlinks: bool = True) -> str:
"""
Convert a path to its canonical, case-normalized, absolute version.
@@ -310,8 +286,7 @@ def normalize_path(path, resolve_symlinks=True):
return os.path.normcase(path)
-def splitext(path):
- # type: (str) -> Tuple[str, str]
+def splitext(path: str) -> Tuple[str, str]:
"""Like os.path.splitext, but take off .tar too"""
base, ext = posixpath.splitext(path)
if base.lower().endswith(".tar"):
@@ -320,8 +295,7 @@ def splitext(path):
return base, ext
-def renames(old, new):
- # type: (str, str) -> None
+def renames(old: str, new: str) -> None:
"""Like os.renames(), but handles renaming across devices."""
# Implementation borrowed from os.renames().
head, tail = os.path.split(new)
@@ -338,8 +312,7 @@ def renames(old, new):
pass
-def is_local(path):
- # type: (str) -> bool
+def is_local(path: str) -> bool:
"""
Return True if path is within sys.prefix, if we're running in a virtualenv.
@@ -353,158 +326,15 @@ def is_local(path):
return path.startswith(normalize_path(sys.prefix))
-def dist_is_local(dist):
- # type: (Distribution) -> bool
- """
- Return True if given Distribution object is installed locally
- (i.e. within current virtualenv).
-
- Always True if we're not in a virtualenv.
-
- """
- return is_local(dist_location(dist))
-
-
-def dist_in_usersite(dist):
- # type: (Distribution) -> bool
- """
- Return True if given Distribution is installed in user site.
- """
- return dist_location(dist).startswith(normalize_path(user_site))
-
-
-def dist_in_site_packages(dist):
- # type: (Distribution) -> bool
- """
- Return True if given Distribution is installed in
- sysconfig.get_python_lib().
- """
- return dist_location(dist).startswith(normalize_path(site_packages))
-
-
-def dist_is_editable(dist):
- # type: (Distribution) -> bool
- """
- Return True if given Distribution is an editable install.
- """
- for path_item in sys.path:
- egg_link = os.path.join(path_item, dist.project_name + ".egg-link")
- if os.path.isfile(egg_link):
- return True
- return False
-
-
-def get_installed_distributions(
- local_only=True, # type: bool
- skip=stdlib_pkgs, # type: Container[str]
- include_editables=True, # type: bool
- editables_only=False, # type: bool
- user_only=False, # type: bool
- paths=None, # type: Optional[List[str]]
-):
- # type: (...) -> List[Distribution]
- """Return a list of installed Distribution objects.
-
- Left for compatibility until direct pkg_resources uses are refactored out.
- """
- from pipenv.patched.notpip._internal.metadata import get_default_environment, get_environment
- from pipenv.patched.notpip._internal.metadata.pkg_resources import Distribution as _Dist
-
- if paths is None:
- env = get_default_environment()
- else:
- env = get_environment(paths)
- dists = env.iter_installed_distributions(
- local_only=local_only,
- skip=skip,
- include_editables=include_editables,
- editables_only=editables_only,
- user_only=user_only,
- )
- return [cast(_Dist, dist)._dist for dist in dists]
-
-
-def get_distribution(req_name):
- # type: (str) -> Optional[Distribution]
- """Given a requirement name, return the installed Distribution object.
-
- This searches from *all* distributions available in the environment, to
- match the behavior of ``pkg_resources.get_distribution()``.
-
- Left for compatibility until direct pkg_resources uses are refactored out.
- """
- from pipenv.patched.notpip._internal.metadata import get_default_environment
- from pipenv.patched.notpip._internal.metadata.pkg_resources import Distribution as _Dist
-
- dist = get_default_environment().get_distribution(req_name)
- if dist is None:
- return None
- return cast(_Dist, dist)._dist
-
-
-def egg_link_path(dist):
- # type: (Distribution) -> Optional[str]
- """
- Return the path for the .egg-link file if it exists, otherwise, None.
-
- There's 3 scenarios:
- 1) not in a virtualenv
- try to find in site.USER_SITE, then site_packages
- 2) in a no-global virtualenv
- try to find in site_packages
- 3) in a yes-global virtualenv
- try to find in site_packages, then site.USER_SITE
- (don't look in global location)
-
- For #1 and #3, there could be odd cases, where there's an egg-link in 2
- locations.
-
- This method will just return the first one found.
- """
- sites = []
- if running_under_virtualenv():
- sites.append(site_packages)
- if not virtualenv_no_global() and user_site:
- sites.append(user_site)
- else:
- if user_site:
- sites.append(user_site)
- sites.append(site_packages)
-
- for site in sites:
- egglink = os.path.join(site, dist.project_name) + ".egg-link"
- if os.path.isfile(egglink):
- return egglink
- return None
-
-
-def dist_location(dist):
- # type: (Distribution) -> str
- """
- Get the site-packages location of this distribution. Generally
- this is dist.location, except in the case of develop-installed
- packages, where dist.location is the source code location, and we
- want to know where the egg-link file is.
-
- The returned location is normalized (in particular, with symlinks removed).
- """
- egg_link = egg_link_path(dist)
- if egg_link:
- return normalize_path(egg_link)
- return normalize_path(dist.location)
-
-
-def write_output(msg, *args):
- # type: (Any, Any) -> None
+def write_output(msg: Any, *args: Any) -> None:
logger.info(msg, *args)
class StreamWrapper(StringIO):
- orig_stream = None # type: TextIO
+ orig_stream: TextIO = None
@classmethod
- def from_stream(cls, orig_stream):
- # type: (TextIO) -> StreamWrapper
+ def from_stream(cls, orig_stream: TextIO) -> "StreamWrapper":
cls.orig_stream = orig_stream
return cls()
@@ -516,8 +346,7 @@ def encoding(self): # type: ignore
@contextlib.contextmanager
-def captured_output(stream_name):
- # type: (str) -> Iterator[StreamWrapper]
+def captured_output(stream_name: str) -> Iterator[StreamWrapper]:
"""Return a context manager used by captured_stdout/stdin/stderr
that temporarily replaces the sys stream *stream_name* with a StringIO.
@@ -531,8 +360,7 @@ def captured_output(stream_name):
setattr(sys, stream_name, orig_stdout)
-def captured_stdout():
- # type: () -> ContextManager[StreamWrapper]
+def captured_stdout() -> ContextManager[StreamWrapper]:
"""Capture the output of sys.stdout:
with captured_stdout() as stdout:
@@ -544,8 +372,7 @@ def captured_stdout():
return captured_output("stdout")
-def captured_stderr():
- # type: () -> ContextManager[StreamWrapper]
+def captured_stderr() -> ContextManager[StreamWrapper]:
"""
See captured_stdout().
"""
@@ -553,16 +380,14 @@ def captured_stderr():
# Simulates an enum
-def enum(*sequential, **named):
- # type: (*Any, **Any) -> Type[Any]
+def enum(*sequential: Any, **named: Any) -> Type[Any]:
enums = dict(zip(sequential, range(len(sequential))), **named)
reverse = {value: key for key, value in enums.items()}
enums["reverse_mapping"] = reverse
return type("Enum", (), enums)
-def build_netloc(host, port):
- # type: (str, Optional[int]) -> str
+def build_netloc(host: str, port: Optional[int]) -> str:
"""
Build a netloc from a host-port pair
"""
@@ -574,8 +399,7 @@ def build_netloc(host, port):
return f"{host}:{port}"
-def build_url_from_netloc(netloc, scheme="https"):
- # type: (str, str) -> str
+def build_url_from_netloc(netloc: str, scheme: str = "https") -> str:
"""
Build a full URL from a netloc.
"""
@@ -585,8 +409,7 @@ def build_url_from_netloc(netloc, scheme="https"):
return f"{scheme}://{netloc}"
-def parse_netloc(netloc):
- # type: (str) -> Tuple[str, Optional[int]]
+def parse_netloc(netloc: str) -> Tuple[str, Optional[int]]:
"""
Return the host-port pair from a netloc.
"""
@@ -595,8 +418,7 @@ def parse_netloc(netloc):
return parsed.hostname, parsed.port
-def split_auth_from_netloc(netloc):
- # type: (str) -> NetlocTuple
+def split_auth_from_netloc(netloc: str) -> NetlocTuple:
"""
Parse out and remove the auth information from a netloc.
@@ -609,7 +431,7 @@ def split_auth_from_netloc(netloc):
# behaves if more than one @ is present (which can be checked using
# the password attribute of urlsplit()'s return value).
auth, netloc = netloc.rsplit("@", 1)
- pw = None # type: Optional[str]
+ pw: Optional[str] = None
if ":" in auth:
# Split from the left because that's how urllib.parse.urlsplit()
# behaves if more than one : is present (which again can be checked
@@ -625,8 +447,7 @@ def split_auth_from_netloc(netloc):
return netloc, (user, pw)
-def redact_netloc(netloc):
- # type: (str) -> str
+def redact_netloc(netloc: str) -> str:
"""
Replace the sensitive data in a netloc with "****", if it exists.
@@ -648,8 +469,9 @@ def redact_netloc(netloc):
)
-def _transform_url(url, transform_netloc):
- # type: (str, Callable[[str], Tuple[Any, ...]]) -> Tuple[str, NetlocTuple]
+def _transform_url(
+ url: str, transform_netloc: Callable[[str], Tuple[Any, ...]]
+) -> Tuple[str, NetlocTuple]:
"""Transform and replace netloc in a url.
transform_netloc is a function taking the netloc and returning a
@@ -667,18 +489,15 @@ def _transform_url(url, transform_netloc):
return surl, cast("NetlocTuple", netloc_tuple)
-def _get_netloc(netloc):
- # type: (str) -> NetlocTuple
+def _get_netloc(netloc: str) -> NetlocTuple:
return split_auth_from_netloc(netloc)
-def _redact_netloc(netloc):
- # type: (str) -> Tuple[str,]
+def _redact_netloc(netloc: str) -> Tuple[str]:
return (redact_netloc(netloc),)
-def split_auth_netloc_from_url(url):
- # type: (str) -> Tuple[str, str, Tuple[str, str]]
+def split_auth_netloc_from_url(url: str) -> Tuple[str, str, Tuple[str, str]]:
"""
Parse a url into separate netloc, auth, and url with no auth.
@@ -688,41 +507,31 @@ def split_auth_netloc_from_url(url):
return url_without_auth, netloc, auth
-def remove_auth_from_url(url):
- # type: (str) -> str
+def remove_auth_from_url(url: str) -> str:
"""Return a copy of url with 'username:password@' removed."""
# username/pass params are passed to subversion through flags
# and are not recognized in the url.
return _transform_url(url, _get_netloc)[0]
-def redact_auth_from_url(url):
- # type: (str) -> str
+def redact_auth_from_url(url: str) -> str:
"""Replace the password in a given url with ****."""
return _transform_url(url, _redact_netloc)[0]
class HiddenText:
- def __init__(
- self,
- secret, # type: str
- redacted, # type: str
- ):
- # type: (...) -> None
+ def __init__(self, secret: str, redacted: str) -> None:
self.secret = secret
self.redacted = redacted
- def __repr__(self):
- # type: (...) -> str
+ def __repr__(self) -> str:
return "".format(str(self))
- def __str__(self):
- # type: (...) -> str
+ def __str__(self) -> str:
return self.redacted
# This is useful for testing.
- def __eq__(self, other):
- # type: (Any) -> bool
+ def __eq__(self, other: Any) -> bool:
if type(self) != type(other):
return False
@@ -731,19 +540,16 @@ def __eq__(self, other):
return self.secret == other.secret
-def hide_value(value):
- # type: (str) -> HiddenText
+def hide_value(value: str) -> HiddenText:
return HiddenText(value, redacted="****")
-def hide_url(url):
- # type: (str) -> HiddenText
+def hide_url(url: str) -> HiddenText:
redacted = redact_auth_from_url(url)
return HiddenText(url, redacted=redacted)
-def protect_pip_from_modification_on_windows(modifying_pip):
- # type: (bool) -> None
+def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None:
"""Protection of pip.exe from modification on Windows
On Windows, any operation modifying pip should be run as:
@@ -769,14 +575,12 @@ def protect_pip_from_modification_on_windows(modifying_pip):
)
-def is_console_interactive():
- # type: () -> bool
+def is_console_interactive() -> bool:
"""Is this console interactive?"""
return sys.stdin is not None and sys.stdin.isatty()
-def hash_file(path, blocksize=1 << 20):
- # type: (str, int) -> Tuple[Any, int]
+def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]:
"""Return (hash, length) for path using hashlib.sha256()"""
h = hashlib.sha256()
@@ -788,8 +592,7 @@ def hash_file(path, blocksize=1 << 20):
return h, length
-def is_wheel_installed():
- # type: () -> bool
+def is_wheel_installed() -> bool:
"""
Return whether the wheel package is installed.
"""
@@ -801,8 +604,7 @@ def is_wheel_installed():
return True
-def pairwise(iterable):
- # type: (Iterable[Any]) -> Iterator[Tuple[Any, Any]]
+def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]:
"""
Return paired elements.
@@ -814,10 +616,9 @@ def pairwise(iterable):
def partition(
- pred, # type: Callable[[T], bool]
- iterable, # type: Iterable[T]
-):
- # type: (...) -> Tuple[Iterable[T], Iterable[T]]
+ pred: Callable[[T], bool],
+ iterable: Iterable[T],
+) -> Tuple[Iterable[T], Iterable[T]]:
"""
Use a predicate to partition entries into false entries and true entries,
like
diff --git a/pipenv/patched/notpip/_internal/utils/models.py b/pipenv/patched/notpip/_internal/utils/models.py
index 0e02bc7a5b..b6bb21a8b2 100644
--- a/pipenv/patched/notpip/_internal/utils/models.py
+++ b/pipenv/patched/notpip/_internal/utils/models.py
@@ -10,37 +10,29 @@ class KeyBasedCompareMixin:
__slots__ = ["_compare_key", "_defining_class"]
- def __init__(self, key, defining_class):
- # type: (Any, Type[KeyBasedCompareMixin]) -> None
+ def __init__(self, key: Any, defining_class: Type["KeyBasedCompareMixin"]) -> None:
self._compare_key = key
self._defining_class = defining_class
- def __hash__(self):
- # type: () -> int
+ def __hash__(self) -> int:
return hash(self._compare_key)
- def __lt__(self, other):
- # type: (Any) -> bool
+ def __lt__(self, other: Any) -> bool:
return self._compare(other, operator.__lt__)
- def __le__(self, other):
- # type: (Any) -> bool
+ def __le__(self, other: Any) -> bool:
return self._compare(other, operator.__le__)
- def __gt__(self, other):
- # type: (Any) -> bool
+ def __gt__(self, other: Any) -> bool:
return self._compare(other, operator.__gt__)
- def __ge__(self, other):
- # type: (Any) -> bool
+ def __ge__(self, other: Any) -> bool:
return self._compare(other, operator.__ge__)
- def __eq__(self, other):
- # type: (Any) -> bool
+ def __eq__(self, other: Any) -> bool:
return self._compare(other, operator.__eq__)
- def _compare(self, other, method):
- # type: (Any, Callable[[Any, Any], bool]) -> bool
+ def _compare(self, other: Any, method: Callable[[Any, Any], bool]) -> bool:
if not isinstance(other, self._defining_class):
return NotImplemented
diff --git a/pipenv/patched/notpip/_internal/utils/packaging.py b/pipenv/patched/notpip/_internal/utils/packaging.py
index ea6d29f14f..10255d4312 100644
--- a/pipenv/patched/notpip/_internal/utils/packaging.py
+++ b/pipenv/patched/notpip/_internal/utils/packaging.py
@@ -1,20 +1,19 @@
+import functools
import logging
-from email.message import Message
-from email.parser import FeedParser
-from typing import Optional, Tuple
+import re
+from typing import NewType, Optional, Tuple, cast
-from pipenv.patched.notpip._vendor import pkg_resources
from pipenv.patched.notpip._vendor.packaging import specifiers, version
-from pipenv.patched.notpip._vendor.pkg_resources import Distribution
+from pipenv.patched.notpip._vendor.packaging.requirements import Requirement
-from pipenv.patched.notpip._internal.exceptions import NoneMetadataError
-from pipenv.patched.notpip._internal.utils.misc import display_path
+NormalizedExtra = NewType("NormalizedExtra", str)
logger = logging.getLogger(__name__)
-def check_requires_python(requires_python, version_info):
- # type: (Optional[str], Tuple[int, ...]) -> bool
+def check_requires_python(
+ requires_python: Optional[str], version_info: Tuple[int, ...]
+) -> bool:
"""
Check if the given Python version matches a "Requires-Python" specifier.
@@ -35,55 +34,24 @@ def check_requires_python(requires_python, version_info):
return python_version in requires_python_specifier
-def get_metadata(dist):
- # type: (Distribution) -> Message
- """
- :raises NoneMetadataError: if the distribution reports `has_metadata()`
- True but `get_metadata()` returns None.
- """
- metadata_name = "METADATA"
- if isinstance(dist, pkg_resources.DistInfoDistribution) and dist.has_metadata(
- metadata_name
- ):
- metadata = dist.get_metadata(metadata_name)
- elif dist.has_metadata("PKG-INFO"):
- metadata_name = "PKG-INFO"
- metadata = dist.get_metadata(metadata_name)
- else:
- logger.warning("No metadata found in %s", display_path(dist.location))
- metadata = ""
+@functools.lru_cache(maxsize=512)
+def get_requirement(req_string: str) -> Requirement:
+ """Construct a packaging.Requirement object with caching"""
+ # Parsing requirement strings is expensive, and is also expected to happen
+ # with a low diversity of different arguments (at least relative the number
+ # constructed). This method adds a cache to requirement object creation to
+ # minimize repeated parsing of the same string to construct equivalent
+ # Requirement objects.
+ return Requirement(req_string)
- if metadata is None:
- raise NoneMetadataError(dist, metadata_name)
- feed_parser = FeedParser()
- # The following line errors out if with a "NoneType" TypeError if
- # passed metadata=None.
- feed_parser.feed(metadata)
- return feed_parser.close()
+def safe_extra(extra: str) -> NormalizedExtra:
+ """Convert an arbitrary string to a standard 'extra' name
+ Any runs of non-alphanumeric characters are replaced with a single '_',
+ and the result is always lowercased.
-def get_requires_python(dist):
- # type: (pkg_resources.Distribution) -> Optional[str]
+ This function is duplicated from ``pkg_resources``. Note that this is not
+ the same to either ``canonicalize_name`` or ``_egg_link_name``.
"""
- Return the "Requires-Python" metadata for a distribution, or None
- if not present.
- """
- pkg_info_dict = get_metadata(dist)
- requires_python = pkg_info_dict.get("Requires-Python")
-
- if requires_python is not None:
- # Convert to a str to satisfy the type checker, since requires_python
- # can be a Header object.
- requires_python = str(requires_python)
-
- return requires_python
-
-
-def get_installer(dist):
- # type: (Distribution) -> str
- if dist.has_metadata("INSTALLER"):
- for line in dist.get_metadata_lines("INSTALLER"):
- if line.strip():
- return line.strip()
- return ""
+ return cast(NormalizedExtra, re.sub("[^A-Za-z0-9.-]+", "_", extra).lower())
diff --git a/pipenv/patched/notpip/_internal/utils/parallel.py b/pipenv/patched/notpip/_internal/utils/parallel.py
deleted file mode 100644
index 31e12c8e4a..0000000000
--- a/pipenv/patched/notpip/_internal/utils/parallel.py
+++ /dev/null
@@ -1,101 +0,0 @@
-"""Convenient parallelization of higher order functions.
-
-This module provides two helper functions, with appropriate fallbacks on
-Python 2 and on systems lacking support for synchronization mechanisms:
-
-- map_multiprocess
-- map_multithread
-
-These helpers work like Python 3's map, with two differences:
-
-- They don't guarantee the order of processing of
- the elements of the iterable.
-- The underlying process/thread pools chop the iterable into
- a number of chunks, so that for very long iterables using
- a large value for chunksize can make the job complete much faster
- than using the default value of 1.
-"""
-
-__all__ = ["map_multiprocess", "map_multithread"]
-
-from contextlib import contextmanager
-from multiprocessing import Pool as ProcessPool
-from multiprocessing import pool
-from multiprocessing.dummy import Pool as ThreadPool
-from typing import Callable, Iterable, Iterator, TypeVar, Union
-
-from pipenv.patched.notpip._vendor.requests.adapters import DEFAULT_POOLSIZE
-
-Pool = Union[pool.Pool, pool.ThreadPool]
-S = TypeVar("S")
-T = TypeVar("T")
-
-# On platforms without sem_open, multiprocessing[.dummy] Pool
-# cannot be created.
-try:
- import multiprocessing.synchronize # noqa
-except ImportError:
- LACK_SEM_OPEN = True
-else:
- LACK_SEM_OPEN = False
-
-# Incredibly large timeout to work around bpo-8296 on Python 2.
-TIMEOUT = 2000000
-
-
-@contextmanager
-def closing(pool):
- # type: (Pool) -> Iterator[Pool]
- """Return a context manager making sure the pool closes properly."""
- try:
- yield pool
- finally:
- # For Pool.imap*, close and join are needed
- # for the returned iterator to begin yielding.
- pool.close()
- pool.join()
- pool.terminate()
-
-
-def _map_fallback(func, iterable, chunksize=1):
- # type: (Callable[[S], T], Iterable[S], int) -> Iterator[T]
- """Make an iterator applying func to each element in iterable.
-
- This function is the sequential fallback either on Python 2
- where Pool.imap* doesn't react to KeyboardInterrupt
- or when sem_open is unavailable.
- """
- return map(func, iterable)
-
-
-def _map_multiprocess(func, iterable, chunksize=1):
- # type: (Callable[[S], T], Iterable[S], int) -> Iterator[T]
- """Chop iterable into chunks and submit them to a process pool.
-
- For very long iterables using a large value for chunksize can make
- the job complete much faster than using the default value of 1.
-
- Return an unordered iterator of the results.
- """
- with closing(ProcessPool()) as pool:
- return pool.imap_unordered(func, iterable, chunksize)
-
-
-def _map_multithread(func, iterable, chunksize=1):
- # type: (Callable[[S], T], Iterable[S], int) -> Iterator[T]
- """Chop iterable into chunks and submit them to a thread pool.
-
- For very long iterables using a large value for chunksize can make
- the job complete much faster than using the default value of 1.
-
- Return an unordered iterator of the results.
- """
- with closing(ThreadPool(DEFAULT_POOLSIZE)) as pool:
- return pool.imap_unordered(func, iterable, chunksize)
-
-
-if LACK_SEM_OPEN:
- map_multiprocess = map_multithread = _map_fallback
-else:
- map_multiprocess = _map_multiprocess
- map_multithread = _map_multithread
diff --git a/pipenv/patched/notpip/_internal/utils/pkg_resources.py b/pipenv/patched/notpip/_internal/utils/pkg_resources.py
deleted file mode 100644
index 3d60b61885..0000000000
--- a/pipenv/patched/notpip/_internal/utils/pkg_resources.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from typing import Dict, Iterable, List
-
-from pipenv.patched.notpip._vendor.pkg_resources import yield_lines
-
-
-class DictMetadata:
- """IMetadataProvider that reads metadata files from a dictionary."""
-
- def __init__(self, metadata):
- # type: (Dict[str, bytes]) -> None
- self._metadata = metadata
-
- def has_metadata(self, name):
- # type: (str) -> bool
- return name in self._metadata
-
- def get_metadata(self, name):
- # type: (str) -> str
- try:
- return self._metadata[name].decode()
- except UnicodeDecodeError as e:
- # Mirrors handling done in pkg_resources.NullProvider.
- e.reason += f" in {name} file"
- raise
-
- def get_metadata_lines(self, name):
- # type: (str) -> Iterable[str]
- return yield_lines(self.get_metadata(name))
-
- def metadata_isdir(self, name):
- # type: (str) -> bool
- return False
-
- def metadata_listdir(self, name):
- # type: (str) -> List[str]
- return []
-
- def run_script(self, script_name, namespace):
- # type: (str, str) -> None
- pass
diff --git a/pipenv/patched/notpip/_internal/utils/setuptools_build.py b/pipenv/patched/notpip/_internal/utils/setuptools_build.py
index 4b8e4b359f..f460c4003f 100644
--- a/pipenv/patched/notpip/_internal/utils/setuptools_build.py
+++ b/pipenv/patched/notpip/_internal/utils/setuptools_build.py
@@ -1,30 +1,57 @@
import sys
+import textwrap
from typing import List, Optional, Sequence
# Shim to wrap setup.py invocation with setuptools
-#
-# We set sys.argv[0] to the path to the underlying setup.py file so
-# setuptools / distutils don't take the path to the setup.py to be "-c" when
-# invoking via the shim. This avoids e.g. the following manifest_maker
-# warning: "warning: manifest_maker: standard file '-c' not found".
-_SETUPTOOLS_SHIM = (
- "import io, os, sys, setuptools, tokenize; sys.argv[0] = {0!r}; __file__={0!r};"
- "f = getattr(tokenize, 'open', open)(__file__) "
- "if os.path.exists(__file__) "
- "else io.StringIO('from setuptools import setup; setup()');"
- "code = f.read().replace('\\r\\n', '\\n');"
- "f.close();"
- "exec(compile(code, __file__, 'exec'))"
-)
+# Note that __file__ is handled via two {!r} *and* %r, to ensure that paths on
+# Windows are correctly handled (it should be "C:\\Users" not "C:\Users").
+_SETUPTOOLS_SHIM = textwrap.dedent(
+ """
+ exec(compile('''
+ # This is -- a caller that pip uses to run setup.py
+ #
+ # - It imports setuptools before invoking setup.py, to enable projects that directly
+ # import from `distutils.core` to work with newer packaging standards.
+ # - It provides a clear error message when setuptools is not installed.
+ # - It sets `sys.argv[0]` to the underlying `setup.py`, when invoking `setup.py` so
+ # setuptools doesn't think the script is `-c`. This avoids the following warning:
+ # manifest_maker: standard file '-c' not found".
+ # - It generates a shim setup.py, for handling setup.cfg-only projects.
+ import os, sys, tokenize
+
+ try:
+ import setuptools
+ except ImportError as error:
+ print(
+ "ERROR: Can not execute `setup.py` since setuptools is not available in "
+ "the build environment.",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+ __file__ = %r
+ sys.argv[0] = __file__
+
+ if os.path.exists(__file__):
+ filename = __file__
+ with tokenize.open(__file__) as f:
+ setup_py_code = f.read()
+ else:
+ filename = ""
+ setup_py_code = "from setuptools import setup; setup()"
+
+ exec(compile(setup_py_code, filename, "exec"))
+ ''' % ({!r},), "", "exec"))
+ """
+).rstrip()
def make_setuptools_shim_args(
- setup_py_path, # type: str
- global_options=None, # type: Sequence[str]
- no_user_config=False, # type: bool
- unbuffered_output=False, # type: bool
-):
- # type: (...) -> List[str]
+ setup_py_path: str,
+ global_options: Sequence[str] = None,
+ no_user_config: bool = False,
+ unbuffered_output: bool = False,
+) -> List[str]:
"""
Get setuptools command arguments with shim wrapped setup file invocation.
@@ -46,12 +73,11 @@ def make_setuptools_shim_args(
def make_setuptools_bdist_wheel_args(
- setup_py_path, # type: str
- global_options, # type: Sequence[str]
- build_options, # type: Sequence[str]
- destination_dir, # type: str
-):
- # type: (...) -> List[str]
+ setup_py_path: str,
+ global_options: Sequence[str],
+ build_options: Sequence[str],
+ destination_dir: str,
+) -> List[str]:
# NOTE: Eventually, we'd want to also -S to the flags here, when we're
# isolating. Currently, it breaks Python in virtualenvs, because it
# relies on site.py to find parts of the standard library outside the
@@ -65,10 +91,9 @@ def make_setuptools_bdist_wheel_args(
def make_setuptools_clean_args(
- setup_py_path, # type: str
- global_options, # type: Sequence[str]
-):
- # type: (...) -> List[str]
+ setup_py_path: str,
+ global_options: Sequence[str],
+) -> List[str]:
args = make_setuptools_shim_args(
setup_py_path, global_options=global_options, unbuffered_output=True
)
@@ -77,15 +102,14 @@ def make_setuptools_clean_args(
def make_setuptools_develop_args(
- setup_py_path, # type: str
- global_options, # type: Sequence[str]
- install_options, # type: Sequence[str]
- no_user_config, # type: bool
- prefix, # type: Optional[str]
- home, # type: Optional[str]
- use_user_site, # type: bool
-):
- # type: (...) -> List[str]
+ setup_py_path: str,
+ global_options: Sequence[str],
+ install_options: Sequence[str],
+ no_user_config: bool,
+ prefix: Optional[str],
+ home: Optional[str],
+ use_user_site: bool,
+) -> List[str]:
assert not (use_user_site and prefix)
args = make_setuptools_shim_args(
@@ -110,11 +134,10 @@ def make_setuptools_develop_args(
def make_setuptools_egg_info_args(
- setup_py_path, # type: str
- egg_info_dir, # type: Optional[str]
- no_user_config, # type: bool
-):
- # type: (...) -> List[str]
+ setup_py_path: str,
+ egg_info_dir: Optional[str],
+ no_user_config: bool,
+) -> List[str]:
args = make_setuptools_shim_args(setup_py_path, no_user_config=no_user_config)
args += ["egg_info"]
@@ -126,19 +149,18 @@ def make_setuptools_egg_info_args(
def make_setuptools_install_args(
- setup_py_path, # type: str
- global_options, # type: Sequence[str]
- install_options, # type: Sequence[str]
- record_filename, # type: str
- root, # type: Optional[str]
- prefix, # type: Optional[str]
- header_dir, # type: Optional[str]
- home, # type: Optional[str]
- use_user_site, # type: bool
- no_user_config, # type: bool
- pycompile, # type: bool
-):
- # type: (...) -> List[str]
+ setup_py_path: str,
+ global_options: Sequence[str],
+ install_options: Sequence[str],
+ record_filename: str,
+ root: Optional[str],
+ prefix: Optional[str],
+ header_dir: Optional[str],
+ home: Optional[str],
+ use_user_site: bool,
+ no_user_config: bool,
+ pycompile: bool,
+) -> List[str]:
assert not (use_user_site and prefix)
assert not (use_user_site and root)
diff --git a/pipenv/patched/notpip/_internal/utils/subprocess.py b/pipenv/patched/notpip/_internal/utils/subprocess.py
index c23aee0627..16cff7979e 100644
--- a/pipenv/patched/notpip/_internal/utils/subprocess.py
+++ b/pipenv/patched/notpip/_internal/utils/subprocess.py
@@ -2,25 +2,38 @@
import os
import shlex
import subprocess
-from typing import Any, Callable, Iterable, List, Mapping, Optional, Union
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Iterable,
+ List,
+ Mapping,
+ Optional,
+ Union,
+)
+
+from pipenv.patched.notpip._vendor.rich.markup import escape
from pipenv.patched.notpip._internal.cli.spinners import SpinnerInterface, open_spinner
from pipenv.patched.notpip._internal.exceptions import InstallationSubprocessError
from pipenv.patched.notpip._internal.utils.logging import VERBOSE, subprocess_logger
from pipenv.patched.notpip._internal.utils.misc import HiddenText
-CommandArgs = List[Union[str, HiddenText]]
-
+if TYPE_CHECKING:
+ # Literal was introduced in Python 3.8.
+ #
+ # TODO: Remove `if TYPE_CHECKING` when dropping support for Python 3.7.
+ from typing import Literal
-LOG_DIVIDER = "----------------------------------------"
+CommandArgs = List[Union[str, HiddenText]]
-def make_command(*args):
- # type: (Union[str, HiddenText, CommandArgs]) -> CommandArgs
+def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
"""
Create a CommandArgs object.
"""
- command_args = [] # type: CommandArgs
+ command_args: CommandArgs = []
for arg in args:
# Check for list instead of CommandArgs since CommandArgs is
# only known during type-checking.
@@ -33,8 +46,7 @@ def make_command(*args):
return command_args
-def format_command_args(args):
- # type: (Union[List[str], CommandArgs]) -> str
+def format_command_args(args: Union[List[str], CommandArgs]) -> str:
"""
Format command arguments for display.
"""
@@ -49,64 +61,27 @@ def format_command_args(args):
)
-def reveal_command_args(args):
- # type: (Union[List[str], CommandArgs]) -> List[str]
+def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
"""
Return the arguments in their raw, unredacted form.
"""
return [arg.secret if isinstance(arg, HiddenText) else arg for arg in args]
-def make_subprocess_output_error(
- cmd_args, # type: Union[List[str], CommandArgs]
- cwd, # type: Optional[str]
- lines, # type: List[str]
- exit_status, # type: int
-):
- # type: (...) -> str
- """
- Create and return the error message to use to log a subprocess error
- with command output.
-
- :param lines: A list of lines, each ending with a newline.
- """
- command = format_command_args(cmd_args)
-
- # We know the joined output value ends in a newline.
- output = "".join(lines)
- msg = (
- # Use a unicode string to avoid "UnicodeEncodeError: 'ascii'
- # codec can't encode character ..." in Python 2 when a format
- # argument (e.g. `output`) has a non-ascii character.
- "Command errored out with exit status {exit_status}:\n"
- " command: {command_display}\n"
- " cwd: {cwd_display}\n"
- "Complete output ({line_count} lines):\n{output}{divider}"
- ).format(
- exit_status=exit_status,
- command_display=command,
- cwd_display=cwd,
- line_count=len(lines),
- output=output,
- divider=LOG_DIVIDER,
- )
- return msg
-
-
def call_subprocess(
- cmd, # type: Union[List[str], CommandArgs]
- show_stdout=False, # type: bool
- cwd=None, # type: Optional[str]
- on_returncode="raise", # type: str
- extra_ok_returncodes=None, # type: Optional[Iterable[int]]
- command_desc=None, # type: Optional[str]
- extra_environ=None, # type: Optional[Mapping[str, Any]]
- unset_environ=None, # type: Optional[Iterable[str]]
- spinner=None, # type: Optional[SpinnerInterface]
- log_failed_cmd=True, # type: Optional[bool]
- stdout_only=False, # type: Optional[bool]
-):
- # type: (...) -> str
+ cmd: Union[List[str], CommandArgs],
+ show_stdout: bool = False,
+ cwd: Optional[str] = None,
+ on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise",
+ extra_ok_returncodes: Optional[Iterable[int]] = None,
+ extra_environ: Optional[Mapping[str, Any]] = None,
+ unset_environ: Optional[Iterable[str]] = None,
+ spinner: Optional[SpinnerInterface] = None,
+ log_failed_cmd: Optional[bool] = True,
+ stdout_only: Optional[bool] = False,
+ *,
+ command_desc: str,
+) -> str:
"""
Args:
show_stdout: if true, use INFO to log the subprocess's stderr and
@@ -156,9 +131,6 @@ def call_subprocess(
# and we have a spinner.
use_spinner = not showing_subprocess and spinner is not None
- if command_desc is None:
- command_desc = format_command_args(cmd)
-
log_subprocess("Running command %s", command_desc)
env = os.environ.copy()
if extra_environ:
@@ -191,7 +163,7 @@ def call_subprocess(
proc.stdin.close()
# In this mode, stdout and stderr are in the same pipe.
while True:
- line = proc.stdout.readline() # type: str
+ line: str = proc.stdout.readline()
if not line:
break
line = line.rstrip()
@@ -231,17 +203,25 @@ def call_subprocess(
spinner.finish("done")
if proc_had_error:
if on_returncode == "raise":
- if not showing_subprocess and log_failed_cmd:
- # Then the subprocess streams haven't been logged to the
- # console yet.
- msg = make_subprocess_output_error(
- cmd_args=cmd,
- cwd=cwd,
- lines=all_output,
- exit_status=proc.returncode,
+ error = InstallationSubprocessError(
+ command_description=command_desc,
+ exit_code=proc.returncode,
+ output_lines=all_output if not showing_subprocess else None,
+ )
+ if log_failed_cmd:
+ subprocess_logger.error("[present-diagnostic] %s", error)
+ subprocess_logger.verbose(
+ "[bold magenta]full command[/]: [blue]%s[/]",
+ escape(format_command_args(cmd)),
+ extra={"markup": True},
)
- subprocess_logger.error(msg)
- raise InstallationSubprocessError(proc.returncode, command_desc)
+ subprocess_logger.verbose(
+ "[bold magenta]cwd[/]: %s",
+ escape(cwd or "[inherit]"),
+ extra={"markup": True},
+ )
+
+ raise error
elif on_returncode == "warn":
subprocess_logger.warning(
'Command "%s" had error code %s in %s',
@@ -256,8 +236,7 @@ def call_subprocess(
return output
-def runner_with_spinner_message(message):
- # type: (str) -> Callable[..., None]
+def runner_with_spinner_message(message: str) -> Callable[..., None]:
"""Provide a subprocess_runner that shows a spinner message.
Intended for use with for pep517's Pep517HookCaller. Thus, the runner has
@@ -265,14 +244,14 @@ def runner_with_spinner_message(message):
"""
def runner(
- cmd, # type: List[str]
- cwd=None, # type: Optional[str]
- extra_environ=None, # type: Optional[Mapping[str, Any]]
- ):
- # type: (...) -> None
+ cmd: List[str],
+ cwd: Optional[str] = None,
+ extra_environ: Optional[Mapping[str, Any]] = None,
+ ) -> None:
with open_spinner(message) as spinner:
call_subprocess(
cmd,
+ command_desc=message,
cwd=cwd,
extra_environ=extra_environ,
spinner=spinner,
diff --git a/pipenv/patched/notpip/_internal/utils/temp_dir.py b/pipenv/patched/notpip/_internal/utils/temp_dir.py
index 1bc0c14e89..cf26edec91 100644
--- a/pipenv/patched/notpip/_internal/utils/temp_dir.py
+++ b/pipenv/patched/notpip/_internal/utils/temp_dir.py
@@ -22,12 +22,11 @@
)
-_tempdir_manager = None # type: Optional[ExitStack]
+_tempdir_manager: Optional[ExitStack] = None
@contextmanager
-def global_tempdir_manager():
- # type: () -> Iterator[None]
+def global_tempdir_manager() -> Iterator[None]:
global _tempdir_manager
with ExitStack() as stack:
old_tempdir_manager, _tempdir_manager = _tempdir_manager, stack
@@ -40,31 +39,27 @@ def global_tempdir_manager():
class TempDirectoryTypeRegistry:
"""Manages temp directory behavior"""
- def __init__(self):
- # type: () -> None
- self._should_delete = {} # type: Dict[str, bool]
+ def __init__(self) -> None:
+ self._should_delete: Dict[str, bool] = {}
- def set_delete(self, kind, value):
- # type: (str, bool) -> None
+ def set_delete(self, kind: str, value: bool) -> None:
"""Indicate whether a TempDirectory of the given kind should be
auto-deleted.
"""
self._should_delete[kind] = value
- def get_delete(self, kind):
- # type: (str) -> bool
+ def get_delete(self, kind: str) -> bool:
"""Get configured auto-delete flag for a given TempDirectory type,
default True.
"""
return self._should_delete.get(kind, True)
-_tempdir_registry = None # type: Optional[TempDirectoryTypeRegistry]
+_tempdir_registry: Optional[TempDirectoryTypeRegistry] = None
@contextmanager
-def tempdir_registry():
- # type: () -> Iterator[TempDirectoryTypeRegistry]
+def tempdir_registry() -> Iterator[TempDirectoryTypeRegistry]:
"""Provides a scoped global tempdir registry that can be used to dictate
whether directories should be deleted.
"""
@@ -107,10 +102,10 @@ class TempDirectory:
def __init__(
self,
- path=None, # type: Optional[str]
- delete=_default, # type: Union[bool, None, _Default]
- kind="temp", # type: str
- globally_managed=False, # type: bool
+ path: Optional[str] = None,
+ delete: Union[bool, None, _Default] = _default,
+ kind: str = "temp",
+ globally_managed: bool = False,
):
super().__init__()
@@ -139,21 +134,17 @@ def __init__(
_tempdir_manager.enter_context(self)
@property
- def path(self):
- # type: () -> str
+ def path(self) -> str:
assert not self._deleted, f"Attempted to access deleted path: {self._path}"
return self._path
- def __repr__(self):
- # type: () -> str
+ def __repr__(self) -> str:
return f"<{self.__class__.__name__} {self.path!r}>"
- def __enter__(self):
- # type: (_T) -> _T
+ def __enter__(self: _T) -> _T:
return self
- def __exit__(self, exc, value, tb):
- # type: (Any, Any, Any) -> None
+ def __exit__(self, exc: Any, value: Any, tb: Any) -> None:
if self.delete is not None:
delete = self.delete
elif _tempdir_registry:
@@ -164,8 +155,7 @@ def __exit__(self, exc, value, tb):
if delete:
self.cleanup()
- def _create(self, kind):
- # type: (str) -> str
+ def _create(self, kind: str) -> str:
"""Create a temporary directory and store its path in self.path"""
# We realpath here because some systems have their default tmpdir
# symlinked to another directory. This tends to confuse build
@@ -175,8 +165,7 @@ def _create(self, kind):
logger.debug("Created temporary directory: %s", path)
return path
- def cleanup(self):
- # type: () -> None
+ def cleanup(self) -> None:
"""Remove the temporary directory created and reset state"""
self._deleted = True
if not os.path.exists(self._path):
@@ -206,14 +195,12 @@ class AdjacentTempDirectory(TempDirectory):
# with leading '-' and invalid metadata
LEADING_CHARS = "-~.=%0123456789"
- def __init__(self, original, delete=None):
- # type: (str, Optional[bool]) -> None
+ def __init__(self, original: str, delete: Optional[bool] = None) -> None:
self.original = original.rstrip("/\\")
super().__init__(delete=delete)
@classmethod
- def _generate_names(cls, name):
- # type: (str) -> Iterator[str]
+ def _generate_names(cls, name: str) -> Iterator[str]:
"""Generates a series of temporary names.
The algorithm replaces the leading characters in the name
@@ -238,8 +225,7 @@ def _generate_names(cls, name):
if new_name != name:
yield new_name
- def _create(self, kind):
- # type: (str) -> str
+ def _create(self, kind: str) -> str:
root, name = os.path.split(self.original)
for candidate in self._generate_names(name):
path = os.path.join(root, candidate)
diff --git a/pipenv/patched/notpip/_internal/utils/unpacking.py b/pipenv/patched/notpip/_internal/utils/unpacking.py
index aea8900a4f..0a9b5f4500 100644
--- a/pipenv/patched/notpip/_internal/utils/unpacking.py
+++ b/pipenv/patched/notpip/_internal/utils/unpacking.py
@@ -40,16 +40,14 @@
logger.debug("lzma module is not available")
-def current_umask():
- # type: () -> int
+def current_umask() -> int:
"""Get the current umask which involves having to set it temporarily."""
mask = os.umask(0)
os.umask(mask)
return mask
-def split_leading_dir(path):
- # type: (str) -> List[str]
+def split_leading_dir(path: str) -> List[str]:
path = path.lstrip("/").lstrip("\\")
if "/" in path and (
("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path
@@ -61,8 +59,7 @@ def split_leading_dir(path):
return [path, ""]
-def has_leading_dir(paths):
- # type: (Iterable[str]) -> bool
+def has_leading_dir(paths: Iterable[str]) -> bool:
"""Returns true if all the paths have the same leading path name
(i.e., everything is in one subdirectory in an archive)"""
common_prefix = None
@@ -77,8 +74,7 @@ def has_leading_dir(paths):
return True
-def is_within_directory(directory, target):
- # type: (str, str) -> bool
+def is_within_directory(directory: str, target: str) -> bool:
"""
Return true if the absolute path of target is within the directory
"""
@@ -89,8 +85,7 @@ def is_within_directory(directory, target):
return prefix == abs_directory
-def set_extracted_file_to_default_mode_plus_executable(path):
- # type: (str) -> None
+def set_extracted_file_to_default_mode_plus_executable(path: str) -> None:
"""
Make file present at path have execute for user/group/world
(chmod +x) is no-op on windows per python docs
@@ -98,16 +93,14 @@ def set_extracted_file_to_default_mode_plus_executable(path):
os.chmod(path, (0o777 & ~current_umask() | 0o111))
-def zip_item_is_executable(info):
- # type: (ZipInfo) -> bool
+def zip_item_is_executable(info: ZipInfo) -> bool:
mode = info.external_attr >> 16
# if mode and regular file and any execute permissions for
# user/group/world?
return bool(mode and stat.S_ISREG(mode) and mode & 0o111)
-def unzip_file(filename, location, flatten=True):
- # type: (str, str, bool) -> None
+def unzip_file(filename: str, location: str, flatten: bool = True) -> None:
"""
Unzip the file (with path `filename`) to the destination `location`. All
files are written based on system defaults and umask (i.e. permissions are
@@ -153,8 +146,7 @@ def unzip_file(filename, location, flatten=True):
zipfp.close()
-def untar_file(filename, location):
- # type: (str, str) -> None
+def untar_file(filename: str, location: str) -> None:
"""
Untar the file (with path `filename`) to the destination `location`.
All files are written based on system defaults and umask (i.e. permissions
@@ -236,11 +228,10 @@ def untar_file(filename, location):
def unpack_file(
- filename, # type: str
- location, # type: str
- content_type=None, # type: Optional[str]
-):
- # type: (...) -> None
+ filename: str,
+ location: str,
+ content_type: Optional[str] = None,
+) -> None:
filename = os.path.realpath(filename)
if (
content_type == "application/zip"
diff --git a/pipenv/patched/notpip/_internal/utils/urls.py b/pipenv/patched/notpip/_internal/utils/urls.py
index 7b51052c9a..6ba2e04f35 100644
--- a/pipenv/patched/notpip/_internal/utils/urls.py
+++ b/pipenv/patched/notpip/_internal/utils/urls.py
@@ -7,15 +7,13 @@
from .compat import WINDOWS
-def get_url_scheme(url):
- # type: (str) -> Optional[str]
+def get_url_scheme(url: str) -> Optional[str]:
if ":" not in url:
return None
return url.split(":", 1)[0].lower()
-def path_to_url(path):
- # type: (str) -> str
+def path_to_url(path: str) -> str:
"""
Convert a path to a file: URL. The path will be made absolute and have
quoted path parts.
@@ -25,8 +23,7 @@ def path_to_url(path):
return url
-def url_to_path(url):
- # type: (str) -> str
+def url_to_path(url: str) -> str:
"""
Convert a file: URL to a path.
"""
diff --git a/pipenv/patched/notpip/_internal/utils/virtualenv.py b/pipenv/patched/notpip/_internal/utils/virtualenv.py
index 51cacb55ca..c926db4c33 100644
--- a/pipenv/patched/notpip/_internal/utils/virtualenv.py
+++ b/pipenv/patched/notpip/_internal/utils/virtualenv.py
@@ -11,8 +11,7 @@
)
-def _running_under_venv():
- # type: () -> bool
+def _running_under_venv() -> bool:
"""Checks if sys.base_prefix and sys.prefix match.
This handles PEP 405 compliant virtual environments.
@@ -20,8 +19,7 @@ def _running_under_venv():
return sys.prefix != getattr(sys, "base_prefix", sys.prefix)
-def _running_under_regular_virtualenv():
- # type: () -> bool
+def _running_under_regular_virtualenv() -> bool:
"""Checks if sys.real_prefix is set.
This handles virtual environments created with pypa's virtualenv.
@@ -30,14 +28,12 @@ def _running_under_regular_virtualenv():
return hasattr(sys, "real_prefix")
-def running_under_virtualenv():
- # type: () -> bool
+def running_under_virtualenv() -> bool:
"""Return True if we're running inside a virtualenv, False otherwise."""
return _running_under_venv() or _running_under_regular_virtualenv()
-def _get_pyvenv_cfg_lines():
- # type: () -> Optional[List[str]]
+def _get_pyvenv_cfg_lines() -> Optional[List[str]]:
"""Reads {sys.prefix}/pyvenv.cfg and returns its contents as list of lines
Returns None, if it could not read/access the file.
@@ -52,8 +48,7 @@ def _get_pyvenv_cfg_lines():
return None
-def _no_global_under_venv():
- # type: () -> bool
+def _no_global_under_venv() -> bool:
"""Check `{sys.prefix}/pyvenv.cfg` for system site-packages inclusion
PEP 405 specifies that when system site-packages are not supposed to be
@@ -82,8 +77,7 @@ def _no_global_under_venv():
return False
-def _no_global_under_regular_virtualenv():
- # type: () -> bool
+def _no_global_under_regular_virtualenv() -> bool:
"""Check if "no-global-site-packages.txt" exists beside site.py
This mirrors logic in pypa/virtualenv for determining whether system
@@ -97,8 +91,7 @@ def _no_global_under_regular_virtualenv():
return os.path.exists(no_global_site_packages_file)
-def virtualenv_no_global():
- # type: () -> bool
+def virtualenv_no_global() -> bool:
"""Returns a boolean, whether running in venv with no system site-packages."""
# PEP 405 compliance needs to be checked first since virtualenv >=20 would
# return True for both checks, but is only able to use the PEP 405 config.
diff --git a/pipenv/patched/notpip/_internal/utils/wheel.py b/pipenv/patched/notpip/_internal/utils/wheel.py
index a125b5ffd0..9e9627cd1e 100644
--- a/pipenv/patched/notpip/_internal/utils/wheel.py
+++ b/pipenv/patched/notpip/_internal/utils/wheel.py
@@ -4,14 +4,12 @@
import logging
from email.message import Message
from email.parser import Parser
-from typing import Dict, Tuple
+from typing import Tuple
from zipfile import BadZipFile, ZipFile
from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name
-from pipenv.patched.notpip._vendor.pkg_resources import DistInfoDistribution, Distribution
from pipenv.patched.notpip._internal.exceptions import UnsupportedWheel
-from pipenv.patched.notpip._internal.utils.pkg_resources import DictMetadata
VERSION_COMPATIBLE = (1, 0)
@@ -19,53 +17,7 @@
logger = logging.getLogger(__name__)
-class WheelMetadata(DictMetadata):
- """Metadata provider that maps metadata decoding exceptions to our
- internal exception type.
- """
-
- def __init__(self, metadata, wheel_name):
- # type: (Dict[str, bytes], str) -> None
- super().__init__(metadata)
- self._wheel_name = wheel_name
-
- def get_metadata(self, name):
- # type: (str) -> str
- try:
- return super().get_metadata(name)
- except UnicodeDecodeError as e:
- # Augment the default error with the origin of the file.
- raise UnsupportedWheel(
- f"Error decoding metadata for {self._wheel_name}: {e}"
- )
-
-
-def pkg_resources_distribution_for_wheel(wheel_zip, name, location):
- # type: (ZipFile, str, str) -> Distribution
- """Get a pkg_resources distribution given a wheel.
-
- :raises UnsupportedWheel: on any errors
- """
- info_dir, _ = parse_wheel(wheel_zip, name)
-
- metadata_files = [p for p in wheel_zip.namelist() if p.startswith(f"{info_dir}/")]
-
- metadata_text = {} # type: Dict[str, bytes]
- for path in metadata_files:
- _, metadata_name = path.split("/", 1)
-
- try:
- metadata_text[metadata_name] = read_wheel_metadata_file(wheel_zip, path)
- except UnsupportedWheel as e:
- raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e)))
-
- metadata = WheelMetadata(metadata_text, location)
-
- return DistInfoDistribution(location=location, metadata=metadata, project_name=name)
-
-
-def parse_wheel(wheel_zip, name):
- # type: (ZipFile, str) -> Tuple[str, Message]
+def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]:
"""Extract information from the provided wheel, ensuring it meets basic
standards.
@@ -83,8 +35,7 @@ def parse_wheel(wheel_zip, name):
return info_dir, metadata
-def wheel_dist_info_dir(source, name):
- # type: (ZipFile, str) -> str
+def wheel_dist_info_dir(source: ZipFile, name: str) -> str:
"""Returns the name of the contained .dist-info directory.
Raises AssertionError or UnsupportedWheel if not found, >1 found, or
@@ -117,8 +68,7 @@ def wheel_dist_info_dir(source, name):
return info_dir
-def read_wheel_metadata_file(source, path):
- # type: (ZipFile, str) -> bytes
+def read_wheel_metadata_file(source: ZipFile, path: str) -> bytes:
try:
return source.read(path)
# BadZipFile for general corruption, KeyError for missing entry,
@@ -127,8 +77,7 @@ def read_wheel_metadata_file(source, path):
raise UnsupportedWheel(f"could not read {path!r} file: {e!r}")
-def wheel_metadata(source, dist_info_dir):
- # type: (ZipFile, str) -> Message
+def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message:
"""Return the WHEEL metadata of an extracted wheel, if possible.
Otherwise, raise UnsupportedWheel.
"""
@@ -147,8 +96,7 @@ def wheel_metadata(source, dist_info_dir):
return Parser().parsestr(wheel_text)
-def wheel_version(wheel_data):
- # type: (Message) -> Tuple[int, ...]
+def wheel_version(wheel_data: Message) -> Tuple[int, ...]:
"""Given WHEEL metadata, return the parsed Wheel-Version.
Otherwise, raise UnsupportedWheel.
"""
@@ -164,8 +112,7 @@ def wheel_version(wheel_data):
raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}")
-def check_compatibility(version, name):
- # type: (Tuple[int, ...], str) -> None
+def check_compatibility(version: Tuple[int, ...], name: str) -> None:
"""Raises errors or warns if called with an incompatible Wheel-Version.
pip should refuse to install a Wheel-Version that's a major series
diff --git a/pipenv/patched/notpip/_internal/vcs/bazaar.py b/pipenv/patched/notpip/_internal/vcs/bazaar.py
index 0913297c64..b33d967f22 100644
--- a/pipenv/patched/notpip/_internal/vcs/bazaar.py
+++ b/pipenv/patched/notpip/_internal/vcs/bazaar.py
@@ -16,61 +16,65 @@
class Bazaar(VersionControl):
- name = 'bzr'
- dirname = '.bzr'
- repo_name = 'branch'
+ name = "bzr"
+ dirname = ".bzr"
+ repo_name = "branch"
schemes = (
- 'bzr+http', 'bzr+https', 'bzr+ssh', 'bzr+sftp', 'bzr+ftp',
- 'bzr+lp', 'bzr+file'
+ "bzr+http",
+ "bzr+https",
+ "bzr+ssh",
+ "bzr+sftp",
+ "bzr+ftp",
+ "bzr+lp",
+ "bzr+file",
)
@staticmethod
- def get_base_rev_args(rev):
- # type: (str) -> List[str]
- return ['-r', rev]
+ def get_base_rev_args(rev: str) -> List[str]:
+ return ["-r", rev]
- def fetch_new(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def fetch_new(
+ self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+ ) -> None:
rev_display = rev_options.to_display()
logger.info(
- 'Checking out %s%s to %s',
+ "Checking out %s%s to %s",
url,
rev_display,
display_path(dest),
)
- cmd_args = (
- make_command('branch', '-q', rev_options.to_args(), url, dest)
- )
+ if verbosity <= 0:
+ flag = "--quiet"
+ elif verbosity == 1:
+ flag = ""
+ else:
+ flag = f"-{'v'*verbosity}"
+ cmd_args = make_command("branch", flag, rev_options.to_args(), url, dest)
self.run_command(cmd_args)
- def switch(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
- self.run_command(make_command('switch', url), cwd=dest)
+ def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ self.run_command(make_command("switch", url), cwd=dest)
- def update(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
- cmd_args = make_command('pull', '-q', rev_options.to_args())
+ def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ cmd_args = make_command("pull", "-q", rev_options.to_args())
self.run_command(cmd_args, cwd=dest)
@classmethod
- def get_url_rev_and_auth(cls, url):
- # type: (str) -> Tuple[str, Optional[str], AuthInfo]
+ def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
# hotfix the URL scheme after removing bzr+ from bzr+ssh:// readd it
url, rev, user_pass = super().get_url_rev_and_auth(url)
- if url.startswith('ssh://'):
- url = 'bzr+' + url
+ if url.startswith("ssh://"):
+ url = "bzr+" + url
return url, rev, user_pass
@classmethod
- def get_remote_url(cls, location):
- # type: (str) -> str
+ def get_remote_url(cls, location: str) -> str:
urls = cls.run_command(
- ['info'], show_stdout=False, stdout_only=True, cwd=location
+ ["info"], show_stdout=False, stdout_only=True, cwd=location
)
for line in urls.splitlines():
line = line.strip()
- for x in ('checkout of branch: ',
- 'parent branch: '):
+ for x in ("checkout of branch: ", "parent branch: "):
if line.startswith(x):
repo = line.split(x)[1]
if cls._is_local_repository(repo):
@@ -79,16 +83,17 @@ def get_remote_url(cls, location):
raise RemoteNotFoundError
@classmethod
- def get_revision(cls, location):
- # type: (str) -> str
+ def get_revision(cls, location: str) -> str:
revision = cls.run_command(
- ['revno'], show_stdout=False, stdout_only=True, cwd=location,
+ ["revno"],
+ show_stdout=False,
+ stdout_only=True,
+ cwd=location,
)
return revision.splitlines()[-1]
@classmethod
- def is_commit_id_equal(cls, dest, name):
- # type: (str, Optional[str]) -> bool
+ def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
"""Always assume the versions don't match"""
return False
diff --git a/pipenv/patched/notpip/_internal/vcs/git.py b/pipenv/patched/notpip/_internal/vcs/git.py
index 37b0e787a5..c8ba695ca8 100644
--- a/pipenv/patched/notpip/_internal/vcs/git.py
+++ b/pipenv/patched/notpip/_internal/vcs/git.py
@@ -34,10 +34,11 @@
r".*$" # Suffix, including any pre- and post-release segments we don't care about.
)
-HASH_REGEX = re.compile('^[a-fA-F0-9]{40}$')
+HASH_REGEX = re.compile("^[a-fA-F0-9]{40}$")
# SCP (Secure copy protocol) shorthand. e.g. 'git@example.com:foo/bar.git'
-SCP_REGEX = re.compile(r"""^
+SCP_REGEX = re.compile(
+ r"""^
# Optional user, e.g. 'git@'
(\w+@)?
# Server, e.g. 'github.com'.
@@ -46,33 +47,36 @@
# alphanumeric character so as not to be confusable with a Windows paths
# like 'C:/foo/bar' or 'C:\foo\bar'.
(\w[^:]*)
-$""", re.VERBOSE)
+ $""",
+ re.VERBOSE,
+)
-def looks_like_hash(sha):
- # type: (str) -> bool
+def looks_like_hash(sha: str) -> bool:
return bool(HASH_REGEX.match(sha))
class Git(VersionControl):
- name = 'git'
- dirname = '.git'
- repo_name = 'clone'
+ name = "git"
+ dirname = ".git"
+ repo_name = "clone"
schemes = (
- 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file',
+ "git+http",
+ "git+https",
+ "git+ssh",
+ "git+git",
+ "git+file",
)
# Prevent the user's environment variables from interfering with pip:
# https://github.com/pypa/pip/issues/1130
- unset_environ = ('GIT_DIR', 'GIT_WORK_TREE')
- default_arg_rev = 'HEAD'
+ unset_environ = ("GIT_DIR", "GIT_WORK_TREE")
+ default_arg_rev = "HEAD"
@staticmethod
- def get_base_rev_args(rev):
- # type: (str) -> List[str]
+ def get_base_rev_args(rev: str) -> List[str]:
return [rev]
- def is_immutable_rev_checkout(self, url, dest):
- # type: (str, str) -> bool
+ def is_immutable_rev_checkout(self, url: str, dest: str) -> bool:
_, rev_options = self.get_url_rev_options(hide_url(url))
if not rev_options.rev:
return False
@@ -83,23 +87,24 @@ def is_immutable_rev_checkout(self, url, dest):
# return False in the rare case rev is both a commit hash
# and a tag or a branch; we don't want to cache in that case
# because that branch/tag could point to something else in the future
- is_tag_or_branch = bool(
- self.get_revision_sha(dest, rev_options.rev)[0]
- )
+ is_tag_or_branch = bool(self.get_revision_sha(dest, rev_options.rev)[0])
return not is_tag_or_branch
def get_git_version(self) -> Tuple[int, ...]:
version = self.run_command(
- ['version'], show_stdout=False, stdout_only=True
+ ["version"],
+ command_desc="git version",
+ show_stdout=False,
+ stdout_only=True,
)
match = GIT_VERSION_REGEX.match(version)
if not match:
+ logger.warning("Can't parse git version: %s", version)
return ()
return tuple(int(c) for c in match.groups())
@classmethod
- def get_current_branch(cls, location):
- # type: (str) -> Optional[str]
+ def get_current_branch(cls, location: str) -> Optional[str]:
"""
Return the current branch, or None if HEAD isn't at a branch
(e.g. detached HEAD).
@@ -108,24 +113,23 @@ def get_current_branch(cls, location):
# HEAD rather than a symbolic ref. In addition, the -q causes the
# command to exit with status code 1 instead of 128 in this case
# and to suppress the message to stderr.
- args = ['symbolic-ref', '-q', 'HEAD']
+ args = ["symbolic-ref", "-q", "HEAD"]
output = cls.run_command(
args,
- extra_ok_returncodes=(1, ),
+ extra_ok_returncodes=(1,),
show_stdout=False,
stdout_only=True,
cwd=location,
)
ref = output.strip()
- if ref.startswith('refs/heads/'):
- return ref[len('refs/heads/'):]
+ if ref.startswith("refs/heads/"):
+ return ref[len("refs/heads/") :]
return None
@classmethod
- def get_revision_sha(cls, dest, rev):
- # type: (str, str) -> Tuple[Optional[str], bool]
+ def get_revision_sha(cls, dest: str, rev: str) -> Tuple[Optional[str], bool]:
"""
Return (sha_or_none, is_branch), where sha_or_none is a commit hash
if the revision names a remote branch or tag, otherwise None.
@@ -136,11 +140,11 @@ def get_revision_sha(cls, dest, rev):
"""
# Pass rev to pre-filter the list.
output = cls.run_command(
- ['show-ref', rev],
+ ["show-ref", rev],
cwd=dest,
show_stdout=False,
stdout_only=True,
- on_returncode='ignore',
+ on_returncode="ignore",
)
refs = {}
# NOTE: We do not use splitlines here since that would split on other
@@ -155,12 +159,12 @@ def get_revision_sha(cls, dest, rev):
except ValueError:
# Include the offending line to simplify troubleshooting if
# this error ever occurs.
- raise ValueError(f'unexpected show-ref line: {line!r}')
+ raise ValueError(f"unexpected show-ref line: {line!r}")
refs[ref_name] = ref_sha
- branch_ref = f'refs/remotes/origin/{rev}'
- tag_ref = f'refs/tags/{rev}'
+ branch_ref = f"refs/remotes/origin/{rev}"
+ tag_ref = f"refs/tags/{rev}"
sha = refs.get(branch_ref)
if sha is not None:
@@ -171,8 +175,7 @@ def get_revision_sha(cls, dest, rev):
return (sha, False)
@classmethod
- def _should_fetch(cls, dest, rev):
- # type: (str, str) -> bool
+ def _should_fetch(cls, dest: str, rev: str) -> bool:
"""
Return true if rev is a ref or is a commit that we don't have locally.
@@ -195,8 +198,9 @@ def _should_fetch(cls, dest, rev):
return True
@classmethod
- def resolve_revision(cls, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> RevOptions
+ def resolve_revision(
+ cls, dest: str, url: HiddenText, rev_options: RevOptions
+ ) -> RevOptions:
"""
Resolve a revision to a new RevOptions object with the SHA1 of the
branch, tag, or ref if found.
@@ -230,18 +234,17 @@ def resolve_revision(cls, dest, url, rev_options):
# fetch the requested revision
cls.run_command(
- make_command('fetch', '-q', url, rev_options.to_args()),
+ make_command("fetch", "-q", url, rev_options.to_args()),
cwd=dest,
)
# Change the revision to the SHA of the ref we fetched
- sha = cls.get_revision(dest, rev='FETCH_HEAD')
+ sha = cls.get_revision(dest, rev="FETCH_HEAD")
rev_options = rev_options.make_new(sha)
return rev_options
@classmethod
- def is_commit_id_equal(cls, dest, name):
- # type: (str, Optional[str]) -> bool
+ def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
"""
Return whether the current commit hash equals the given name.
@@ -255,30 +258,58 @@ def is_commit_id_equal(cls, dest, name):
return cls.get_revision(dest) == name
- def fetch_new(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def fetch_new(
+ self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+ ) -> None:
rev_display = rev_options.to_display()
- logger.info('Cloning %s%s to %s', url, rev_display, display_path(dest))
- self.run_command(make_command('clone', '-q', url, dest))
+ logger.info("Cloning %s%s to %s", url, rev_display, display_path(dest))
+ if verbosity <= 0:
+ flags: Tuple[str, ...] = ("--quiet",)
+ elif verbosity == 1:
+ flags = ()
+ else:
+ flags = ("--verbose", "--progress")
+ if self.get_git_version() >= (2, 17):
+ # Git added support for partial clone in 2.17
+ # https://git-scm.com/docs/partial-clone
+ # Speeds up cloning by functioning without a complete copy of repository
+ self.run_command(
+ make_command(
+ "clone",
+ "--filter=blob:none",
+ *flags,
+ url,
+ dest,
+ )
+ )
+ else:
+ self.run_command(make_command("clone", *flags, url, dest))
if rev_options.rev:
# Then a specific revision was requested.
rev_options = self.resolve_revision(dest, url, rev_options)
- branch_name = getattr(rev_options, 'branch_name', None)
+ branch_name = getattr(rev_options, "branch_name", None)
+ logger.debug("Rev options %s, branch_name %s", rev_options, branch_name)
if branch_name is None:
# Only do a checkout if the current commit id doesn't match
# the requested revision.
if not self.is_commit_id_equal(dest, rev_options.rev):
cmd_args = make_command(
- 'checkout', '-q', rev_options.to_args(),
+ "checkout",
+ "-q",
+ rev_options.to_args(),
)
self.run_command(cmd_args, cwd=dest)
elif self.get_current_branch(dest) != branch_name:
# Then a specific branch was requested, and that branch
# is not yet checked out.
- track_branch = f'origin/{branch_name}'
+ track_branch = f"origin/{branch_name}"
cmd_args = [
- 'checkout', '-b', branch_name, '--track', track_branch,
+ "checkout",
+ "-b",
+ branch_name,
+ "--track",
+ track_branch,
]
self.run_command(cmd_args, cwd=dest)
else:
@@ -290,35 +321,32 @@ def fetch_new(self, dest, url, rev_options):
#: repo may contain submodules
self.update_submodules(dest)
- def switch(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
self.run_command(
- make_command('config', 'remote.origin.url', url),
+ make_command("config", "remote.origin.url", url),
cwd=dest,
)
- cmd_args = make_command('checkout', '-q', rev_options.to_args())
+ cmd_args = make_command("checkout", "-q", rev_options.to_args())
self.run_command(cmd_args, cwd=dest)
self.update_submodules(dest)
- def update(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
# First fetch changes from the default remote
if self.get_git_version() >= (1, 9):
# fetch tags in addition to everything else
- self.run_command(['fetch', '-q', '--tags'], cwd=dest)
+ self.run_command(["fetch", "-q", "--tags"], cwd=dest)
else:
- self.run_command(['fetch', '-q'], cwd=dest)
+ self.run_command(["fetch", "-q"], cwd=dest)
# Then reset to wanted revision (maybe even origin/master)
rev_options = self.resolve_revision(dest, url, rev_options)
- cmd_args = make_command('reset', '--hard', '-q', rev_options.to_args())
+ cmd_args = make_command("reset", "--hard", "-q", rev_options.to_args())
self.run_command(cmd_args, cwd=dest)
#: update submodules
self.update_submodules(dest)
@classmethod
- def get_remote_url(cls, location):
- # type: (str) -> str
+ def get_remote_url(cls, location: str) -> str:
"""
Return URL of the first remote encountered.
@@ -328,8 +356,8 @@ def get_remote_url(cls, location):
# We need to pass 1 for extra_ok_returncodes since the command
# exits with return code 1 if there are no matching lines.
stdout = cls.run_command(
- ['config', '--get-regexp', r'remote\..*\.url'],
- extra_ok_returncodes=(1, ),
+ ["config", "--get-regexp", r"remote\..*\.url"],
+ extra_ok_returncodes=(1,),
show_stdout=False,
stdout_only=True,
cwd=location,
@@ -341,15 +369,14 @@ def get_remote_url(cls, location):
raise RemoteNotFoundError
for remote in remotes:
- if remote.startswith('remote.origin.url '):
+ if remote.startswith("remote.origin.url "):
found_remote = remote
break
- url = found_remote.split(' ')[1]
+ url = found_remote.split(" ")[1]
return cls._git_remote_to_pip_url(url.strip())
@staticmethod
- def _git_remote_to_pip_url(url):
- # type: (str) -> str
+ def _git_remote_to_pip_url(url: str) -> str:
"""
Convert a remote url from what git uses to what pip accepts.
@@ -380,14 +407,13 @@ def _git_remote_to_pip_url(url):
raise RemoteNotValidError(url)
@classmethod
- def has_commit(cls, location, rev):
- # type: (str, str) -> bool
+ def has_commit(cls, location: str, rev: str) -> bool:
"""
Check if rev is a commit that is available in the local repository.
"""
try:
cls.run_command(
- ['rev-parse', '-q', '--verify', "sha^" + rev],
+ ["rev-parse", "-q", "--verify", "sha^" + rev],
cwd=location,
log_failed_cmd=False,
)
@@ -397,12 +423,11 @@ def has_commit(cls, location, rev):
return True
@classmethod
- def get_revision(cls, location, rev=None):
- # type: (str, Optional[str]) -> str
+ def get_revision(cls, location: str, rev: Optional[str] = None) -> str:
if rev is None:
- rev = 'HEAD'
+ rev = "HEAD"
current_rev = cls.run_command(
- ['rev-parse', rev],
+ ["rev-parse", rev],
show_stdout=False,
stdout_only=True,
cwd=location,
@@ -410,27 +435,25 @@ def get_revision(cls, location, rev=None):
return current_rev.strip()
@classmethod
- def get_subdirectory(cls, location):
- # type: (str) -> Optional[str]
+ def get_subdirectory(cls, location: str) -> Optional[str]:
"""
Return the path to Python project root, relative to the repo root.
Return None if the project root is in the repo root.
"""
# find the repo root
git_dir = cls.run_command(
- ['rev-parse', '--git-dir'],
+ ["rev-parse", "--git-dir"],
show_stdout=False,
stdout_only=True,
cwd=location,
).strip()
if not os.path.isabs(git_dir):
git_dir = os.path.join(location, git_dir)
- repo_root = os.path.abspath(os.path.join(git_dir, '..'))
+ repo_root = os.path.abspath(os.path.join(git_dir, ".."))
return find_path_to_project_root_from_repo_root(location, repo_root)
@classmethod
- def get_url_rev_and_auth(cls, url):
- # type: (str) -> Tuple[str, Optional[str], AuthInfo]
+ def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
"""
Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
That's required because although they use SSH they sometimes don't
@@ -440,66 +463,63 @@ def get_url_rev_and_auth(cls, url):
# Works around an apparent Git bug
# (see https://article.gmane.org/gmane.comp.version-control.git/146500)
scheme, netloc, path, query, fragment = urlsplit(url)
- if scheme.endswith('file'):
- initial_slashes = path[:-len(path.lstrip('/'))]
- newpath = (
- initial_slashes +
- urllib.request.url2pathname(path)
- .replace('\\', '/').lstrip('/')
- )
- after_plus = scheme.find('+') + 1
+ if scheme.endswith("file"):
+ initial_slashes = path[: -len(path.lstrip("/"))]
+ newpath = initial_slashes + urllib.request.url2pathname(path).replace(
+ "\\", "/"
+ ).lstrip("/")
+ after_plus = scheme.find("+") + 1
url = scheme[:after_plus] + urlunsplit(
(scheme[after_plus:], netloc, newpath, query, fragment),
)
- if '://' not in url:
- assert 'file:' not in url
- url = url.replace('git+', 'git+ssh://')
+ if "://" not in url:
+ assert "file:" not in url
+ url = url.replace("git+", "git+ssh://")
url, rev, user_pass = super().get_url_rev_and_auth(url)
- url = url.replace('ssh://', '')
+ url = url.replace("ssh://", "")
else:
url, rev, user_pass = super().get_url_rev_and_auth(url)
return url, rev, user_pass
@classmethod
- def update_submodules(cls, location):
- # type: (str) -> None
- if not os.path.exists(os.path.join(location, '.gitmodules')):
+ def update_submodules(cls, location: str) -> None:
+ if not os.path.exists(os.path.join(location, ".gitmodules")):
return
cls.run_command(
- ['submodule', 'update', '--init', '--recursive', '-q'],
+ ["submodule", "update", "--init", "--recursive", "-q"],
cwd=location,
)
@classmethod
- def get_repository_root(cls, location):
- # type: (str) -> Optional[str]
+ def get_repository_root(cls, location: str) -> Optional[str]:
loc = super().get_repository_root(location)
if loc:
return loc
try:
r = cls.run_command(
- ['rev-parse', '--show-toplevel'],
+ ["rev-parse", "--show-toplevel"],
cwd=location,
show_stdout=False,
stdout_only=True,
- on_returncode='raise',
+ on_returncode="raise",
log_failed_cmd=False,
)
except BadCommand:
- logger.debug("could not determine if %s is under git control "
- "because git is not available", location)
+ logger.debug(
+ "could not determine if %s is under git control "
+ "because git is not available",
+ location,
+ )
return None
except InstallationError:
return None
- return os.path.normpath(r.rstrip('\r\n'))
+ return os.path.normpath(r.rstrip("\r\n"))
@staticmethod
- def should_add_vcs_url_prefix(repo_url):
- # type: (str) -> bool
- """In either https or ssh form, requirements must be prefixed with git+.
- """
+ def should_add_vcs_url_prefix(repo_url: str) -> bool:
+ """In either https or ssh form, requirements must be prefixed with git+."""
return True
diff --git a/pipenv/patched/notpip/_internal/vcs/mercurial.py b/pipenv/patched/notpip/_internal/vcs/mercurial.py
index f108780b60..cdc16ad173 100644
--- a/pipenv/patched/notpip/_internal/vcs/mercurial.py
+++ b/pipenv/patched/notpip/_internal/vcs/mercurial.py
@@ -1,7 +1,7 @@
import configparser
import logging
import os
-from typing import List, Optional
+from typing import List, Optional, Tuple
from pipenv.patched.notpip._internal.exceptions import BadCommand, InstallationError
from pipenv.patched.notpip._internal.utils.misc import HiddenText, display_path
@@ -18,61 +18,68 @@
class Mercurial(VersionControl):
- name = 'hg'
- dirname = '.hg'
- repo_name = 'clone'
+ name = "hg"
+ dirname = ".hg"
+ repo_name = "clone"
schemes = (
- 'hg+file', 'hg+http', 'hg+https', 'hg+ssh', 'hg+static-http',
+ "hg+file",
+ "hg+http",
+ "hg+https",
+ "hg+ssh",
+ "hg+static-http",
)
@staticmethod
- def get_base_rev_args(rev):
- # type: (str) -> List[str]
+ def get_base_rev_args(rev: str) -> List[str]:
return [rev]
- def fetch_new(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def fetch_new(
+ self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+ ) -> None:
rev_display = rev_options.to_display()
logger.info(
- 'Cloning hg %s%s to %s',
+ "Cloning hg %s%s to %s",
url,
rev_display,
display_path(dest),
)
- self.run_command(make_command('clone', '--noupdate', '-q', url, dest))
+ if verbosity <= 0:
+ flags: Tuple[str, ...] = ("--quiet",)
+ elif verbosity == 1:
+ flags = ()
+ elif verbosity == 2:
+ flags = ("--verbose",)
+ else:
+ flags = ("--verbose", "--debug")
+ self.run_command(make_command("clone", "--noupdate", *flags, url, dest))
self.run_command(
- make_command('update', '-q', rev_options.to_args()),
+ make_command("update", *flags, rev_options.to_args()),
cwd=dest,
)
- def switch(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
- repo_config = os.path.join(dest, self.dirname, 'hgrc')
+ def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ repo_config = os.path.join(dest, self.dirname, "hgrc")
config = configparser.RawConfigParser()
try:
config.read(repo_config)
- config.set('paths', 'default', url.secret)
- with open(repo_config, 'w') as config_file:
+ config.set("paths", "default", url.secret)
+ with open(repo_config, "w") as config_file:
config.write(config_file)
except (OSError, configparser.NoSectionError) as exc:
- logger.warning(
- 'Could not switch Mercurial repository to %s: %s', url, exc,
- )
+ logger.warning("Could not switch Mercurial repository to %s: %s", url, exc)
else:
- cmd_args = make_command('update', '-q', rev_options.to_args())
+ cmd_args = make_command("update", "-q", rev_options.to_args())
self.run_command(cmd_args, cwd=dest)
- def update(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
- self.run_command(['pull', '-q'], cwd=dest)
- cmd_args = make_command('update', '-q', rev_options.to_args())
+ def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
+ self.run_command(["pull", "-q"], cwd=dest)
+ cmd_args = make_command("update", "-q", rev_options.to_args())
self.run_command(cmd_args, cwd=dest)
@classmethod
- def get_remote_url(cls, location):
- # type: (str) -> str
+ def get_remote_url(cls, location: str) -> str:
url = cls.run_command(
- ['showconfig', 'paths.default'],
+ ["showconfig", "paths.default"],
show_stdout=False,
stdout_only=True,
cwd=location,
@@ -82,13 +89,12 @@ def get_remote_url(cls, location):
return url.strip()
@classmethod
- def get_revision(cls, location):
- # type: (str) -> str
+ def get_revision(cls, location: str) -> str:
"""
Return the repository-local changeset revision number, as an integer.
"""
current_revision = cls.run_command(
- ['parents', '--template={rev}'],
+ ["parents", "--template={rev}"],
show_stdout=False,
stdout_only=True,
cwd=location,
@@ -96,14 +102,13 @@ def get_revision(cls, location):
return current_revision
@classmethod
- def get_requirement_revision(cls, location):
- # type: (str) -> str
+ def get_requirement_revision(cls, location: str) -> str:
"""
Return the changeset identification hash, as a 40-character
hexadecimal string
"""
current_rev_hash = cls.run_command(
- ['parents', '--template={node}'],
+ ["parents", "--template={node}"],
show_stdout=False,
stdout_only=True,
cwd=location,
@@ -111,48 +116,48 @@ def get_requirement_revision(cls, location):
return current_rev_hash
@classmethod
- def is_commit_id_equal(cls, dest, name):
- # type: (str, Optional[str]) -> bool
+ def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
"""Always assume the versions don't match"""
return False
@classmethod
- def get_subdirectory(cls, location):
- # type: (str) -> Optional[str]
+ def get_subdirectory(cls, location: str) -> Optional[str]:
"""
Return the path to Python project root, relative to the repo root.
Return None if the project root is in the repo root.
"""
# find the repo root
repo_root = cls.run_command(
- ['root'], show_stdout=False, stdout_only=True, cwd=location
+ ["root"], show_stdout=False, stdout_only=True, cwd=location
).strip()
if not os.path.isabs(repo_root):
repo_root = os.path.abspath(os.path.join(location, repo_root))
return find_path_to_project_root_from_repo_root(location, repo_root)
@classmethod
- def get_repository_root(cls, location):
- # type: (str) -> Optional[str]
+ def get_repository_root(cls, location: str) -> Optional[str]:
loc = super().get_repository_root(location)
if loc:
return loc
try:
r = cls.run_command(
- ['root'],
+ ["root"],
cwd=location,
show_stdout=False,
stdout_only=True,
- on_returncode='raise',
+ on_returncode="raise",
log_failed_cmd=False,
)
except BadCommand:
- logger.debug("could not determine if %s is under hg control "
- "because hg is not available", location)
+ logger.debug(
+ "could not determine if %s is under hg control "
+ "because hg is not available",
+ location,
+ )
return None
except InstallationError:
return None
- return os.path.normpath(r.rstrip('\r\n'))
+ return os.path.normpath(r.rstrip("\r\n"))
vcs.register(Mercurial)
diff --git a/pipenv/patched/notpip/_internal/vcs/subversion.py b/pipenv/patched/notpip/_internal/vcs/subversion.py
index 11bb41ac7d..01f19d9be6 100644
--- a/pipenv/patched/notpip/_internal/vcs/subversion.py
+++ b/pipenv/patched/notpip/_internal/vcs/subversion.py
@@ -24,30 +24,25 @@
_svn_xml_url_re = re.compile('url="([^"]+)"')
_svn_rev_re = re.compile(r'committed-rev="(\d+)"')
_svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"')
-_svn_info_xml_url_re = re.compile(r'(.*)')
+_svn_info_xml_url_re = re.compile(r"(.*)")
class Subversion(VersionControl):
- name = 'svn'
- dirname = '.svn'
- repo_name = 'checkout'
- schemes = (
- 'svn+ssh', 'svn+http', 'svn+https', 'svn+svn', 'svn+file'
- )
+ name = "svn"
+ dirname = ".svn"
+ repo_name = "checkout"
+ schemes = ("svn+ssh", "svn+http", "svn+https", "svn+svn", "svn+file")
@classmethod
- def should_add_vcs_url_prefix(cls, remote_url):
- # type: (str) -> bool
+ def should_add_vcs_url_prefix(cls, remote_url: str) -> bool:
return True
@staticmethod
- def get_base_rev_args(rev):
- # type: (str) -> List[str]
- return ['-r', rev]
+ def get_base_rev_args(rev: str) -> List[str]:
+ return ["-r", rev]
@classmethod
- def get_revision(cls, location):
- # type: (str) -> str
+ def get_revision(cls, location: str) -> str:
"""
Return the maximum revision for all files under a given location
"""
@@ -57,9 +52,9 @@ def get_revision(cls, location):
for base, dirs, _ in os.walk(location):
if cls.dirname not in dirs:
dirs[:] = []
- continue # no sense walking uncontrolled subdirs
+ continue # no sense walking uncontrolled subdirs
dirs.remove(cls.dirname)
- entries_fn = os.path.join(base, cls.dirname, 'entries')
+ entries_fn = os.path.join(base, cls.dirname, "entries")
if not os.path.exists(entries_fn):
# FIXME: should we warn?
continue
@@ -68,21 +63,22 @@ def get_revision(cls, location):
if base == location:
assert dirurl is not None
- base = dirurl + '/' # save the root url
+ base = dirurl + "/" # save the root url
elif not dirurl or not dirurl.startswith(base):
dirs[:] = []
- continue # not part of the same svn tree, skip it
+ continue # not part of the same svn tree, skip it
revision = max(revision, localrev)
return str(revision)
@classmethod
- def get_netloc_and_auth(cls, netloc, scheme):
- # type: (str, str) -> Tuple[str, Tuple[Optional[str], Optional[str]]]
+ def get_netloc_and_auth(
+ cls, netloc: str, scheme: str
+ ) -> Tuple[str, Tuple[Optional[str], Optional[str]]]:
"""
This override allows the auth information to be passed to svn via the
--username and --password options instead of via the URL.
"""
- if scheme == 'ssh':
+ if scheme == "ssh":
# The --username and --password options can't be used for
# svn+ssh URLs, so keep the auth information in the URL.
return super().get_netloc_and_auth(netloc, scheme)
@@ -90,28 +86,27 @@ def get_netloc_and_auth(cls, netloc, scheme):
return split_auth_from_netloc(netloc)
@classmethod
- def get_url_rev_and_auth(cls, url):
- # type: (str) -> Tuple[str, Optional[str], AuthInfo]
+ def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
# hotfix the URL scheme after removing svn+ from svn+ssh:// readd it
url, rev, user_pass = super().get_url_rev_and_auth(url)
- if url.startswith('ssh://'):
- url = 'svn+' + url
+ if url.startswith("ssh://"):
+ url = "svn+" + url
return url, rev, user_pass
@staticmethod
- def make_rev_args(username, password):
- # type: (Optional[str], Optional[HiddenText]) -> CommandArgs
- extra_args = [] # type: CommandArgs
+ def make_rev_args(
+ username: Optional[str], password: Optional[HiddenText]
+ ) -> CommandArgs:
+ extra_args: CommandArgs = []
if username:
- extra_args += ['--username', username]
+ extra_args += ["--username", username]
if password:
- extra_args += ['--password', password]
+ extra_args += ["--password", password]
return extra_args
@classmethod
- def get_remote_url(cls, location):
- # type: (str) -> str
+ def get_remote_url(cls, location: str) -> str:
# In cases where the source is in a subdirectory, we have to look up in
# the location until we find a valid project root.
orig_location = location
@@ -135,30 +130,27 @@ def get_remote_url(cls, location):
return url
@classmethod
- def _get_svn_url_rev(cls, location):
- # type: (str) -> Tuple[Optional[str], int]
+ def _get_svn_url_rev(cls, location: str) -> Tuple[Optional[str], int]:
from pipenv.patched.notpip._internal.exceptions import InstallationError
- entries_path = os.path.join(location, cls.dirname, 'entries')
+ entries_path = os.path.join(location, cls.dirname, "entries")
if os.path.exists(entries_path):
with open(entries_path) as f:
data = f.read()
else: # subversion >= 1.7 does not have the 'entries' file
- data = ''
+ data = ""
url = None
- if (data.startswith('8') or
- data.startswith('9') or
- data.startswith('10')):
- entries = list(map(str.splitlines, data.split('\n\x0c\n')))
+ if data.startswith("8") or data.startswith("9") or data.startswith("10"):
+ entries = list(map(str.splitlines, data.split("\n\x0c\n")))
del entries[0][0] # get rid of the '8'
url = entries[0][3]
revs = [int(d[9]) for d in entries if len(d) > 9 and d[9]] + [0]
- elif data.startswith(' bool
+ def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
"""Always assume the versions don't match"""
return False
- def __init__(self, use_interactive=None):
- # type: (bool) -> None
+ def __init__(self, use_interactive: bool = None) -> None:
if use_interactive is None:
use_interactive = is_console_interactive()
self.use_interactive = use_interactive
@@ -206,12 +194,11 @@ def __init__(self, use_interactive=None):
# Special value definitions:
# None: Not evaluated yet.
# Empty tuple: Could not parse version.
- self._vcs_version = None # type: Optional[Tuple[int, ...]]
+ self._vcs_version: Optional[Tuple[int, ...]] = None
super().__init__()
- def call_vcs_version(self):
- # type: () -> Tuple[int, ...]
+ def call_vcs_version(self) -> Tuple[int, ...]:
"""Query the version of the currently installed Subversion client.
:return: A tuple containing the parts of the version information or
@@ -225,15 +212,13 @@ def call_vcs_version(self):
# compiled Mar 28 2018, 08:49:13 on x86_64-pc-linux-gnu
# svn, version 1.12.0-SlikSvn (SlikSvn/1.12.0)
# compiled May 28 2019, 13:44:56 on x86_64-microsoft-windows6.2
- version_prefix = 'svn, version '
- version = self.run_command(
- ['--version'], show_stdout=False, stdout_only=True
- )
+ version_prefix = "svn, version "
+ version = self.run_command(["--version"], show_stdout=False, stdout_only=True)
if not version.startswith(version_prefix):
return ()
- version = version[len(version_prefix):].split()[0]
- version_list = version.partition('-')[0].split('.')
+ version = version[len(version_prefix) :].split()[0]
+ version_list = version.partition("-")[0].split(".")
try:
parsed_version = tuple(map(int, version_list))
except ValueError:
@@ -241,8 +226,7 @@ def call_vcs_version(self):
return parsed_version
- def get_vcs_version(self):
- # type: () -> Tuple[int, ...]
+ def get_vcs_version(self) -> Tuple[int, ...]:
"""Return the version of the currently installed Subversion client.
If the version of the Subversion client has already been queried,
@@ -262,8 +246,7 @@ def get_vcs_version(self):
self._vcs_version = vcs_version
return vcs_version
- def get_remote_call_options(self):
- # type: () -> CommandArgs
+ def get_remote_call_options(self) -> CommandArgs:
"""Return options to be used on calls to Subversion that contact the server.
These options are applicable for the following ``svn`` subcommands used
@@ -278,7 +261,7 @@ def get_remote_call_options(self):
if not self.use_interactive:
# --non-interactive switch is available since Subversion 0.14.4.
# Subversion < 1.8 runs in interactive mode by default.
- return ['--non-interactive']
+ return ["--non-interactive"]
svn_version = self.get_vcs_version()
# By default, Subversion >= 1.8 runs in non-interactive mode if
@@ -290,37 +273,49 @@ def get_remote_call_options(self):
# SVN 1.7, pip should continue to support SVN 1.7. Therefore, pip
# can't safely add the option if the SVN version is < 1.8 (or unknown).
if svn_version >= (1, 8):
- return ['--force-interactive']
+ return ["--force-interactive"]
return []
- def fetch_new(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def fetch_new(
+ self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+ ) -> None:
rev_display = rev_options.to_display()
logger.info(
- 'Checking out %s%s to %s',
+ "Checking out %s%s to %s",
url,
rev_display,
display_path(dest),
)
+ if verbosity <= 0:
+ flag = "--quiet"
+ else:
+ flag = ""
cmd_args = make_command(
- 'checkout', '-q', self.get_remote_call_options(),
- rev_options.to_args(), url, dest,
+ "checkout",
+ flag,
+ self.get_remote_call_options(),
+ rev_options.to_args(),
+ url,
+ dest,
)
self.run_command(cmd_args)
- def switch(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
cmd_args = make_command(
- 'switch', self.get_remote_call_options(), rev_options.to_args(),
- url, dest,
+ "switch",
+ self.get_remote_call_options(),
+ rev_options.to_args(),
+ url,
+ dest,
)
self.run_command(cmd_args)
- def update(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
cmd_args = make_command(
- 'update', self.get_remote_call_options(), rev_options.to_args(),
+ "update",
+ self.get_remote_call_options(),
+ rev_options.to_args(),
dest,
)
self.run_command(cmd_args)
diff --git a/pipenv/patched/notpip/_internal/vcs/versioncontrol.py b/pipenv/patched/notpip/_internal/vcs/versioncontrol.py
index 9355848e76..d62ef29cfe 100644
--- a/pipenv/patched/notpip/_internal/vcs/versioncontrol.py
+++ b/pipenv/patched/notpip/_internal/vcs/versioncontrol.py
@@ -6,6 +6,7 @@
import sys
import urllib.parse
from typing import (
+ TYPE_CHECKING,
Any,
Dict,
Iterable,
@@ -30,10 +31,22 @@
is_installable_dir,
rmtree,
)
-from pipenv.patched.notpip._internal.utils.subprocess import CommandArgs, call_subprocess, make_command
+from pipenv.patched.notpip._internal.utils.subprocess import (
+ CommandArgs,
+ call_subprocess,
+ format_command_args,
+ make_command,
+)
from pipenv.patched.notpip._internal.utils.urls import get_url_scheme
-__all__ = ['vcs']
+if TYPE_CHECKING:
+ # Literal was introduced in Python 3.8.
+ #
+ # TODO: Remove `if TYPE_CHECKING` when dropping support for Python 3.7.
+ from typing import Literal
+
+
+__all__ = ["vcs"]
logger = logging.getLogger(__name__)
@@ -41,19 +54,19 @@
AuthInfo = Tuple[Optional[str], Optional[str]]
-def is_url(name):
- # type: (str) -> bool
+def is_url(name: str) -> bool:
"""
Return true if the name looks like a URL.
"""
scheme = get_url_scheme(name)
if scheme is None:
return False
- return scheme in ['http', 'https', 'file', 'ftp'] + vcs.all_schemes
+ return scheme in ["http", "https", "file", "ftp"] + vcs.all_schemes
-def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None):
- # type: (str, str, str, Optional[str]) -> str
+def make_vcs_requirement_url(
+ repo_url: str, rev: str, project_name: str, subdir: Optional[str] = None
+) -> str:
"""
Return the URL for a VCS requirement.
@@ -62,15 +75,16 @@ def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None):
project_name: the (unescaped) project name.
"""
egg_project_name = project_name.replace("-", "_")
- req = f'{repo_url}@{rev}#egg={egg_project_name}'
+ req = f"{repo_url}@{rev}#egg={egg_project_name}"
if subdir:
- req += f'&subdirectory={subdir}'
+ req += f"&subdirectory={subdir}"
return req
-def find_path_to_project_root_from_repo_root(location, repo_root):
- # type: (str, str) -> Optional[str]
+def find_path_to_project_root_from_repo_root(
+ location: str, repo_root: str
+) -> Optional[str]:
"""
Find the the Python project's root by searching up the filesystem from
`location`. Return the path to project root relative to `repo_root`.
@@ -118,11 +132,10 @@ class RevOptions:
def __init__(
self,
- vc_class, # type: Type[VersionControl]
- rev=None, # type: Optional[str]
- extra_args=None, # type: Optional[CommandArgs]
- ):
- # type: (...) -> None
+ vc_class: Type["VersionControl"],
+ rev: Optional[str] = None,
+ extra_args: Optional[CommandArgs] = None,
+ ) -> None:
"""
Args:
vc_class: a VersionControl subclass.
@@ -135,26 +148,23 @@ def __init__(
self.extra_args = extra_args
self.rev = rev
self.vc_class = vc_class
- self.branch_name = None # type: Optional[str]
+ self.branch_name: Optional[str] = None
- def __repr__(self):
- # type: () -> str
- return f''
+ def __repr__(self) -> str:
+ return f""
@property
- def arg_rev(self):
- # type: () -> Optional[str]
+ def arg_rev(self) -> Optional[str]:
if self.rev is None:
return self.vc_class.default_arg_rev
return self.rev
- def to_args(self):
- # type: () -> CommandArgs
+ def to_args(self) -> CommandArgs:
"""
Return the VCS-specific command arguments.
"""
- args = [] # type: CommandArgs
+ args: CommandArgs = []
rev = self.arg_rev
if rev is not None:
args += self.vc_class.get_base_rev_args(rev)
@@ -162,15 +172,13 @@ def to_args(self):
return args
- def to_display(self):
- # type: () -> str
+ def to_display(self) -> str:
if not self.rev:
- return ''
+ return ""
- return f' (to revision {self.rev})'
+ return f" (to revision {self.rev})"
- def make_new(self, rev):
- # type: (str) -> RevOptions
+ def make_new(self, rev: str) -> "RevOptions":
"""
Make a copy of the current instance, but with a new rev.
@@ -181,54 +189,46 @@ def make_new(self, rev):
class VcsSupport:
- _registry = {} # type: Dict[str, VersionControl]
- schemes = ['ssh', 'git', 'hg', 'bzr', 'sftp', 'svn']
+ _registry: Dict[str, "VersionControl"] = {}
+ schemes = ["ssh", "git", "hg", "bzr", "sftp", "svn"]
- def __init__(self):
- # type: () -> None
+ def __init__(self) -> None:
# Register more schemes with urlparse for various version control
# systems
urllib.parse.uses_netloc.extend(self.schemes)
super().__init__()
- def __iter__(self):
- # type: () -> Iterator[str]
+ def __iter__(self) -> Iterator[str]:
return self._registry.__iter__()
@property
- def backends(self):
- # type: () -> List[VersionControl]
+ def backends(self) -> List["VersionControl"]:
return list(self._registry.values())
@property
- def dirnames(self):
- # type: () -> List[str]
+ def dirnames(self) -> List[str]:
return [backend.dirname for backend in self.backends]
@property
- def all_schemes(self):
- # type: () -> List[str]
- schemes = [] # type: List[str]
+ def all_schemes(self) -> List[str]:
+ schemes: List[str] = []
for backend in self.backends:
schemes.extend(backend.schemes)
return schemes
- def register(self, cls):
- # type: (Type[VersionControl]) -> None
- if not hasattr(cls, 'name'):
- logger.warning('Cannot register VCS %s', cls.__name__)
+ def register(self, cls: Type["VersionControl"]) -> None:
+ if not hasattr(cls, "name"):
+ logger.warning("Cannot register VCS %s", cls.__name__)
return
if cls.name not in self._registry:
self._registry[cls.name] = cls()
- logger.debug('Registered VCS backend: %s', cls.name)
+ logger.debug("Registered VCS backend: %s", cls.name)
- def unregister(self, name):
- # type: (str) -> None
+ def unregister(self, name: str) -> None:
if name in self._registry:
del self._registry[name]
- def get_backend_for_dir(self, location):
- # type: (str) -> Optional[VersionControl]
+ def get_backend_for_dir(self, location: str) -> Optional["VersionControl"]:
"""
Return a VersionControl object if a repository of that type is found
at the given directory.
@@ -238,8 +238,7 @@ def get_backend_for_dir(self, location):
repo_path = vcs_backend.get_repository_root(location)
if not repo_path:
continue
- logger.debug('Determine that %s uses VCS: %s',
- location, vcs_backend.name)
+ logger.debug("Determine that %s uses VCS: %s", location, vcs_backend.name)
vcs_backends[repo_path] = vcs_backend
if not vcs_backends:
@@ -252,8 +251,7 @@ def get_backend_for_dir(self, location):
inner_most_repo_path = max(vcs_backends, key=len)
return vcs_backends[inner_most_repo_path]
- def get_backend_for_scheme(self, scheme):
- # type: (str) -> Optional[VersionControl]
+ def get_backend_for_scheme(self, scheme: str) -> Optional["VersionControl"]:
"""
Return a VersionControl object or None.
"""
@@ -262,8 +260,7 @@ def get_backend_for_scheme(self, scheme):
return vcs_backend
return None
- def get_backend(self, name):
- # type: (str) -> Optional[VersionControl]
+ def get_backend(self, name: str) -> Optional["VersionControl"]:
"""
Return a VersionControl object or None.
"""
@@ -275,27 +272,25 @@ def get_backend(self, name):
class VersionControl:
- name = ''
- dirname = ''
- repo_name = ''
+ name = ""
+ dirname = ""
+ repo_name = ""
# List of supported schemes for this Version Control
- schemes = () # type: Tuple[str, ...]
+ schemes: Tuple[str, ...] = ()
# Iterable of environment variable names to pass to call_subprocess().
- unset_environ = () # type: Tuple[str, ...]
- default_arg_rev = None # type: Optional[str]
+ unset_environ: Tuple[str, ...] = ()
+ default_arg_rev: Optional[str] = None
@classmethod
- def should_add_vcs_url_prefix(cls, remote_url):
- # type: (str) -> bool
+ def should_add_vcs_url_prefix(cls, remote_url: str) -> bool:
"""
Return whether the vcs prefix (e.g. "git+") should be added to a
repository's remote url when used in a requirement.
"""
- return not remote_url.lower().startswith(f'{cls.name}:')
+ return not remote_url.lower().startswith(f"{cls.name}:")
@classmethod
- def get_subdirectory(cls, location):
- # type: (str) -> Optional[str]
+ def get_subdirectory(cls, location: str) -> Optional[str]:
"""
Return the path to Python project root, relative to the repo root.
Return None if the project root is in the repo root.
@@ -303,16 +298,14 @@ def get_subdirectory(cls, location):
return None
@classmethod
- def get_requirement_revision(cls, repo_dir):
- # type: (str) -> str
+ def get_requirement_revision(cls, repo_dir: str) -> str:
"""
Return the revision string that should be used in a requirement.
"""
return cls.get_revision(repo_dir)
@classmethod
- def get_src_requirement(cls, repo_dir, project_name):
- # type: (str, str) -> str
+ def get_src_requirement(cls, repo_dir: str, project_name: str) -> str:
"""
Return the requirement string to use to redownload the files
currently at the given repository directory.
@@ -327,18 +320,16 @@ def get_src_requirement(cls, repo_dir, project_name):
repo_url = cls.get_remote_url(repo_dir)
if cls.should_add_vcs_url_prefix(repo_url):
- repo_url = f'{cls.name}+{repo_url}'
+ repo_url = f"{cls.name}+{repo_url}"
revision = cls.get_requirement_revision(repo_dir)
subdir = cls.get_subdirectory(repo_dir)
- req = make_vcs_requirement_url(repo_url, revision, project_name,
- subdir=subdir)
+ req = make_vcs_requirement_url(repo_url, revision, project_name, subdir=subdir)
return req
@staticmethod
- def get_base_rev_args(rev):
- # type: (str) -> List[str]
+ def get_base_rev_args(rev: str) -> List[str]:
"""
Return the base revision arguments for a vcs command.
@@ -347,8 +338,7 @@ def get_base_rev_args(rev):
"""
raise NotImplementedError
- def is_immutable_rev_checkout(self, url, dest):
- # type: (str, str) -> bool
+ def is_immutable_rev_checkout(self, url: str, dest: str) -> bool:
"""
Return true if the commit hash checked out at dest matches
the revision in url.
@@ -362,8 +352,9 @@ def is_immutable_rev_checkout(self, url, dest):
return False
@classmethod
- def make_rev_options(cls, rev=None, extra_args=None):
- # type: (Optional[str], Optional[CommandArgs]) -> RevOptions
+ def make_rev_options(
+ cls, rev: Optional[str] = None, extra_args: Optional[CommandArgs] = None
+ ) -> RevOptions:
"""
Return a RevOptions object.
@@ -374,18 +365,18 @@ def make_rev_options(cls, rev=None, extra_args=None):
return RevOptions(cls, rev, extra_args=extra_args)
@classmethod
- def _is_local_repository(cls, repo):
- # type: (str) -> bool
+ def _is_local_repository(cls, repo: str) -> bool:
"""
- posix absolute paths start with os.path.sep,
- win32 ones start with drive (like c:\\folder)
+ posix absolute paths start with os.path.sep,
+ win32 ones start with drive (like c:\\folder)
"""
drive, tail = os.path.splitdrive(repo)
return repo.startswith(os.path.sep) or bool(drive)
@classmethod
- def get_netloc_and_auth(cls, netloc, scheme):
- # type: (str, str) -> Tuple[str, Tuple[Optional[str], Optional[str]]]
+ def get_netloc_and_auth(
+ cls, netloc: str, scheme: str
+ ) -> Tuple[str, Tuple[Optional[str], Optional[str]]]:
"""
Parse the repository URL's netloc, and return the new netloc to use
along with auth information.
@@ -404,8 +395,7 @@ def get_netloc_and_auth(cls, netloc, scheme):
return netloc, (None, None)
@classmethod
- def get_url_rev_and_auth(cls, url):
- # type: (str) -> Tuple[str, Optional[str], AuthInfo]
+ def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]:
"""
Parse the repository URL to use, and return the URL, revision,
and auth info to use.
@@ -413,44 +403,44 @@ def get_url_rev_and_auth(cls, url):
Returns: (url, rev, (username, password)).
"""
scheme, netloc, path, query, frag = urllib.parse.urlsplit(url)
- if '+' not in scheme:
+ if "+" not in scheme:
raise ValueError(
"Sorry, {!r} is a malformed VCS url. "
"The format is +://, "
"e.g. svn+http://myrepo/svn/MyApp#egg=MyApp".format(url)
)
# Remove the vcs prefix.
- scheme = scheme.split('+', 1)[1]
+ scheme = scheme.split("+", 1)[1]
netloc, user_pass = cls.get_netloc_and_auth(netloc, scheme)
rev = None
- if '@' in path:
- path, rev = path.rsplit('@', 1)
+ if "@" in path:
+ path, rev = path.rsplit("@", 1)
if not rev:
raise InstallationError(
"The URL {!r} has an empty revision (after @) "
"which is not supported. Include a revision after @ "
"or remove @ from the URL.".format(url)
)
- url = urllib.parse.urlunsplit((scheme, netloc, path, query, ''))
+ url = urllib.parse.urlunsplit((scheme, netloc, path, query, ""))
return url, rev, user_pass
@staticmethod
- def make_rev_args(username, password):
- # type: (Optional[str], Optional[HiddenText]) -> CommandArgs
+ def make_rev_args(
+ username: Optional[str], password: Optional[HiddenText]
+ ) -> CommandArgs:
"""
Return the RevOptions "extra arguments" to use in obtain().
"""
return []
- def get_url_rev_options(self, url):
- # type: (HiddenText) -> Tuple[HiddenText, RevOptions]
+ def get_url_rev_options(self, url: HiddenText) -> Tuple[HiddenText, RevOptions]:
"""
Return the URL and RevOptions object to use in obtain(),
as a tuple (url, rev_options).
"""
secret_url, rev, user_pass = self.get_url_rev_and_auth(url.secret)
username, secret_password = user_pass
- password = None # type: Optional[HiddenText]
+ password: Optional[HiddenText] = None
if secret_password is not None:
password = hide_value(secret_password)
extra_args = self.make_rev_args(username, password)
@@ -459,24 +449,23 @@ def get_url_rev_options(self, url):
return hide_url(secret_url), rev_options
@staticmethod
- def normalize_url(url):
- # type: (str) -> str
+ def normalize_url(url: str) -> str:
"""
Normalize a URL for comparison by unquoting it and removing any
trailing slash.
"""
- return urllib.parse.unquote(url).rstrip('/')
+ return urllib.parse.unquote(url).rstrip("/")
@classmethod
- def compare_urls(cls, url1, url2):
- # type: (str, str) -> bool
+ def compare_urls(cls, url1: str, url2: str) -> bool:
"""
Compare two repo URLs for identity, ignoring incidental differences.
"""
- return (cls.normalize_url(url1) == cls.normalize_url(url2))
+ return cls.normalize_url(url1) == cls.normalize_url(url2)
- def fetch_new(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def fetch_new(
+ self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int
+ ) -> None:
"""
Fetch a revision from a repository, in the case that this is the
first fetch from the repository.
@@ -484,11 +473,11 @@ def fetch_new(self, dest, url, rev_options):
Args:
dest: the directory to fetch the repository to.
rev_options: a RevOptions object.
+ verbosity: verbosity level.
"""
raise NotImplementedError
- def switch(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def switch(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
"""
Switch the repo at ``dest`` to point to ``URL``.
@@ -497,8 +486,7 @@ def switch(self, dest, url, rev_options):
"""
raise NotImplementedError
- def update(self, dest, url, rev_options):
- # type: (str, HiddenText, RevOptions) -> None
+ def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None:
"""
Update an already-existing repo to the given ``rev_options``.
@@ -508,8 +496,7 @@ def update(self, dest, url, rev_options):
raise NotImplementedError
@classmethod
- def is_commit_id_equal(cls, dest, name):
- # type: (str, Optional[str]) -> bool
+ def is_commit_id_equal(cls, dest: str, name: Optional[str]) -> bool:
"""
Return whether the id of the current commit equals the given name.
@@ -519,19 +506,19 @@ def is_commit_id_equal(cls, dest, name):
"""
raise NotImplementedError
- def obtain(self, dest, url):
- # type: (str, HiddenText) -> None
+ def obtain(self, dest: str, url: HiddenText, verbosity: int) -> None:
"""
Install or update in editable mode the package represented by this
VersionControl object.
:param dest: the repository directory in which to install or update.
:param url: the repository URL starting with a vcs prefix.
+ :param verbosity: verbosity level.
"""
url, rev_options = self.get_url_rev_options(url)
if not os.path.exists(dest):
- self.fetch_new(dest, url, rev_options)
+ self.fetch_new(dest, url, rev_options, verbosity=verbosity)
return
rev_display = rev_options.to_display()
@@ -539,73 +526,68 @@ def obtain(self, dest, url):
existing_url = self.get_remote_url(dest)
if self.compare_urls(existing_url, url.secret):
logger.debug(
- '%s in %s exists, and has correct URL (%s)',
+ "%s in %s exists, and has correct URL (%s)",
self.repo_name.title(),
display_path(dest),
url,
)
if not self.is_commit_id_equal(dest, rev_options.rev):
logger.info(
- 'Updating %s %s%s',
+ "Updating %s %s%s",
display_path(dest),
self.repo_name,
rev_display,
)
self.update(dest, url, rev_options)
else:
- logger.info('Skipping because already up-to-date.')
+ logger.info("Skipping because already up-to-date.")
return
logger.warning(
- '%s %s in %s exists with URL %s',
+ "%s %s in %s exists with URL %s",
self.name,
self.repo_name,
display_path(dest),
existing_url,
)
- prompt = ('(s)witch, (i)gnore, (w)ipe, (b)ackup ',
- ('s', 'i', 'w', 'b'))
+ prompt = ("(s)witch, (i)gnore, (w)ipe, (b)ackup ", ("s", "i", "w", "b"))
else:
logger.warning(
- 'Directory %s already exists, and is not a %s %s.',
+ "Directory %s already exists, and is not a %s %s.",
dest,
self.name,
self.repo_name,
)
# https://github.com/python/mypy/issues/1174
- prompt = ('(i)gnore, (w)ipe, (b)ackup ', # type: ignore
- ('i', 'w', 'b'))
+ prompt = ("(i)gnore, (w)ipe, (b)ackup ", ("i", "w", "b")) # type: ignore
logger.warning(
- 'The plan is to install the %s repository %s',
+ "The plan is to install the %s repository %s",
self.name,
url,
)
- response = ask_path_exists('What to do? {}'.format(
- prompt[0]), prompt[1])
+ response = ask_path_exists("What to do? {}".format(prompt[0]), prompt[1])
- if response == 'a':
+ if response == "a":
sys.exit(-1)
- if response == 'w':
- logger.warning('Deleting %s', display_path(dest))
+ if response == "w":
+ logger.warning("Deleting %s", display_path(dest))
rmtree(dest)
- self.fetch_new(dest, url, rev_options)
+ self.fetch_new(dest, url, rev_options, verbosity=verbosity)
return
- if response == 'b':
+ if response == "b":
dest_dir = backup_dir(dest)
- logger.warning(
- 'Backing up %s to %s', display_path(dest), dest_dir,
- )
+ logger.warning("Backing up %s to %s", display_path(dest), dest_dir)
shutil.move(dest, dest_dir)
- self.fetch_new(dest, url, rev_options)
+ self.fetch_new(dest, url, rev_options, verbosity=verbosity)
return
# Do nothing if the response is "i".
- if response == 's':
+ if response == "s":
logger.info(
- 'Switching %s %s to %s%s',
+ "Switching %s %s to %s%s",
self.repo_name,
display_path(dest),
url,
@@ -613,21 +595,20 @@ def obtain(self, dest, url):
)
self.switch(dest, url, rev_options)
- def unpack(self, location, url):
- # type: (str, HiddenText) -> None
+ def unpack(self, location: str, url: HiddenText, verbosity: int) -> None:
"""
Clean up current location and download the url repository
(and vcs infos) into location
:param url: the repository URL starting with a vcs prefix.
+ :param verbosity: verbosity level.
"""
if os.path.exists(location):
rmtree(location)
- self.obtain(location, url=url)
+ self.obtain(location, url=url, verbosity=verbosity)
@classmethod
- def get_remote_url(cls, location):
- # type: (str) -> str
+ def get_remote_url(cls, location: str) -> str:
"""
Return the url used at location
@@ -637,8 +618,7 @@ def get_remote_url(cls, location):
raise NotImplementedError
@classmethod
- def get_revision(cls, location):
- # type: (str) -> str
+ def get_revision(cls, location: str) -> str:
"""
Return the current commit id of the files at the given location.
"""
@@ -647,40 +627,46 @@ def get_revision(cls, location):
@classmethod
def run_command(
cls,
- cmd, # type: Union[List[str], CommandArgs]
- show_stdout=True, # type: bool
- cwd=None, # type: Optional[str]
- on_returncode='raise', # type: str
- extra_ok_returncodes=None, # type: Optional[Iterable[int]]
- command_desc=None, # type: Optional[str]
- extra_environ=None, # type: Optional[Mapping[str, Any]]
- spinner=None, # type: Optional[SpinnerInterface]
- log_failed_cmd=True, # type: bool
- stdout_only=False, # type: bool
- ):
- # type: (...) -> str
+ cmd: Union[List[str], CommandArgs],
+ show_stdout: bool = True,
+ cwd: Optional[str] = None,
+ on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise",
+ extra_ok_returncodes: Optional[Iterable[int]] = None,
+ command_desc: Optional[str] = None,
+ extra_environ: Optional[Mapping[str, Any]] = None,
+ spinner: Optional[SpinnerInterface] = None,
+ log_failed_cmd: bool = True,
+ stdout_only: bool = False,
+ ) -> str:
"""
Run a VCS subcommand
This is simply a wrapper around call_subprocess that adds the VCS
command name, and checks that the VCS is available
"""
cmd = make_command(cls.name, *cmd)
+ if command_desc is None:
+ command_desc = format_command_args(cmd)
try:
- return call_subprocess(cmd, show_stdout, cwd,
- on_returncode=on_returncode,
- extra_ok_returncodes=extra_ok_returncodes,
- command_desc=command_desc,
- extra_environ=extra_environ,
- unset_environ=cls.unset_environ,
- spinner=spinner,
- log_failed_cmd=log_failed_cmd,
- stdout_only=stdout_only)
+ return call_subprocess(
+ cmd,
+ show_stdout,
+ cwd,
+ on_returncode=on_returncode,
+ extra_ok_returncodes=extra_ok_returncodes,
+ command_desc=command_desc,
+ extra_environ=extra_environ,
+ unset_environ=cls.unset_environ,
+ spinner=spinner,
+ log_failed_cmd=log_failed_cmd,
+ stdout_only=stdout_only,
+ )
except FileNotFoundError:
# errno.ENOENT = no such file or directory
# In other words, the VCS executable isn't available
raise BadCommand(
- f'Cannot find command {cls.name!r} - do you have '
- f'{cls.name!r} installed and in your PATH?')
+ f"Cannot find command {cls.name!r} - do you have "
+ f"{cls.name!r} installed and in your PATH?"
+ )
except PermissionError:
# errno.EACCES = Permission denied
# This error occurs, for instance, when the command is installed
@@ -695,18 +681,15 @@ def run_command(
)
@classmethod
- def is_repository_directory(cls, path):
- # type: (str) -> bool
+ def is_repository_directory(cls, path: str) -> bool:
"""
Return whether a directory path is a repository directory.
"""
- logger.debug('Checking in %s for %s (%s)...',
- path, cls.dirname, cls.name)
+ logger.debug("Checking in %s for %s (%s)...", path, cls.dirname, cls.name)
return os.path.exists(os.path.join(path, cls.dirname))
@classmethod
- def get_repository_root(cls, location):
- # type: (str) -> Optional[str]
+ def get_repository_root(cls, location: str) -> Optional[str]:
"""
Return the "root" (top-level) directory controlled by the vcs,
or `None` if the directory is not in any.
diff --git a/pipenv/patched/notpip/_internal/wheel_builder.py b/pipenv/patched/notpip/_internal/wheel_builder.py
index ddb5bf788a..9cbd9abe60 100644
--- a/pipenv/patched/notpip/_internal/wheel_builder.py
+++ b/pipenv/patched/notpip/_internal/wheel_builder.py
@@ -12,10 +12,11 @@
from pipenv.patched.notpip._internal.cache import WheelCache
from pipenv.patched.notpip._internal.exceptions import InvalidWheelFilename, UnsupportedWheel
-from pipenv.patched.notpip._internal.metadata import get_wheel_distribution
+from pipenv.patched.notpip._internal.metadata import FilesystemWheel, get_wheel_distribution
from pipenv.patched.notpip._internal.models.link import Link
from pipenv.patched.notpip._internal.models.wheel import Wheel
from pipenv.patched.notpip._internal.operations.build.wheel import build_wheel_pep517
+from pipenv.patched.notpip._internal.operations.build.wheel_editable import build_wheel_editable
from pipenv.patched.notpip._internal.operations.build.wheel_legacy import build_wheel_legacy
from pipenv.patched.notpip._internal.req.req_install import InstallRequirement
from pipenv.patched.notpip._internal.utils.logging import indent_log
@@ -28,14 +29,13 @@
logger = logging.getLogger(__name__)
-_egg_info_re = re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.IGNORECASE)
+_egg_info_re = re.compile(r"([a-z0-9_.]+)-([a-z0-9_.!+-]+)", re.IGNORECASE)
BinaryAllowedPredicate = Callable[[InstallRequirement], bool]
BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]]
-def _contains_egg_info(s):
- # type: (str) -> bool
+def _contains_egg_info(s: str) -> bool:
"""Determine whether the string looks like an egg_info.
:param s: The string to parse. E.g. foo-2.1
@@ -44,11 +44,10 @@ def _contains_egg_info(s):
def _should_build(
- req, # type: InstallRequirement
- need_wheel, # type: bool
- check_binary_allowed, # type: BinaryAllowedPredicate
-):
- # type: (...) -> bool
+ req: InstallRequirement,
+ need_wheel: bool,
+ check_binary_allowed: BinaryAllowedPredicate,
+) -> bool:
"""Return whether an InstallRequirement should be built into a wheel."""
if req.constraint:
# never build requirements that are merely constraints
@@ -56,7 +55,8 @@ def _should_build(
if req.is_wheel:
if need_wheel:
logger.info(
- 'Skipping %s, due to already being wheel.', req.name,
+ "Skipping %s, due to already being wheel.",
+ req.name,
)
return False
@@ -67,16 +67,20 @@ def _should_build(
# From this point, this concerns the pip install command only
# (need_wheel=False).
- if req.editable or not req.source_dir:
+ if not req.source_dir:
return False
+ if req.editable:
+ # we only build PEP 660 editable requirements
+ return req.supports_pyproject_editable()
+
if req.use_pep517:
return True
if not check_binary_allowed(req):
logger.info(
- "Skipping wheel build for %s, due to binaries "
- "being disabled for it.", req.name,
+ "Skipping wheel build for %s, due to binaries being disabled for it.",
+ req.name,
)
return False
@@ -84,7 +88,8 @@ def _should_build(
# we don't build legacy requirements if wheel is not installed
logger.info(
"Using legacy 'setup.py install' for %s, "
- "since package 'wheel' is not installed.", req.name,
+ "since package 'wheel' is not installed.",
+ req.name,
)
return False
@@ -92,28 +97,23 @@ def _should_build(
def should_build_for_wheel_command(
- req, # type: InstallRequirement
-):
- # type: (...) -> bool
- return _should_build(
- req, need_wheel=True, check_binary_allowed=_always_true
- )
+ req: InstallRequirement,
+) -> bool:
+ return _should_build(req, need_wheel=True, check_binary_allowed=_always_true)
def should_build_for_install_command(
- req, # type: InstallRequirement
- check_binary_allowed, # type: BinaryAllowedPredicate
-):
- # type: (...) -> bool
+ req: InstallRequirement,
+ check_binary_allowed: BinaryAllowedPredicate,
+) -> bool:
return _should_build(
req, need_wheel=False, check_binary_allowed=check_binary_allowed
)
def _should_cache(
- req, # type: InstallRequirement
-):
- # type: (...) -> Optional[bool]
+ req: InstallRequirement,
+) -> Optional[bool]:
"""
Return whether a built InstallRequirement can be stored in the persistent
wheel cache, assuming the wheel cache is available, and _should_build()
@@ -144,10 +144,9 @@ def _should_cache(
def _get_cache_dir(
- req, # type: InstallRequirement
- wheel_cache, # type: WheelCache
-):
- # type: (...) -> str
+ req: InstallRequirement,
+ wheel_cache: WheelCache,
+) -> str:
"""Return the persistent or temporary cache directory where the built
wheel need to be stored.
"""
@@ -160,13 +159,11 @@ def _get_cache_dir(
return cache_dir
-def _always_true(_):
- # type: (Any) -> bool
+def _always_true(_: Any) -> bool:
return True
-def _verify_one(req, wheel_path):
- # type: (InstallRequirement, str) -> None
+def _verify_one(req: InstallRequirement, wheel_path: str) -> None:
canonical_name = canonicalize_name(req.name or "")
w = Wheel(os.path.basename(wheel_path))
if canonicalize_name(w.name) != canonical_name:
@@ -174,7 +171,7 @@ def _verify_one(req, wheel_path):
"Wheel has unexpected file name: expected {!r}, "
"got {!r}".format(canonical_name, w.name),
)
- dist = get_wheel_distribution(wheel_path, canonical_name)
+ dist = get_wheel_distribution(FilesystemWheel(wheel_path), canonical_name)
dist_verstr = str(dist.version)
if canonicalize_version(dist_verstr) != canonicalize_version(w.version):
raise InvalidWheelFilename(
@@ -189,8 +186,7 @@ def _verify_one(req, wheel_path):
except InvalidVersion:
msg = f"Invalid Metadata-Version: {metadata_version_value}"
raise UnsupportedWheel(msg)
- if (metadata_version >= Version("1.2")
- and not isinstance(dist.version, Version)):
+ if metadata_version >= Version("1.2") and not isinstance(dist.version, Version):
raise UnsupportedWheel(
"Metadata 1.2 mandates PEP 440 version, "
"but {!r} is not".format(dist_verstr)
@@ -198,47 +194,50 @@ def _verify_one(req, wheel_path):
def _build_one(
- req, # type: InstallRequirement
- output_dir, # type: str
- verify, # type: bool
- build_options, # type: List[str]
- global_options, # type: List[str]
-):
- # type: (...) -> Optional[str]
+ req: InstallRequirement,
+ output_dir: str,
+ verify: bool,
+ build_options: List[str],
+ global_options: List[str],
+ editable: bool,
+) -> Optional[str]:
"""Build one wheel.
:return: The filename of the built wheel, or None if the build failed.
"""
+ artifact = "editable" if editable else "wheel"
try:
ensure_dir(output_dir)
except OSError as e:
logger.warning(
- "Building wheel for %s failed: %s",
- req.name, e,
+ "Building %s for %s failed: %s",
+ artifact,
+ req.name,
+ e,
)
return None
# Install build deps into temporary directory (PEP 518)
with req.build_env:
wheel_path = _build_one_inside_env(
- req, output_dir, build_options, global_options
+ req, output_dir, build_options, global_options, editable
)
if wheel_path and verify:
try:
_verify_one(req, wheel_path)
except (InvalidWheelFilename, UnsupportedWheel) as e:
- logger.warning("Built wheel for %s is invalid: %s", req.name, e)
+ logger.warning("Built %s for %s is invalid: %s", artifact, req.name, e)
return None
return wheel_path
def _build_one_inside_env(
- req, # type: InstallRequirement
- output_dir, # type: str
- build_options, # type: List[str]
- global_options, # type: List[str]
-):
- # type: (...) -> Optional[str]
+ req: InstallRequirement,
+ output_dir: str,
+ build_options: List[str],
+ global_options: List[str],
+ editable: bool,
+) -> Optional[str]:
with TempDirectory(kind="wheel") as temp_dir:
assert req.name
if req.use_pep517:
@@ -246,18 +245,26 @@ def _build_one_inside_env(
assert req.pep517_backend
if global_options:
logger.warning(
- 'Ignoring --global-option when building %s using PEP 517', req.name
+ "Ignoring --global-option when building %s using PEP 517", req.name
)
if build_options:
logger.warning(
- 'Ignoring --build-option when building %s using PEP 517', req.name
+ "Ignoring --build-option when building %s using PEP 517", req.name
+ )
+ if editable:
+ wheel_path = build_wheel_editable(
+ name=req.name,
+ backend=req.pep517_backend,
+ metadata_directory=req.metadata_directory,
+ tempd=temp_dir.path,
+ )
+ else:
+ wheel_path = build_wheel_pep517(
+ name=req.name,
+ backend=req.pep517_backend,
+ metadata_directory=req.metadata_directory,
+ tempd=temp_dir.path,
)
- wheel_path = build_wheel_pep517(
- name=req.name,
- backend=req.pep517_backend,
- metadata_directory=req.metadata_directory,
- tempd=temp_dir.path,
- )
else:
wheel_path = build_wheel_legacy(
name=req.name,
@@ -274,16 +281,20 @@ def _build_one_inside_env(
try:
wheel_hash, length = hash_file(wheel_path)
shutil.move(wheel_path, dest_path)
- logger.info('Created wheel for %s: '
- 'filename=%s size=%d sha256=%s',
- req.name, wheel_name, length,
- wheel_hash.hexdigest())
- logger.info('Stored in directory: %s', output_dir)
+ logger.info(
+ "Created wheel for %s: filename=%s size=%d sha256=%s",
+ req.name,
+ wheel_name,
+ length,
+ wheel_hash.hexdigest(),
+ )
+ logger.info("Stored in directory: %s", output_dir)
return dest_path
except Exception as e:
logger.warning(
"Building wheel for %s failed: %s",
- req.name, e,
+ req.name,
+ e,
)
# Ignore return, we can't do anything else useful.
if not req.use_pep517:
@@ -291,30 +302,30 @@ def _build_one_inside_env(
return None
-def _clean_one_legacy(req, global_options):
- # type: (InstallRequirement, List[str]) -> bool
+def _clean_one_legacy(req: InstallRequirement, global_options: List[str]) -> bool:
clean_args = make_setuptools_clean_args(
req.setup_py_path,
global_options=global_options,
)
- logger.info('Running setup.py clean for %s', req.name)
+ logger.info("Running setup.py clean for %s", req.name)
try:
- call_subprocess(clean_args, cwd=req.source_dir)
+ call_subprocess(
+ clean_args, command_desc="python setup.py clean", cwd=req.source_dir
+ )
return True
except Exception:
- logger.error('Failed cleaning build dir for %s', req.name)
+ logger.error("Failed cleaning build dir for %s", req.name)
return False
def build(
- requirements, # type: Iterable[InstallRequirement]
- wheel_cache, # type: WheelCache
- verify, # type: bool
- build_options, # type: List[str]
- global_options, # type: List[str]
-):
- # type: (...) -> BuildResult
+ requirements: Iterable[InstallRequirement],
+ wheel_cache: WheelCache,
+ verify: bool,
+ build_options: List[str],
+ global_options: List[str],
+) -> BuildResult:
"""Build wheels.
:return: The list of InstallRequirement that succeeded to build and
@@ -325,16 +336,22 @@ def build(
# Build the wheels.
logger.info(
- 'Building wheels for collected packages: %s',
- ', '.join(req.name for req in requirements), # type: ignore
+ "Building wheels for collected packages: %s",
+ ", ".join(req.name for req in requirements), # type: ignore
)
with indent_log():
build_successes, build_failures = [], []
for req in requirements:
+ assert req.name
cache_dir = _get_cache_dir(req, wheel_cache)
wheel_file = _build_one(
- req, cache_dir, verify, build_options, global_options
+ req,
+ cache_dir,
+ verify,
+ build_options,
+ global_options,
+ req.editable and req.permit_editable_wheels,
)
if wheel_file:
# Update the link for this.
@@ -348,13 +365,13 @@ def build(
# notify success/failure
if build_successes:
logger.info(
- 'Successfully built %s',
- ' '.join([req.name for req in build_successes]), # type: ignore
+ "Successfully built %s",
+ " ".join([req.name for req in build_successes]), # type: ignore
)
if build_failures:
logger.info(
- 'Failed to build %s',
- ' '.join([req.name for req in build_failures]), # type: ignore
+ "Failed to build %s",
+ " ".join([req.name for req in build_failures]), # type: ignore
)
# Return a list of requirements that failed to build
return build_successes, build_failures
diff --git a/pipenv/patched/notpip/_vendor/__init__.py b/pipenv/patched/notpip/_vendor/__init__.py
index d6c55e38b7..94c2e826cf 100644
--- a/pipenv/patched/notpip/_vendor/__init__.py
+++ b/pipenv/patched/notpip/_vendor/__init__.py
@@ -58,7 +58,6 @@ def vendored(modulename):
sys.path[:] = glob.glob(os.path.join(WHEEL_DIR, "*.whl")) + sys.path
# Actually alias all of our vendored dependencies.
- vendored("appdirs")
vendored("cachecontrol")
vendored("certifi")
vendored("colorama")
@@ -74,6 +73,7 @@ def vendored(modulename):
vendored("packaging.specifiers")
vendored("pep517")
vendored("pkg_resources")
+ vendored("platformdirs")
vendored("progress")
vendored("requests")
vendored("requests.exceptions")
diff --git a/pipenv/patched/notpip/_vendor/appdirs.LICENSE.txt b/pipenv/patched/notpip/_vendor/appdirs.LICENSE.txt
deleted file mode 100644
index 107c61405e..0000000000
--- a/pipenv/patched/notpip/_vendor/appdirs.LICENSE.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-# This is the MIT license
-
-Copyright (c) 2010 ActiveState Software Inc.
-
-Permission is hereby granted, free of charge, to any person obtaining a
-copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be included
-in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
diff --git a/pipenv/patched/notpip/_vendor/appdirs.py b/pipenv/patched/notpip/_vendor/appdirs.py
deleted file mode 100644
index 65501092f9..0000000000
--- a/pipenv/patched/notpip/_vendor/appdirs.py
+++ /dev/null
@@ -1,633 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# Copyright (c) 2005-2010 ActiveState Software Inc.
-# Copyright (c) 2013 Eddy Petrișor
-
-"""Utilities for determining application-specific dirs.
-
-See for details and usage.
-"""
-# Dev Notes:
-# - MSDN on where to store app data files:
-# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
-# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
-# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
-
-__version__ = "1.4.4"
-__version_info__ = tuple(int(segment) for segment in __version__.split("."))
-
-
-import sys
-import os
-
-PY3 = sys.version_info[0] == 3
-
-if PY3:
- unicode = str
-
-if sys.platform.startswith('java'):
- import platform
- os_name = platform.java_ver()[3][0]
- if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc.
- system = 'win32'
- elif os_name.startswith('Mac'): # "Mac OS X", etc.
- system = 'darwin'
- else: # "Linux", "SunOS", "FreeBSD", etc.
- # Setting this to "linux2" is not ideal, but only Windows or Mac
- # are actually checked for and the rest of the module expects
- # *sys.platform* style strings.
- system = 'linux2'
-elif sys.platform == 'cli' and os.name == 'nt':
- # Detect Windows in IronPython to match pipenv.patched.notpip._internal.utils.compat.WINDOWS
- # Discussion:
- system = 'win32'
-else:
- system = sys.platform
-
-
-
-def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
- r"""Return full path to the user-specific data dir for this application.
-
- "appname" is the name of application.
- If None, just the system directory is returned.
- "appauthor" (only used on Windows) is the name of the
- appauthor or distributing body for this application. Typically
- it is the owning company name. This falls back to appname. You may
- pass False to disable it.
- "version" is an optional version path element to append to the
- path. You might want to use this if you want multiple versions
- of your app to be able to run independently. If used, this
- would typically be ".".
- Only applied when appname is present.
- "roaming" (boolean, default False) can be set True to use the Windows
- roaming appdata directory. That means that for users on a Windows
- network setup for roaming profiles, this user data will be
- sync'd on login. See
-
- for a discussion of issues.
-
- Typical user data directories are:
- Mac OS X: ~/Library/Application Support/ # or ~/.config/, if the other does not exist
- Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined
- Win XP (not roaming): C:\Documents and Settings\\Application Data\\
- Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\
- Win 7 (not roaming): C:\Users\\AppData\Local\\
- Win 7 (roaming): C:\Users\\AppData\Roaming\\
-
- For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
- That means, by default "~/.local/share/".
- """
- if system == "win32":
- if appauthor is None:
- appauthor = appname
- const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
- path = os.path.normpath(_get_win_folder(const))
- if appname:
- if appauthor is not False:
- path = os.path.join(path, appauthor, appname)
- else:
- path = os.path.join(path, appname)
- elif system == 'darwin':
- path = os.path.expanduser('~/Library/Application Support/')
- if appname:
- path = os.path.join(path, appname)
- else:
- path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share"))
- if appname:
- path = os.path.join(path, appname)
- if appname and version:
- path = os.path.join(path, version)
- return path
-
-
-def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
- r"""Return full path to the user-shared data dir for this application.
-
- "appname" is the name of application.
- If None, just the system directory is returned.
- "appauthor" (only used on Windows) is the name of the
- appauthor or distributing body for this application. Typically
- it is the owning company name. This falls back to appname. You may
- pass False to disable it.
- "version" is an optional version path element to append to the
- path. You might want to use this if you want multiple versions
- of your app to be able to run independently. If used, this
- would typically be ".".
- Only applied when appname is present.
- "multipath" is an optional parameter only applicable to *nix
- which indicates that the entire list of data dirs should be
- returned. By default, the first item from XDG_DATA_DIRS is
- returned, or '/usr/local/share/',
- if XDG_DATA_DIRS is not set
-
- Typical site data directories are:
- Mac OS X: /Library/Application Support/
- Unix: /usr/local/share/ or /usr/share/
- Win XP: C:\Documents and Settings\All Users\Application Data\\
- Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
- Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7.
-
- For Unix, this is using the $XDG_DATA_DIRS[0] default.
-
- WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
- """
- if system == "win32":
- if appauthor is None:
- appauthor = appname
- path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
- if appname:
- if appauthor is not False:
- path = os.path.join(path, appauthor, appname)
- else:
- path = os.path.join(path, appname)
- elif system == 'darwin':
- path = os.path.expanduser('/Library/Application Support')
- if appname:
- path = os.path.join(path, appname)
- else:
- # XDG default for $XDG_DATA_DIRS
- # only first, if multipath is False
- path = os.getenv('XDG_DATA_DIRS',
- os.pathsep.join(['/usr/local/share', '/usr/share']))
- pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
- if appname:
- if version:
- appname = os.path.join(appname, version)
- pathlist = [os.path.join(x, appname) for x in pathlist]
-
- if multipath:
- path = os.pathsep.join(pathlist)
- else:
- path = pathlist[0]
- return path
-
- if appname and version:
- path = os.path.join(path, version)
- return path
-
-
-def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
- r"""Return full path to the user-specific config dir for this application.
-
- "appname" is the name of application.
- If None, just the system directory is returned.
- "appauthor" (only used on Windows) is the name of the
- appauthor or distributing body for this application. Typically
- it is the owning company name. This falls back to appname. You may
- pass False to disable it.
- "version" is an optional version path element to append to the
- path. You might want to use this if you want multiple versions
- of your app to be able to run independently. If used, this
- would typically be ".".
- Only applied when appname is present.
- "roaming" (boolean, default False) can be set True to use the Windows
- roaming appdata directory. That means that for users on a Windows
- network setup for roaming profiles, this user data will be
- sync'd on login. See
-
- for a discussion of issues.
-
- Typical user config directories are:
- Mac OS X: same as user_data_dir
- Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined
- Win *: same as user_data_dir
-
- For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
- That means, by default "~/.config/".
- """
- if system in ["win32", "darwin"]:
- path = user_data_dir(appname, appauthor, None, roaming)
- else:
- path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
- if appname:
- path = os.path.join(path, appname)
- if appname and version:
- path = os.path.join(path, version)
- return path
-
-
-# for the discussion regarding site_config_dir locations
-# see
-def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
- r"""Return full path to the user-shared data dir for this application.
-
- "appname" is the name of application.
- If None, just the system directory is returned.
- "appauthor" (only used on Windows) is the name of the
- appauthor or distributing body for this application. Typically
- it is the owning company name. This falls back to appname. You may
- pass False to disable it.
- "version" is an optional version path element to append to the
- path. You might want to use this if you want multiple versions
- of your app to be able to run independently. If used, this
- would typically be ".".
- Only applied when appname is present.
- "multipath" is an optional parameter only applicable to *nix
- which indicates that the entire list of config dirs should be
- returned. By default, the first item from XDG_CONFIG_DIRS is
- returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set
-
- Typical site config directories are:
- Mac OS X: same as site_data_dir
- Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in
- $XDG_CONFIG_DIRS
- Win *: same as site_data_dir
- Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
-
- For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False
-
- WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
- """
- if system in ["win32", "darwin"]:
- path = site_data_dir(appname, appauthor)
- if appname and version:
- path = os.path.join(path, version)
- else:
- # XDG default for $XDG_CONFIG_DIRS (missing or empty)
- # see
- # only first, if multipath is False
- path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg'
- pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) if x]
- if appname:
- if version:
- appname = os.path.join(appname, version)
- pathlist = [os.path.join(x, appname) for x in pathlist]
-
- if multipath:
- path = os.pathsep.join(pathlist)
- else:
- path = pathlist[0]
- return path
-
-
-def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
- r"""Return full path to the user-specific cache dir for this application.
-
- "appname" is the name of application.
- If None, just the system directory is returned.
- "appauthor" (only used on Windows) is the name of the
- appauthor or distributing body for this application. Typically
- it is the owning company name. This falls back to appname. You may
- pass False to disable it.
- "version" is an optional version path element to append to the
- path. You might want to use this if you want multiple versions
- of your app to be able to run independently. If used, this
- would typically be ".".
- Only applied when appname is present.
- "opinion" (boolean) can be False to disable the appending of
- "Cache" to the base app data dir for Windows. See
- discussion below.
-
- Typical user cache directories are:
- Mac OS X: ~/Library/Caches/
- Unix: ~/.cache/ (XDG default)
- Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache
- Vista: C:\Users\\AppData\Local\\\Cache
-
- On Windows the only suggestion in the MSDN docs is that local settings go in
- the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
- app data dir (the default returned by `user_data_dir` above). Apps typically
- put cache data somewhere *under* the given dir here. Some examples:
- ...\Mozilla\Firefox\Profiles\\Cache
- ...\Acme\SuperApp\Cache\1.0
- OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
- This can be disabled with the `opinion=False` option.
- """
- if system == "win32":
- if appauthor is None:
- appauthor = appname
- path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
- # When using Python 2, return paths as bytes on Windows like we do on
- # other operating systems. See helper function docs for more details.
- if not PY3 and isinstance(path, unicode):
- path = _win_path_to_bytes(path)
- if appname:
- if appauthor is not False:
- path = os.path.join(path, appauthor, appname)
- else:
- path = os.path.join(path, appname)
- if opinion:
- path = os.path.join(path, "Cache")
- elif system == 'darwin':
- path = os.path.expanduser('~/Library/Caches')
- if appname:
- path = os.path.join(path, appname)
- else:
- path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
- if appname:
- path = os.path.join(path, appname)
- if appname and version:
- path = os.path.join(path, version)
- return path
-
-
-def user_state_dir(appname=None, appauthor=None, version=None, roaming=False):
- r"""Return full path to the user-specific state dir for this application.
-
- "appname" is the name of application.
- If None, just the system directory is returned.
- "appauthor" (only used on Windows) is the name of the
- appauthor or distributing body for this application. Typically
- it is the owning company name. This falls back to appname. You may
- pass False to disable it.
- "version" is an optional version path element to append to the
- path. You might want to use this if you want multiple versions
- of your app to be able to run independently. If used, this
- would typically be ".".
- Only applied when appname is present.
- "roaming" (boolean, default False) can be set True to use the Windows
- roaming appdata directory. That means that for users on a Windows
- network setup for roaming profiles, this user data will be
- sync'd on login. See
-
- for a discussion of issues.
-
- Typical user state directories are:
- Mac OS X: same as user_data_dir
- Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined
- Win *: same as user_data_dir
-
- For Unix, we follow this Debian proposal
- to extend the XDG spec and support $XDG_STATE_HOME.
-
- That means, by default "~/.local/state/".
- """
- if system in ["win32", "darwin"]:
- path = user_data_dir(appname, appauthor, None, roaming)
- else:
- path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state"))
- if appname:
- path = os.path.join(path, appname)
- if appname and version:
- path = os.path.join(path, version)
- return path
-
-
-def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
- r"""Return full path to the user-specific log dir for this application.
-
- "appname" is the name of application.
- If None, just the system directory is returned.
- "appauthor" (only used on Windows) is the name of the
- appauthor or distributing body for this application. Typically
- it is the owning company name. This falls back to appname. You may
- pass False to disable it.
- "version" is an optional version path element to append to the
- path. You might want to use this if you want multiple versions
- of your app to be able to run independently. If used, this
- would typically be ".".
- Only applied when appname is present.
- "opinion" (boolean) can be False to disable the appending of
- "Logs" to the base app data dir for Windows, and "log" to the
- base cache dir for Unix. See discussion below.
-
- Typical user log directories are:
- Mac OS X: ~/Library/Logs/
- Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined
- Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs
- Vista: C:\Users\\AppData\Local\\\Logs
-
- On Windows the only suggestion in the MSDN docs is that local settings
- go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
- examples of what some windows apps use for a logs dir.)
-
- OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
- value for Windows and appends "log" to the user cache dir for Unix.
- This can be disabled with the `opinion=False` option.
- """
- if system == "darwin":
- path = os.path.join(
- os.path.expanduser('~/Library/Logs'),
- appname)
- elif system == "win32":
- path = user_data_dir(appname, appauthor, version)
- version = False
- if opinion:
- path = os.path.join(path, "Logs")
- else:
- path = user_cache_dir(appname, appauthor, version)
- version = False
- if opinion:
- path = os.path.join(path, "log")
- if appname and version:
- path = os.path.join(path, version)
- return path
-
-
-class AppDirs(object):
- """Convenience wrapper for getting application dirs."""
- def __init__(self, appname=None, appauthor=None, version=None,
- roaming=False, multipath=False):
- self.appname = appname
- self.appauthor = appauthor
- self.version = version
- self.roaming = roaming
- self.multipath = multipath
-
- @property
- def user_data_dir(self):
- return user_data_dir(self.appname, self.appauthor,
- version=self.version, roaming=self.roaming)
-
- @property
- def site_data_dir(self):
- return site_data_dir(self.appname, self.appauthor,
- version=self.version, multipath=self.multipath)
-
- @property
- def user_config_dir(self):
- return user_config_dir(self.appname, self.appauthor,
- version=self.version, roaming=self.roaming)
-
- @property
- def site_config_dir(self):
- return site_config_dir(self.appname, self.appauthor,
- version=self.version, multipath=self.multipath)
-
- @property
- def user_cache_dir(self):
- return user_cache_dir(self.appname, self.appauthor,
- version=self.version)
-
- @property
- def user_state_dir(self):
- return user_state_dir(self.appname, self.appauthor,
- version=self.version)
-
- @property
- def user_log_dir(self):
- return user_log_dir(self.appname, self.appauthor,
- version=self.version)
-
-
-#---- internal support stuff
-
-def _get_win_folder_from_registry(csidl_name):
- """This is a fallback technique at best. I'm not sure if using the
- registry for this guarantees us the correct answer for all CSIDL_*
- names.
- """
- if PY3:
- import winreg as _winreg
- else:
- import _winreg
-
- shell_folder_name = {
- "CSIDL_APPDATA": "AppData",
- "CSIDL_COMMON_APPDATA": "Common AppData",
- "CSIDL_LOCAL_APPDATA": "Local AppData",
- }[csidl_name]
-
- key = _winreg.OpenKey(
- _winreg.HKEY_CURRENT_USER,
- r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
- )
- dir, type = _winreg.QueryValueEx(key, shell_folder_name)
- return dir
-
-
-def _get_win_folder_with_pywin32(csidl_name):
- from win32com.shell import shellcon, shell
- dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
- # Try to make this a unicode path because SHGetFolderPath does
- # not return unicode strings when there is unicode data in the
- # path.
- try:
- dir = unicode(dir)
-
- # Downgrade to short path name if have highbit chars. See
- # .
- has_high_char = False
- for c in dir:
- if ord(c) > 255:
- has_high_char = True
- break
- if has_high_char:
- try:
- import win32api
- dir = win32api.GetShortPathName(dir)
- except ImportError:
- pass
- except UnicodeError:
- pass
- return dir
-
-
-def _get_win_folder_with_ctypes(csidl_name):
- import ctypes
-
- csidl_const = {
- "CSIDL_APPDATA": 26,
- "CSIDL_COMMON_APPDATA": 35,
- "CSIDL_LOCAL_APPDATA": 28,
- }[csidl_name]
-
- buf = ctypes.create_unicode_buffer(1024)
- ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
-
- # Downgrade to short path name if have highbit chars. See
- # .
- has_high_char = False
- for c in buf:
- if ord(c) > 255:
- has_high_char = True
- break
- if has_high_char:
- buf2 = ctypes.create_unicode_buffer(1024)
- if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
- buf = buf2
-
- return buf.value
-
-def _get_win_folder_with_jna(csidl_name):
- import array
- from com.sun import jna
- from com.sun.jna.platform import win32
-
- buf_size = win32.WinDef.MAX_PATH * 2
- buf = array.zeros('c', buf_size)
- shell = win32.Shell32.INSTANCE
- shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf)
- dir = jna.Native.toString(buf.tostring()).rstrip("\0")
-
- # Downgrade to short path name if have highbit chars. See
- # .
- has_high_char = False
- for c in dir:
- if ord(c) > 255:
- has_high_char = True
- break
- if has_high_char:
- buf = array.zeros('c', buf_size)
- kernel = win32.Kernel32.INSTANCE
- if kernel.GetShortPathName(dir, buf, buf_size):
- dir = jna.Native.toString(buf.tostring()).rstrip("\0")
-
- return dir
-
-if system == "win32":
- try:
- from ctypes import windll
- _get_win_folder = _get_win_folder_with_ctypes
- except ImportError:
- try:
- import com.sun.jna
- _get_win_folder = _get_win_folder_with_jna
- except ImportError:
- _get_win_folder = _get_win_folder_from_registry
-
-
-def _win_path_to_bytes(path):
- """Encode Windows paths to bytes. Only used on Python 2.
-
- Motivation is to be consistent with other operating systems where paths
- are also returned as bytes. This avoids problems mixing bytes and Unicode
- elsewhere in the codebase. For more details and discussion see
- .
-
- If encoding using ASCII and MBCS fails, return the original Unicode path.
- """
- for encoding in ('ASCII', 'MBCS'):
- try:
- return path.encode(encoding)
- except (UnicodeEncodeError, LookupError):
- pass
- return path
-
-
-#---- self test code
-
-if __name__ == "__main__":
- appname = "MyApp"
- appauthor = "MyCompany"
-
- props = ("user_data_dir",
- "user_config_dir",
- "user_cache_dir",
- "user_state_dir",
- "user_log_dir",
- "site_data_dir",
- "site_config_dir")
-
- print("-- app dirs %s --" % __version__)
-
- print("-- app dirs (with optional 'version')")
- dirs = AppDirs(appname, appauthor, version="1.0")
- for prop in props:
- print("%s: %s" % (prop, getattr(dirs, prop)))
-
- print("\n-- app dirs (without optional 'version')")
- dirs = AppDirs(appname, appauthor)
- for prop in props:
- print("%s: %s" % (prop, getattr(dirs, prop)))
-
- print("\n-- app dirs (without optional 'appauthor')")
- dirs = AppDirs(appname)
- for prop in props:
- print("%s: %s" % (prop, getattr(dirs, prop)))
-
- print("\n-- app dirs (with disabled 'appauthor')")
- dirs = AppDirs(appname, appauthor=False)
- for prop in props:
- print("%s: %s" % (prop, getattr(dirs, prop)))
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/LICENSE.txt b/pipenv/patched/notpip/_vendor/cachecontrol/LICENSE.txt
deleted file mode 100644
index 1ed31ac368..0000000000
--- a/pipenv/patched/notpip/_vendor/cachecontrol/LICENSE.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-Copyright 2015 Eric Larson
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-implied.
-
-See the License for the specific language governing permissions and
-limitations under the License.
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/__init__.py b/pipenv/patched/notpip/_vendor/cachecontrol/__init__.py
index a1bbbbe3bf..8435d628d2 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/__init__.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/__init__.py
@@ -1,11 +1,18 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
"""CacheControl import Interface.
Make it easy to import from cachecontrol without long namespaces.
"""
__author__ = "Eric Larson"
__email__ = "eric@ionrock.org"
-__version__ = "0.12.6"
+__version__ = "0.12.10"
from .wrapper import CacheControl
from .adapter import CacheControlAdapter
from .controller import CacheController
+
+import logging
+logging.getLogger(__name__).addHandler(logging.NullHandler())
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/_cmd.py b/pipenv/patched/notpip/_vendor/cachecontrol/_cmd.py
index fb90adf438..2b8b056ce0 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/_cmd.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/_cmd.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
import logging
from pipenv.patched.notpip._vendor import requests
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/adapter.py b/pipenv/patched/notpip/_vendor/cachecontrol/adapter.py
index 58efafffb9..0aab196b77 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/adapter.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/adapter.py
@@ -1,16 +1,20 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
import types
import functools
import zlib
from pipenv.patched.notpip._vendor.requests.adapters import HTTPAdapter
-from .controller import CacheController
+from .controller import CacheController, PERMANENT_REDIRECT_STATUSES
from .cache import DictCache
from .filewrapper import CallbackFileWrapper
class CacheControlAdapter(HTTPAdapter):
- invalidating_methods = {"PUT", "DELETE"}
+ invalidating_methods = {"PUT", "PATCH", "DELETE"}
def __init__(
self,
@@ -93,7 +97,7 @@ def build_response(
response = cached_response
# We always cache the 301 responses
- elif response.status == 301:
+ elif int(response.status) in PERMANENT_REDIRECT_STATUSES:
self.controller.cache_response(request, response)
else:
# Wrap the response file with a wrapper that will cache the
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/cache.py b/pipenv/patched/notpip/_vendor/cachecontrol/cache.py
index 94e07732d9..44e4309d20 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/cache.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/cache.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
"""
The cache object API for implementing caches. The default is a thread
safe in-memory dictionary.
@@ -10,7 +14,7 @@ class BaseCache(object):
def get(self, key):
raise NotImplementedError()
- def set(self, key, value):
+ def set(self, key, value, expires=None):
raise NotImplementedError()
def delete(self, key):
@@ -29,7 +33,7 @@ def __init__(self, init_dict=None):
def get(self, key):
return self.data.get(key, None)
- def set(self, key, value):
+ def set(self, key, value, expires=None):
with self.lock:
self.data.update({key: value})
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/caches/__init__.py b/pipenv/patched/notpip/_vendor/cachecontrol/caches/__init__.py
index 0e1658fa5e..44becd6843 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/caches/__init__.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/caches/__init__.py
@@ -1,2 +1,6 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
from .file_cache import FileCache # noqa
from .redis_cache import RedisCache # noqa
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/caches/file_cache.py b/pipenv/patched/notpip/_vendor/cachecontrol/caches/file_cache.py
index 607b945242..6cd1106f88 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/caches/file_cache.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/caches/file_cache.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
import hashlib
import os
from textwrap import dedent
@@ -114,7 +118,7 @@ def get(self, key):
except FileNotFoundError:
return None
- def set(self, key, value):
+ def set(self, key, value, expires=None):
name = self._fn(key)
# Make sure the directory exists
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/caches/redis_cache.py b/pipenv/patched/notpip/_vendor/cachecontrol/caches/redis_cache.py
index 0cc5e97998..da97071b8e 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/caches/redis_cache.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/caches/redis_cache.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
from __future__ import division
from datetime import datetime
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/compat.py b/pipenv/patched/notpip/_vendor/cachecontrol/compat.py
index 67092cb62a..0a0a218f7a 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/compat.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/compat.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
try:
from urllib.parse import urljoin
except ImportError:
@@ -9,7 +13,6 @@
except ImportError:
import pickle
-
# Handle the case where the requests module has been patched to not have
# urllib3 bundled as part of its source.
try:
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/controller.py b/pipenv/patched/notpip/_vendor/cachecontrol/controller.py
index 80bd030f0a..99b754a5b7 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/controller.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/controller.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
"""
The httplib2 algorithms ported for use with requests.
"""
@@ -17,6 +21,8 @@
URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?")
+PERMANENT_REDIRECT_STATUSES = (301, 308)
+
def parse_uri(uri):
"""Parses a URI using the regex given in Appendix B of RFC 3986.
@@ -37,7 +43,7 @@ def __init__(
self.cache = DictCache() if cache is None else cache
self.cache_etags = cache_etags
self.serializer = serializer or Serializer()
- self.cacheable_status_codes = status_codes or (200, 203, 300, 301)
+ self.cacheable_status_codes = status_codes or (200, 203, 300, 301, 308)
@classmethod
def _urlnorm(cls, uri):
@@ -147,17 +153,18 @@ def cached_request(self, request):
logger.warning("Cache entry deserialization failed, entry ignored")
return False
- # If we have a cached 301, return it immediately. We don't
- # need to test our response for other headers b/c it is
+ # If we have a cached permanent redirect, return it immediately. We
+ # don't need to test our response for other headers b/c it is
# intrinsically "cacheable" as it is Permanent.
+ #
# See:
# https://tools.ietf.org/html/rfc7231#section-6.4.2
#
# Client can try to refresh the value by repeating the request
# with cache busting headers as usual (ie no-cache).
- if resp.status == 301:
+ if int(resp.status) in PERMANENT_REDIRECT_STATUSES:
msg = (
- 'Returning cached "301 Moved Permanently" response '
+ "Returning cached permanent redirect response "
"(ignoring date and etag information)"
)
logger.debug(msg)
@@ -261,6 +268,11 @@ def cache_response(self, request, response, body=None, status_codes=None):
response_headers = CaseInsensitiveDict(response.headers)
+ if "date" in response_headers:
+ date = calendar.timegm(parsedate_tz(response_headers["date"]))
+ else:
+ date = 0
+
# If we've been given a body, our response has a Content-Length, that
# Content-Length is valid then we can check to see if the body we've
# been given matches the expected size, and if it doesn't we'll just
@@ -304,35 +316,62 @@ def cache_response(self, request, response, body=None, status_codes=None):
# If we've been given an etag, then keep the response
if self.cache_etags and "etag" in response_headers:
+ expires_time = 0
+ if response_headers.get("expires"):
+ expires = parsedate_tz(response_headers["expires"])
+ if expires is not None:
+ expires_time = calendar.timegm(expires) - date
+
+ expires_time = max(expires_time, 14 * 86400)
+
+ logger.debug("etag object cached for {0} seconds".format(expires_time))
logger.debug("Caching due to etag")
self.cache.set(
- cache_url, self.serializer.dumps(request, response, body=body)
+ cache_url,
+ self.serializer.dumps(request, response, body),
+ expires=expires_time,
)
- # Add to the cache any 301s. We do this before looking that
- # the Date headers.
- elif response.status == 301:
- logger.debug("Caching permanant redirect")
- self.cache.set(cache_url, self.serializer.dumps(request, response))
+ # Add to the cache any permanent redirects. We do this before looking
+ # that the Date headers.
+ elif int(response.status) in PERMANENT_REDIRECT_STATUSES:
+ logger.debug("Caching permanent redirect")
+ self.cache.set(cache_url, self.serializer.dumps(request, response, b""))
# Add to the cache if the response headers demand it. If there
# is no date header then we can't do anything about expiring
# the cache.
elif "date" in response_headers:
+ date = calendar.timegm(parsedate_tz(response_headers["date"]))
# cache when there is a max-age > 0
if "max-age" in cc and cc["max-age"] > 0:
logger.debug("Caching b/c date exists and max-age > 0")
+ expires_time = cc["max-age"]
self.cache.set(
- cache_url, self.serializer.dumps(request, response, body=body)
+ cache_url,
+ self.serializer.dumps(request, response, body),
+ expires=expires_time,
)
# If the request can expire, it means we should cache it
# in the meantime.
elif "expires" in response_headers:
if response_headers["expires"]:
- logger.debug("Caching b/c of expires header")
+ expires = parsedate_tz(response_headers["expires"])
+ if expires is not None:
+ expires_time = calendar.timegm(expires) - date
+ else:
+ expires_time = None
+
+ logger.debug(
+ "Caching b/c of expires header. expires in {0} seconds".format(
+ expires_time
+ )
+ )
self.cache.set(
- cache_url, self.serializer.dumps(request, response, body=body)
+ cache_url,
+ self.serializer.dumps(request, response, body=body),
+ expires=expires_time,
)
def update_cached_response(self, request, response):
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/filewrapper.py b/pipenv/patched/notpip/_vendor/cachecontrol/filewrapper.py
index 30ed4c5a62..f5ed5f6f6e 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/filewrapper.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/filewrapper.py
@@ -1,4 +1,9 @@
-from io import BytesIO
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
+from tempfile import NamedTemporaryFile
+import mmap
class CallbackFileWrapper(object):
@@ -11,10 +16,17 @@ class CallbackFileWrapper(object):
This class uses members with a double underscore (__) leading prefix so as
not to accidentally shadow an attribute.
+
+ The data is stored in a temporary file until it is all available. As long
+ as the temporary files directory is disk-based (sometimes it's a
+ memory-backed-``tmpfs`` on Linux), data will be unloaded to disk if memory
+ pressure is high. For small files the disk usually won't be used at all,
+ it'll all be in the filesystem memory cache, so there should be no
+ performance impact.
"""
def __init__(self, fp, callback):
- self.__buf = BytesIO()
+ self.__buf = NamedTemporaryFile("rb+", delete=True)
self.__fp = fp
self.__callback = callback
@@ -49,7 +61,19 @@ def __is_fp_closed(self):
def _close(self):
if self.__callback:
- self.__callback(self.__buf.getvalue())
+ if self.__buf.tell() == 0:
+ # Empty file:
+ result = b""
+ else:
+ # Return the data without actually loading it into memory,
+ # relying on Python's buffer API and mmap(). mmap() just gives
+ # a view directly into the filesystem's memory cache, so it
+ # doesn't result in duplicate memory use.
+ self.__buf.seek(0, 0)
+ result = memoryview(
+ mmap.mmap(self.__buf.fileno(), 0, access=mmap.ACCESS_READ)
+ )
+ self.__callback(result)
# We assign this to None here, because otherwise we can get into
# really tricky problems where the CPython interpreter dead locks
@@ -58,9 +82,16 @@ def _close(self):
# and allows the garbage collector to do it's thing normally.
self.__callback = None
+ # Closing the temporary file releases memory and frees disk space.
+ # Important when caching big files.
+ self.__buf.close()
+
def read(self, amt=None):
data = self.__fp.read(amt)
- self.__buf.write(data)
+ if data:
+ # We may be dealing with b'', a sign that things are over:
+ # it's passed e.g. after we've already closed self.__buf.
+ self.__buf.write(data)
if self.__is_fp_closed():
self._close()
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/heuristics.py b/pipenv/patched/notpip/_vendor/cachecontrol/heuristics.py
index 6c0e9790d5..ebe4a96f58 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/heuristics.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/heuristics.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
import calendar
import time
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/serialize.py b/pipenv/patched/notpip/_vendor/cachecontrol/serialize.py
index c5da06ad7c..8a67c15a12 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/serialize.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/serialize.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
import base64
import io
import json
@@ -17,24 +21,18 @@ def _b64_decode_str(s):
return _b64_decode_bytes(s).decode("utf8")
-class Serializer(object):
+_default_body_read = object()
+
+class Serializer(object):
def dumps(self, request, response, body=None):
response_headers = CaseInsensitiveDict(response.headers)
if body is None:
+ # When a body isn't passed in, we'll read the response. We
+ # also update the response with a new file handler to be
+ # sure it acts as though it was never read.
body = response.read(decode_content=False)
-
- # NOTE: 99% sure this is dead code. I'm only leaving it
- # here b/c I don't have a test yet to prove
- # it. Basically, before using
- # `cachecontrol.filewrapper.CallbackFileWrapper`,
- # this made an effort to reset the file handle. The
- # `CallbackFileWrapper` short circuits this code by
- # setting the body as the content is consumed, the
- # result being a `body` argument is *always* passed
- # into cache_response, and in turn,
- # `Serializer.dump`.
response._fp = io.BytesIO(body)
# NOTE: This is all a bit weird, but it's really important that on
diff --git a/pipenv/patched/notpip/_vendor/cachecontrol/wrapper.py b/pipenv/patched/notpip/_vendor/cachecontrol/wrapper.py
index d8e6fc6a9e..b6ee7f2039 100644
--- a/pipenv/patched/notpip/_vendor/cachecontrol/wrapper.py
+++ b/pipenv/patched/notpip/_vendor/cachecontrol/wrapper.py
@@ -1,3 +1,7 @@
+# SPDX-FileCopyrightText: 2015 Eric Larson
+#
+# SPDX-License-Identifier: Apache-2.0
+
from .adapter import CacheControlAdapter
from .cache import DictCache
diff --git a/pipenv/patched/notpip/_vendor/certifi/LICENSE b/pipenv/patched/notpip/_vendor/certifi/LICENSE
deleted file mode 100644
index c2fda9a264..0000000000
--- a/pipenv/patched/notpip/_vendor/certifi/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-This package contains a modified version of ca-bundle.crt:
-
-ca-bundle.crt -- Bundle of CA Root Certificates
-
-Certificate data from Mozilla as of: Thu Nov 3 19:04:19 2011#
-This is a bundle of X.509 certificates of public Certificate Authorities
-(CA). These were automatically extracted from Mozilla's root certificates
-file (certdata.txt). This file can be found in the mozilla source tree:
-http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1#
-It contains the certificates in PEM format and therefore
-can be directly used with curl / libcurl / php_curl, or with
-an Apache+mod_ssl webserver for SSL client authentication.
-Just configure this file as the SSLCACertificateFile.#
-
-***** BEGIN LICENSE BLOCK *****
-This Source Code Form is subject to the terms of the Mozilla Public License,
-v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain
-one at http://mozilla.org/MPL/2.0/.
-
-***** END LICENSE BLOCK *****
-@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $
diff --git a/pipenv/patched/notpip/_vendor/certifi/__init__.py b/pipenv/patched/notpip/_vendor/certifi/__init__.py
index eebdf88867..8db1a0e554 100644
--- a/pipenv/patched/notpip/_vendor/certifi/__init__.py
+++ b/pipenv/patched/notpip/_vendor/certifi/__init__.py
@@ -1,3 +1,3 @@
from .core import contents, where
-__version__ = "2021.05.30"
+__version__ = "2021.10.08"
diff --git a/pipenv/patched/notpip/_vendor/certifi/cacert.pem b/pipenv/patched/notpip/_vendor/certifi/cacert.pem
index 96e2fc65a8..6d0ccc0d1c 100644
--- a/pipenv/patched/notpip/_vendor/certifi/cacert.pem
+++ b/pipenv/patched/notpip/_vendor/certifi/cacert.pem
@@ -4255,3 +4255,108 @@ qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP
0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf
E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
-----END CERTIFICATE-----
+
+# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique
+# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique
+# Label: "TunTrust Root CA"
+# Serial: 108534058042236574382096126452369648152337120275
+# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4
+# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb
+# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41
+-----BEGIN CERTIFICATE-----
+MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL
+BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg
+Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv
+b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG
+EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u
+IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ
+n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd
+2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF
+VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ
+GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF
+li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU
+r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2
+eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb
+MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg
+jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB
+7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW
+5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE
+ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
+90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z
+xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu
+QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4
+FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH
+22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP
+xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn
+dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5
+Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b
+nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ
+CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH
+u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj
+d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
+-----END CERTIFICATE-----
+
+# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Label: "HARICA TLS RSA Root CA 2021"
+# Serial: 76817823531813593706434026085292783742
+# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91
+# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d
+# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs
+MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
+c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg
+Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL
+MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl
+YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv
+b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l
+mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE
+4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv
+a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M
+pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw
+Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b
+LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY
+AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB
+AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq
+E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr
+W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ
+CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU
+X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3
+f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja
+H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP
+JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P
+zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt
+jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0
+/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT
+BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79
+aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW
+xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU
+63ZTGI0RmLo=
+-----END CERTIFICATE-----
+
+# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Label: "HARICA TLS ECC Root CA 2021"
+# Serial: 137515985548005187474074462014555733966
+# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0
+# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48
+# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01
+-----BEGIN CERTIFICATE-----
+MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw
+CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh
+cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v
+dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG
+A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj
+aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg
+Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7
+KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y
+STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD
+AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw
+SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN
+nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
+-----END CERTIFICATE-----
diff --git a/pipenv/patched/notpip/_vendor/chardet/LICENSE b/pipenv/patched/notpip/_vendor/chardet/LICENSE
deleted file mode 100644
index 8add30ad59..0000000000
--- a/pipenv/patched/notpip/_vendor/chardet/LICENSE
+++ /dev/null
@@ -1,504 +0,0 @@
- GNU LESSER GENERAL PUBLIC LICENSE
- Version 2.1, February 1999
-
- Copyright (C) 1991, 1999 Free Software Foundation, Inc.
- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-[This is the first released version of the Lesser GPL. It also counts
- as the successor of the GNU Library Public License, version 2, hence
- the version number 2.1.]
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
- This license, the Lesser General Public License, applies to some
-specially designated software packages--typically libraries--of the
-Free Software Foundation and other authors who decide to use it. You
-can use it too, but we suggest you first think carefully about whether
-this license or the ordinary General Public License is the better
-strategy to use in any particular case, based on the explanations below.
-
- When we speak of free software, we are referring to freedom of use,
-not price. Our General Public Licenses are designed to make sure that
-you have the freedom to distribute copies of free software (and charge
-for this service if you wish); that you receive source code or can get
-it if you want it; that you can change the software and use pieces of
-it in new free programs; and that you are informed that you can do
-these things.
-
- To protect your rights, we need to make restrictions that forbid
-distributors to deny you these rights or to ask you to surrender these
-rights. These restrictions translate to certain responsibilities for
-you if you distribute copies of the library or if you modify it.
-
- For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you. You must make sure that they, too, receive or can get the source
-code. If you link other code with the library, you must provide
-complete object files to the recipients, so that they can relink them
-with the library after making changes to the library and recompiling
-it. And you must show them these terms so they know their rights.
-
- We protect your rights with a two-step method: (1) we copyright the
-library, and (2) we offer you this license, which gives you legal
-permission to copy, distribute and/or modify the library.
-
- To protect each distributor, we want to make it very clear that
-there is no warranty for the free library. Also, if the library is
-modified by someone else and passed on, the recipients should know
-that what they have is not the original version, so that the original
-author's reputation will not be affected by problems that might be
-introduced by others.
-
- Finally, software patents pose a constant threat to the existence of
-any free program. We wish to make sure that a company cannot
-effectively restrict the users of a free program by obtaining a
-restrictive license from a patent holder. Therefore, we insist that
-any patent license obtained for a version of the library must be
-consistent with the full freedom of use specified in this license.
-
- Most GNU software, including some libraries, is covered by the
-ordinary GNU General Public License. This license, the GNU Lesser
-General Public License, applies to certain designated libraries, and
-is quite different from the ordinary General Public License. We use
-this license for certain libraries in order to permit linking those
-libraries into non-free programs.
-
- When a program is linked with a library, whether statically or using
-a shared library, the combination of the two is legally speaking a
-combined work, a derivative of the original library. The ordinary
-General Public License therefore permits such linking only if the
-entire combination fits its criteria of freedom. The Lesser General
-Public License permits more lax criteria for linking other code with
-the library.
-
- We call this license the "Lesser" General Public License because it
-does Less to protect the user's freedom than the ordinary General
-Public License. It also provides other free software developers Less
-of an advantage over competing non-free programs. These disadvantages
-are the reason we use the ordinary General Public License for many
-libraries. However, the Lesser license provides advantages in certain
-special circumstances.
-
- For example, on rare occasions, there may be a special need to
-encourage the widest possible use of a certain library, so that it becomes
-a de-facto standard. To achieve this, non-free programs must be
-allowed to use the library. A more frequent case is that a free
-library does the same job as widely used non-free libraries. In this
-case, there is little to gain by limiting the free library to free
-software only, so we use the Lesser General Public License.
-
- In other cases, permission to use a particular library in non-free
-programs enables a greater number of people to use a large body of
-free software. For example, permission to use the GNU C Library in
-non-free programs enables many more people to use the whole GNU
-operating system, as well as its variant, the GNU/Linux operating
-system.
-
- Although the Lesser General Public License is Less protective of the
-users' freedom, it does ensure that the user of a program that is
-linked with the Library has the freedom and the wherewithal to run
-that program using a modified version of the Library.
-
- The precise terms and conditions for copying, distribution and
-modification follow. Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library". The
-former contains code derived from the library, whereas the latter must
-be combined with the library in order to run.
-
- GNU LESSER GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License Agreement applies to any software library or other
-program which contains a notice placed by the copyright holder or
-other authorized party saying it may be distributed under the terms of
-this Lesser General Public License (also called "this License").
-Each licensee is addressed as "you".
-
- A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
- The "Library", below, refers to any such software library or work
-which has been distributed under these terms. A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language. (Hereinafter, translation is
-included without limitation in the term "modification".)
-
- "Source code" for a work means the preferred form of the work for
-making modifications to it. For a library, complete source code means
-all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control compilation
-and installation of the library.
-
- Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it). Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-
- 1. You may copy and distribute verbatim copies of the Library's
-complete source code as you receive it, in any medium, provided that
-you conspicuously and appropriately publish on each copy an
-appropriate copyright notice and disclaimer of warranty; keep intact
-all the notices that refer to this License and to the absence of any
-warranty; and distribute a copy of this License along with the
-Library.
-
- You may charge a fee for the physical act of transferring a copy,
-and you may at your option offer warranty protection in exchange for a
-fee.
-
- 2. You may modify your copy or copies of the Library or any portion
-of it, thus forming a work based on the Library, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) The modified work must itself be a software library.
-
- b) You must cause the files modified to carry prominent notices
- stating that you changed the files and the date of any change.
-
- c) You must cause the whole of the work to be licensed at no
- charge to all third parties under the terms of this License.
-
- d) If a facility in the modified Library refers to a function or a
- table of data to be supplied by an application program that uses
- the facility, other than as an argument passed when the facility
- is invoked, then you must make a good faith effort to ensure that,
- in the event an application does not supply such function or
- table, the facility still operates, and performs whatever part of
- its purpose remains meaningful.
-
- (For example, a function in a library to compute square roots has
- a purpose that is entirely well-defined independent of the
- application. Therefore, Subsection 2d requires that any
- application-supplied function or table used by this function must
- be optional: if the application does not supply it, the square
- root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Library,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Library, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote
-it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library. To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License. (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.) Do not make any other change in
-these notices.
-
- Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
- This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
- 4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you accompany
-it with the complete corresponding machine-readable source code, which
-must be distributed under the terms of Sections 1 and 2 above on a
-medium customarily used for software interchange.
-
- If distribution of object code is made by offering access to copy
-from a designated place, then offering equivalent access to copy the
-source code from the same place satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library". Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
- However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library". The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
- When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library. The
-threshold for this to be true is not precisely defined by law.
-
- If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work. (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
- Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-
- 6. As an exception to the Sections above, you may also combine or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
- You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License. You must supply a copy of this License. If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License. Also, you must do one
-of these things:
-
- a) Accompany the work with the complete corresponding
- machine-readable source code for the Library including whatever
- changes were used in the work (which must be distributed under
- Sections 1 and 2 above); and, if the work is an executable linked
- with the Library, with the complete machine-readable "work that
- uses the Library", as object code and/or source code, so that the
- user can modify the Library and then relink to produce a modified
- executable containing the modified Library. (It is understood
- that the user who changes the contents of definitions files in the
- Library will not necessarily be able to recompile the application
- to use the modified definitions.)
-
- b) Use a suitable shared library mechanism for linking with the
- Library. A suitable mechanism is one that (1) uses at run time a
- copy of the library already present on the user's computer system,
- rather than copying library functions into the executable, and (2)
- will operate properly with a modified version of the library, if
- the user installs one, as long as the modified version is
- interface-compatible with the version that the work was made with.
-
- c) Accompany the work with a written offer, valid for at
- least three years, to give the same user the materials
- specified in Subsection 6a, above, for a charge no more
- than the cost of performing this distribution.
-
- d) If distribution of the work is made by offering access to copy
- from a designated place, offer equivalent access to copy the above
- specified materials from the same place.
-
- e) Verify that the user has already received a copy of these
- materials or that you have already sent this user a copy.
-
- For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it. However, as a special exception,
-the materials to be distributed need not include anything that is
-normally distributed (in either source or binary form) with the major
-components (compiler, kernel, and so on) of the operating system on
-which the executable runs, unless that component itself accompanies
-the executable.
-
- It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system. Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-
- 7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
- a) Accompany the combined library with a copy of the same work
- based on the Library, uncombined with any other library
- facilities. This must be distributed under the terms of the
- Sections above.
-
- b) Give prominent notice with the combined library of the fact
- that part of it is a work based on the Library, and explaining
- where to find the accompanying uncombined form of the same work.
-
- 8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License. Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library is void, and will automatically terminate your
-rights under this License. However, parties who have received copies,
-or rights, from you under this License will not have their licenses
-terminated so long as such parties remain in full compliance.
-
- 9. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Library or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
- 10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-subject to these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties with
-this License.
-
- 11. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Library at all. For example, if a patent
-license would not permit royalty-free redistribution of the Library by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Library.
-
-If any portion of this section is held invalid or unenforceable under any
-particular circumstance, the balance of the section is intended to apply,
-and the section as a whole is intended to apply in other circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library under this License may add
-an explicit geographical distribution limitation excluding those countries,
-so that distribution is permitted only in or among countries not thus
-excluded. In such case, this License incorporates the limitation as if
-written in the body of this License.
-
- 13. The Free Software Foundation may publish revised and/or new
-versions of the Lesser General Public License from time to time.
-Such new versions will be similar in spirit to the present version,
-but may differ in detail to address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Library
-specifies a version number of this License which applies to it and
-"any later version", you have the option of following the terms and
-conditions either of that version or of any later version published by
-the Free Software Foundation. If the Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-
- 14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-write to the author to ask for permission. For software which is
-copyrighted by the Free Software Foundation, write to the Free
-Software Foundation; we sometimes make exceptions for this. Our
-decision will be guided by the two goals of preserving the free status
-of all derivatives of our free software and of promoting the sharing
-and reuse of software generally.
-
- NO WARRANTY
-
- 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
-KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
-LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
-WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
-AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
-FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
-CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
-LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
-RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
-FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Libraries
-
- If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change. You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms of the
-ordinary General Public License).
-
- To apply these terms, attach the following notices to the library. It is
-safest to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least the
-"copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the
- library `Frob' (a library for tweaking knobs) written by James Random Hacker.
-
- , 1 April 1990
- Ty Coon, President of Vice
-
-That's all there is to it!
-
-
diff --git a/pipenv/patched/notpip/_vendor/colorama/LICENSE.txt b/pipenv/patched/notpip/_vendor/colorama/LICENSE.txt
deleted file mode 100644
index 3105888ec1..0000000000
--- a/pipenv/patched/notpip/_vendor/colorama/LICENSE.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright (c) 2010 Jonathan Hartley
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-* Neither the name of the copyright holders, nor those of its contributors
- may be used to endorse or promote products derived from this software without
- specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pipenv/patched/notpip/_vendor/distlib/__init__.py b/pipenv/patched/notpip/_vendor/distlib/__init__.py
index 492c2c7058..6878387a03 100644
--- a/pipenv/patched/notpip/_vendor/distlib/__init__.py
+++ b/pipenv/patched/notpip/_vendor/distlib/__init__.py
@@ -6,7 +6,7 @@
#
import logging
-__version__ = '0.3.2'
+__version__ = '0.3.4'
class DistlibException(Exception):
pass
diff --git a/pipenv/patched/notpip/_vendor/distlib/_backport/__init__.py b/pipenv/patched/notpip/_vendor/distlib/_backport/__init__.py
deleted file mode 100644
index f7dbf4c9aa..0000000000
--- a/pipenv/patched/notpip/_vendor/distlib/_backport/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Modules copied from Python 3 standard libraries, for internal use only.
-
-Individual classes and functions are found in d2._backport.misc. Intended
-usage is to always import things missing from 3.1 from that module: the
-built-in/stdlib objects will be used if found.
-"""
diff --git a/pipenv/patched/notpip/_vendor/distlib/_backport/misc.py b/pipenv/patched/notpip/_vendor/distlib/_backport/misc.py
deleted file mode 100644
index cfb318d34f..0000000000
--- a/pipenv/patched/notpip/_vendor/distlib/_backport/misc.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012 The Python Software Foundation.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""Backports for individual classes and functions."""
-
-import os
-import sys
-
-__all__ = ['cache_from_source', 'callable', 'fsencode']
-
-
-try:
- from imp import cache_from_source
-except ImportError:
- def cache_from_source(py_file, debug=__debug__):
- ext = debug and 'c' or 'o'
- return py_file + ext
-
-
-try:
- callable = callable
-except NameError:
- from collections import Callable
-
- def callable(obj):
- return isinstance(obj, Callable)
-
-
-try:
- fsencode = os.fsencode
-except AttributeError:
- def fsencode(filename):
- if isinstance(filename, bytes):
- return filename
- elif isinstance(filename, str):
- return filename.encode(sys.getfilesystemencoding())
- else:
- raise TypeError("expect bytes or str, not %s" %
- type(filename).__name__)
diff --git a/pipenv/patched/notpip/_vendor/distlib/_backport/shutil.py b/pipenv/patched/notpip/_vendor/distlib/_backport/shutil.py
deleted file mode 100644
index 10ed362539..0000000000
--- a/pipenv/patched/notpip/_vendor/distlib/_backport/shutil.py
+++ /dev/null
@@ -1,764 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012 The Python Software Foundation.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""Utility functions for copying and archiving files and directory trees.
-
-XXX The functions here don't copy the resource fork or other metadata on Mac.
-
-"""
-
-import os
-import sys
-import stat
-from os.path import abspath
-import fnmatch
-try:
- from collections.abc import Callable
-except ImportError:
- from collections import Callable
-import errno
-from . import tarfile
-
-try:
- import bz2
- _BZ2_SUPPORTED = True
-except ImportError:
- _BZ2_SUPPORTED = False
-
-try:
- from pwd import getpwnam
-except ImportError:
- getpwnam = None
-
-try:
- from grp import getgrnam
-except ImportError:
- getgrnam = None
-
-__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
- "copytree", "move", "rmtree", "Error", "SpecialFileError",
- "ExecError", "make_archive", "get_archive_formats",
- "register_archive_format", "unregister_archive_format",
- "get_unpack_formats", "register_unpack_format",
- "unregister_unpack_format", "unpack_archive", "ignore_patterns"]
-
-class Error(EnvironmentError):
- pass
-
-class SpecialFileError(EnvironmentError):
- """Raised when trying to do a kind of operation (e.g. copying) which is
- not supported on a special file (e.g. a named pipe)"""
-
-class ExecError(EnvironmentError):
- """Raised when a command could not be executed"""
-
-class ReadError(EnvironmentError):
- """Raised when an archive cannot be read"""
-
-class RegistryError(Exception):
- """Raised when a registry operation with the archiving
- and unpacking registries fails"""
-
-
-try:
- WindowsError
-except NameError:
- WindowsError = None
-
-def copyfileobj(fsrc, fdst, length=16*1024):
- """copy data from file-like object fsrc to file-like object fdst"""
- while 1:
- buf = fsrc.read(length)
- if not buf:
- break
- fdst.write(buf)
-
-def _samefile(src, dst):
- # Macintosh, Unix.
- if hasattr(os.path, 'samefile'):
- try:
- return os.path.samefile(src, dst)
- except OSError:
- return False
-
- # All other platforms: check for same pathname.
- return (os.path.normcase(os.path.abspath(src)) ==
- os.path.normcase(os.path.abspath(dst)))
-
-def copyfile(src, dst):
- """Copy data from src to dst"""
- if _samefile(src, dst):
- raise Error("`%s` and `%s` are the same file" % (src, dst))
-
- for fn in [src, dst]:
- try:
- st = os.stat(fn)
- except OSError:
- # File most likely does not exist
- pass
- else:
- # XXX What about other special files? (sockets, devices...)
- if stat.S_ISFIFO(st.st_mode):
- raise SpecialFileError("`%s` is a named pipe" % fn)
-
- with open(src, 'rb') as fsrc:
- with open(dst, 'wb') as fdst:
- copyfileobj(fsrc, fdst)
-
-def copymode(src, dst):
- """Copy mode bits from src to dst"""
- if hasattr(os, 'chmod'):
- st = os.stat(src)
- mode = stat.S_IMODE(st.st_mode)
- os.chmod(dst, mode)
-
-def copystat(src, dst):
- """Copy all stat info (mode bits, atime, mtime, flags) from src to dst"""
- st = os.stat(src)
- mode = stat.S_IMODE(st.st_mode)
- if hasattr(os, 'utime'):
- os.utime(dst, (st.st_atime, st.st_mtime))
- if hasattr(os, 'chmod'):
- os.chmod(dst, mode)
- if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
- try:
- os.chflags(dst, st.st_flags)
- except OSError as why:
- if (not hasattr(errno, 'EOPNOTSUPP') or
- why.errno != errno.EOPNOTSUPP):
- raise
-
-def copy(src, dst):
- """Copy data and mode bits ("cp src dst").
-
- The destination may be a directory.
-
- """
- if os.path.isdir(dst):
- dst = os.path.join(dst, os.path.basename(src))
- copyfile(src, dst)
- copymode(src, dst)
-
-def copy2(src, dst):
- """Copy data and all stat info ("cp -p src dst").
-
- The destination may be a directory.
-
- """
- if os.path.isdir(dst):
- dst = os.path.join(dst, os.path.basename(src))
- copyfile(src, dst)
- copystat(src, dst)
-
-def ignore_patterns(*patterns):
- """Function that can be used as copytree() ignore parameter.
-
- Patterns is a sequence of glob-style patterns
- that are used to exclude files"""
- def _ignore_patterns(path, names):
- ignored_names = []
- for pattern in patterns:
- ignored_names.extend(fnmatch.filter(names, pattern))
- return set(ignored_names)
- return _ignore_patterns
-
-def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
- ignore_dangling_symlinks=False):
- """Recursively copy a directory tree.
-
- The destination directory must not already exist.
- If exception(s) occur, an Error is raised with a list of reasons.
-
- If the optional symlinks flag is true, symbolic links in the
- source tree result in symbolic links in the destination tree; if
- it is false, the contents of the files pointed to by symbolic
- links are copied. If the file pointed by the symlink doesn't
- exist, an exception will be added in the list of errors raised in
- an Error exception at the end of the copy process.
-
- You can set the optional ignore_dangling_symlinks flag to true if you
- want to silence this exception. Notice that this has no effect on
- platforms that don't support os.symlink.
-
- The optional ignore argument is a callable. If given, it
- is called with the `src` parameter, which is the directory
- being visited by copytree(), and `names` which is the list of
- `src` contents, as returned by os.listdir():
-
- callable(src, names) -> ignored_names
-
- Since copytree() is called recursively, the callable will be
- called once for each directory that is copied. It returns a
- list of names relative to the `src` directory that should
- not be copied.
-
- The optional copy_function argument is a callable that will be used
- to copy each file. It will be called with the source path and the
- destination path as arguments. By default, copy2() is used, but any
- function that supports the same signature (like copy()) can be used.
-
- """
- names = os.listdir(src)
- if ignore is not None:
- ignored_names = ignore(src, names)
- else:
- ignored_names = set()
-
- os.makedirs(dst)
- errors = []
- for name in names:
- if name in ignored_names:
- continue
- srcname = os.path.join(src, name)
- dstname = os.path.join(dst, name)
- try:
- if os.path.islink(srcname):
- linkto = os.readlink(srcname)
- if symlinks:
- os.symlink(linkto, dstname)
- else:
- # ignore dangling symlink if the flag is on
- if not os.path.exists(linkto) and ignore_dangling_symlinks:
- continue
- # otherwise let the copy occurs. copy2 will raise an error
- copy_function(srcname, dstname)
- elif os.path.isdir(srcname):
- copytree(srcname, dstname, symlinks, ignore, copy_function)
- else:
- # Will raise a SpecialFileError for unsupported file types
- copy_function(srcname, dstname)
- # catch the Error from the recursive copytree so that we can
- # continue with other files
- except Error as err:
- errors.extend(err.args[0])
- except EnvironmentError as why:
- errors.append((srcname, dstname, str(why)))
- try:
- copystat(src, dst)
- except OSError as why:
- if WindowsError is not None and isinstance(why, WindowsError):
- # Copying file access times may fail on Windows
- pass
- else:
- errors.extend((src, dst, str(why)))
- if errors:
- raise Error(errors)
-
-def rmtree(path, ignore_errors=False, onerror=None):
- """Recursively delete a directory tree.
-
- If ignore_errors is set, errors are ignored; otherwise, if onerror
- is set, it is called to handle the error with arguments (func,
- path, exc_info) where func is os.listdir, os.remove, or os.rmdir;
- path is the argument to that function that caused it to fail; and
- exc_info is a tuple returned by sys.exc_info(). If ignore_errors
- is false and onerror is None, an exception is raised.
-
- """
- if ignore_errors:
- def onerror(*args):
- pass
- elif onerror is None:
- def onerror(*args):
- raise
- try:
- if os.path.islink(path):
- # symlinks to directories are forbidden, see bug #1669
- raise OSError("Cannot call rmtree on a symbolic link")
- except OSError:
- onerror(os.path.islink, path, sys.exc_info())
- # can't continue even if onerror hook returns
- return
- names = []
- try:
- names = os.listdir(path)
- except os.error:
- onerror(os.listdir, path, sys.exc_info())
- for name in names:
- fullname = os.path.join(path, name)
- try:
- mode = os.lstat(fullname).st_mode
- except os.error:
- mode = 0
- if stat.S_ISDIR(mode):
- rmtree(fullname, ignore_errors, onerror)
- else:
- try:
- os.remove(fullname)
- except os.error:
- onerror(os.remove, fullname, sys.exc_info())
- try:
- os.rmdir(path)
- except os.error:
- onerror(os.rmdir, path, sys.exc_info())
-
-
-def _basename(path):
- # A basename() variant which first strips the trailing slash, if present.
- # Thus we always get the last component of the path, even for directories.
- return os.path.basename(path.rstrip(os.path.sep))
-
-def move(src, dst):
- """Recursively move a file or directory to another location. This is
- similar to the Unix "mv" command.
-
- If the destination is a directory or a symlink to a directory, the source
- is moved inside the directory. The destination path must not already
- exist.
-
- If the destination already exists but is not a directory, it may be
- overwritten depending on os.rename() semantics.
-
- If the destination is on our current filesystem, then rename() is used.
- Otherwise, src is copied to the destination and then removed.
- A lot more could be done here... A look at a mv.c shows a lot of
- the issues this implementation glosses over.
-
- """
- real_dst = dst
- if os.path.isdir(dst):
- if _samefile(src, dst):
- # We might be on a case insensitive filesystem,
- # perform the rename anyway.
- os.rename(src, dst)
- return
-
- real_dst = os.path.join(dst, _basename(src))
- if os.path.exists(real_dst):
- raise Error("Destination path '%s' already exists" % real_dst)
- try:
- os.rename(src, real_dst)
- except OSError:
- if os.path.isdir(src):
- if _destinsrc(src, dst):
- raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
- copytree(src, real_dst, symlinks=True)
- rmtree(src)
- else:
- copy2(src, real_dst)
- os.unlink(src)
-
-def _destinsrc(src, dst):
- src = abspath(src)
- dst = abspath(dst)
- if not src.endswith(os.path.sep):
- src += os.path.sep
- if not dst.endswith(os.path.sep):
- dst += os.path.sep
- return dst.startswith(src)
-
-def _get_gid(name):
- """Returns a gid, given a group name."""
- if getgrnam is None or name is None:
- return None
- try:
- result = getgrnam(name)
- except KeyError:
- result = None
- if result is not None:
- return result[2]
- return None
-
-def _get_uid(name):
- """Returns an uid, given a user name."""
- if getpwnam is None or name is None:
- return None
- try:
- result = getpwnam(name)
- except KeyError:
- result = None
- if result is not None:
- return result[2]
- return None
-
-def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
- owner=None, group=None, logger=None):
- """Create a (possibly compressed) tar file from all the files under
- 'base_dir'.
-
- 'compress' must be "gzip" (the default), "bzip2", or None.
-
- 'owner' and 'group' can be used to define an owner and a group for the
- archive that is being built. If not provided, the current owner and group
- will be used.
-
- The output tar file will be named 'base_name' + ".tar", possibly plus
- the appropriate compression extension (".gz", or ".bz2").
-
- Returns the output filename.
- """
- tar_compression = {'gzip': 'gz', None: ''}
- compress_ext = {'gzip': '.gz'}
-
- if _BZ2_SUPPORTED:
- tar_compression['bzip2'] = 'bz2'
- compress_ext['bzip2'] = '.bz2'
-
- # flags for compression program, each element of list will be an argument
- if compress is not None and compress not in compress_ext:
- raise ValueError("bad value for 'compress', or compression format not "
- "supported : {0}".format(compress))
-
- archive_name = base_name + '.tar' + compress_ext.get(compress, '')
- archive_dir = os.path.dirname(archive_name)
-
- if not os.path.exists(archive_dir):
- if logger is not None:
- logger.info("creating %s", archive_dir)
- if not dry_run:
- os.makedirs(archive_dir)
-
- # creating the tarball
- if logger is not None:
- logger.info('Creating tar archive')
-
- uid = _get_uid(owner)
- gid = _get_gid(group)
-
- def _set_uid_gid(tarinfo):
- if gid is not None:
- tarinfo.gid = gid
- tarinfo.gname = group
- if uid is not None:
- tarinfo.uid = uid
- tarinfo.uname = owner
- return tarinfo
-
- if not dry_run:
- tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
- try:
- tar.add(base_dir, filter=_set_uid_gid)
- finally:
- tar.close()
-
- return archive_name
-
-def _call_external_zip(base_dir, zip_filename, verbose=False, dry_run=False):
- # XXX see if we want to keep an external call here
- if verbose:
- zipoptions = "-r"
- else:
- zipoptions = "-rq"
- from distutils.errors import DistutilsExecError
- from distutils.spawn import spawn
- try:
- spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
- except DistutilsExecError:
- # XXX really should distinguish between "couldn't find
- # external 'zip' command" and "zip failed".
- raise ExecError("unable to create zip file '%s': "
- "could neither import the 'zipfile' module nor "
- "find a standalone zip utility") % zip_filename
-
-def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, logger=None):
- """Create a zip file from all the files under 'base_dir'.
-
- The output zip file will be named 'base_name' + ".zip". Uses either the
- "zipfile" Python module (if available) or the InfoZIP "zip" utility
- (if installed and found on the default search path). If neither tool is
- available, raises ExecError. Returns the name of the output zip
- file.
- """
- zip_filename = base_name + ".zip"
- archive_dir = os.path.dirname(base_name)
-
- if not os.path.exists(archive_dir):
- if logger is not None:
- logger.info("creating %s", archive_dir)
- if not dry_run:
- os.makedirs(archive_dir)
-
- # If zipfile module is not available, try spawning an external 'zip'
- # command.
- try:
- import zipfile
- except ImportError:
- zipfile = None
-
- if zipfile is None:
- _call_external_zip(base_dir, zip_filename, verbose, dry_run)
- else:
- if logger is not None:
- logger.info("creating '%s' and adding '%s' to it",
- zip_filename, base_dir)
-
- if not dry_run:
- zip = zipfile.ZipFile(zip_filename, "w",
- compression=zipfile.ZIP_DEFLATED)
-
- for dirpath, dirnames, filenames in os.walk(base_dir):
- for name in filenames:
- path = os.path.normpath(os.path.join(dirpath, name))
- if os.path.isfile(path):
- zip.write(path, path)
- if logger is not None:
- logger.info("adding '%s'", path)
- zip.close()
-
- return zip_filename
-
-_ARCHIVE_FORMATS = {
- 'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
- 'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
- 'tar': (_make_tarball, [('compress', None)], "uncompressed tar file"),
- 'zip': (_make_zipfile, [], "ZIP file"),
- }
-
-if _BZ2_SUPPORTED:
- _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
- "bzip2'ed tar-file")
-
-def get_archive_formats():
- """Returns a list of supported formats for archiving and unarchiving.
-
- Each element of the returned sequence is a tuple (name, description)
- """
- formats = [(name, registry[2]) for name, registry in
- _ARCHIVE_FORMATS.items()]
- formats.sort()
- return formats
-
-def register_archive_format(name, function, extra_args=None, description=''):
- """Registers an archive format.
-
- name is the name of the format. function is the callable that will be
- used to create archives. If provided, extra_args is a sequence of
- (name, value) tuples that will be passed as arguments to the callable.
- description can be provided to describe the format, and will be returned
- by the get_archive_formats() function.
- """
- if extra_args is None:
- extra_args = []
- if not isinstance(function, Callable):
- raise TypeError('The %s object is not callable' % function)
- if not isinstance(extra_args, (tuple, list)):
- raise TypeError('extra_args needs to be a sequence')
- for element in extra_args:
- if not isinstance(element, (tuple, list)) or len(element) !=2:
- raise TypeError('extra_args elements are : (arg_name, value)')
-
- _ARCHIVE_FORMATS[name] = (function, extra_args, description)
-
-def unregister_archive_format(name):
- del _ARCHIVE_FORMATS[name]
-
-def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
- dry_run=0, owner=None, group=None, logger=None):
- """Create an archive file (eg. zip or tar).
-
- 'base_name' is the name of the file to create, minus any format-specific
- extension; 'format' is the archive format: one of "zip", "tar", "bztar"
- or "gztar".
-
- 'root_dir' is a directory that will be the root directory of the
- archive; ie. we typically chdir into 'root_dir' before creating the
- archive. 'base_dir' is the directory where we start archiving from;
- ie. 'base_dir' will be the common prefix of all files and
- directories in the archive. 'root_dir' and 'base_dir' both default
- to the current directory. Returns the name of the archive file.
-
- 'owner' and 'group' are used when creating a tar archive. By default,
- uses the current owner and group.
- """
- save_cwd = os.getcwd()
- if root_dir is not None:
- if logger is not None:
- logger.debug("changing into '%s'", root_dir)
- base_name = os.path.abspath(base_name)
- if not dry_run:
- os.chdir(root_dir)
-
- if base_dir is None:
- base_dir = os.curdir
-
- kwargs = {'dry_run': dry_run, 'logger': logger}
-
- try:
- format_info = _ARCHIVE_FORMATS[format]
- except KeyError:
- raise ValueError("unknown archive format '%s'" % format)
-
- func = format_info[0]
- for arg, val in format_info[1]:
- kwargs[arg] = val
-
- if format != 'zip':
- kwargs['owner'] = owner
- kwargs['group'] = group
-
- try:
- filename = func(base_name, base_dir, **kwargs)
- finally:
- if root_dir is not None:
- if logger is not None:
- logger.debug("changing back to '%s'", save_cwd)
- os.chdir(save_cwd)
-
- return filename
-
-
-def get_unpack_formats():
- """Returns a list of supported formats for unpacking.
-
- Each element of the returned sequence is a tuple
- (name, extensions, description)
- """
- formats = [(name, info[0], info[3]) for name, info in
- _UNPACK_FORMATS.items()]
- formats.sort()
- return formats
-
-def _check_unpack_options(extensions, function, extra_args):
- """Checks what gets registered as an unpacker."""
- # first make sure no other unpacker is registered for this extension
- existing_extensions = {}
- for name, info in _UNPACK_FORMATS.items():
- for ext in info[0]:
- existing_extensions[ext] = name
-
- for extension in extensions:
- if extension in existing_extensions:
- msg = '%s is already registered for "%s"'
- raise RegistryError(msg % (extension,
- existing_extensions[extension]))
-
- if not isinstance(function, Callable):
- raise TypeError('The registered function must be a callable')
-
-
-def register_unpack_format(name, extensions, function, extra_args=None,
- description=''):
- """Registers an unpack format.
-
- `name` is the name of the format. `extensions` is a list of extensions
- corresponding to the format.
-
- `function` is the callable that will be
- used to unpack archives. The callable will receive archives to unpack.
- If it's unable to handle an archive, it needs to raise a ReadError
- exception.
-
- If provided, `extra_args` is a sequence of
- (name, value) tuples that will be passed as arguments to the callable.
- description can be provided to describe the format, and will be returned
- by the get_unpack_formats() function.
- """
- if extra_args is None:
- extra_args = []
- _check_unpack_options(extensions, function, extra_args)
- _UNPACK_FORMATS[name] = extensions, function, extra_args, description
-
-def unregister_unpack_format(name):
- """Removes the pack format from the registry."""
- del _UNPACK_FORMATS[name]
-
-def _ensure_directory(path):
- """Ensure that the parent directory of `path` exists"""
- dirname = os.path.dirname(path)
- if not os.path.isdir(dirname):
- os.makedirs(dirname)
-
-def _unpack_zipfile(filename, extract_dir):
- """Unpack zip `filename` to `extract_dir`
- """
- try:
- import zipfile
- except ImportError:
- raise ReadError('zlib not supported, cannot unpack this archive.')
-
- if not zipfile.is_zipfile(filename):
- raise ReadError("%s is not a zip file" % filename)
-
- zip = zipfile.ZipFile(filename)
- try:
- for info in zip.infolist():
- name = info.filename
-
- # don't extract absolute paths or ones with .. in them
- if name.startswith('/') or '..' in name:
- continue
-
- target = os.path.join(extract_dir, *name.split('/'))
- if not target:
- continue
-
- _ensure_directory(target)
- if not name.endswith('/'):
- # file
- data = zip.read(info.filename)
- f = open(target, 'wb')
- try:
- f.write(data)
- finally:
- f.close()
- del data
- finally:
- zip.close()
-
-def _unpack_tarfile(filename, extract_dir):
- """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir`
- """
- try:
- tarobj = tarfile.open(filename)
- except tarfile.TarError:
- raise ReadError(
- "%s is not a compressed or uncompressed tar file" % filename)
- try:
- tarobj.extractall(extract_dir)
- finally:
- tarobj.close()
-
-_UNPACK_FORMATS = {
- 'gztar': (['.tar.gz', '.tgz'], _unpack_tarfile, [], "gzip'ed tar-file"),
- 'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"),
- 'zip': (['.zip'], _unpack_zipfile, [], "ZIP file")
- }
-
-if _BZ2_SUPPORTED:
- _UNPACK_FORMATS['bztar'] = (['.bz2'], _unpack_tarfile, [],
- "bzip2'ed tar-file")
-
-def _find_unpack_format(filename):
- for name, info in _UNPACK_FORMATS.items():
- for extension in info[0]:
- if filename.endswith(extension):
- return name
- return None
-
-def unpack_archive(filename, extract_dir=None, format=None):
- """Unpack an archive.
-
- `filename` is the name of the archive.
-
- `extract_dir` is the name of the target directory, where the archive
- is unpacked. If not provided, the current working directory is used.
-
- `format` is the archive format: one of "zip", "tar", or "gztar". Or any
- other registered format. If not provided, unpack_archive will use the
- filename extension and see if an unpacker was registered for that
- extension.
-
- In case none is found, a ValueError is raised.
- """
- if extract_dir is None:
- extract_dir = os.getcwd()
-
- if format is not None:
- try:
- format_info = _UNPACK_FORMATS[format]
- except KeyError:
- raise ValueError("Unknown unpack format '{0}'".format(format))
-
- func = format_info[1]
- func(filename, extract_dir, **dict(format_info[2]))
- else:
- # we need to look at the registered unpackers supported extensions
- format = _find_unpack_format(filename)
- if format is None:
- raise ReadError("Unknown archive format '{0}'".format(filename))
-
- func = _UNPACK_FORMATS[format][1]
- kwargs = dict(_UNPACK_FORMATS[format][2])
- func(filename, extract_dir, **kwargs)
diff --git a/pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.cfg b/pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.cfg
deleted file mode 100644
index 1746bd01c1..0000000000
--- a/pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.cfg
+++ /dev/null
@@ -1,84 +0,0 @@
-[posix_prefix]
-# Configuration directories. Some of these come straight out of the
-# configure script. They are for implementing the other variables, not to
-# be used directly in [resource_locations].
-confdir = /etc
-datadir = /usr/share
-libdir = /usr/lib
-statedir = /var
-# User resource directory
-local = ~/.local/{distribution.name}
-
-stdlib = {base}/lib/python{py_version_short}
-platstdlib = {platbase}/lib/python{py_version_short}
-purelib = {base}/lib/python{py_version_short}/site-packages
-platlib = {platbase}/lib/python{py_version_short}/site-packages
-include = {base}/include/python{py_version_short}{abiflags}
-platinclude = {platbase}/include/python{py_version_short}{abiflags}
-data = {base}
-
-[posix_home]
-stdlib = {base}/lib/python
-platstdlib = {base}/lib/python
-purelib = {base}/lib/python
-platlib = {base}/lib/python
-include = {base}/include/python
-platinclude = {base}/include/python
-scripts = {base}/bin
-data = {base}
-
-[nt]
-stdlib = {base}/Lib
-platstdlib = {base}/Lib
-purelib = {base}/Lib/site-packages
-platlib = {base}/Lib/site-packages
-include = {base}/Include
-platinclude = {base}/Include
-scripts = {base}/Scripts
-data = {base}
-
-[os2]
-stdlib = {base}/Lib
-platstdlib = {base}/Lib
-purelib = {base}/Lib/site-packages
-platlib = {base}/Lib/site-packages
-include = {base}/Include
-platinclude = {base}/Include
-scripts = {base}/Scripts
-data = {base}
-
-[os2_home]
-stdlib = {userbase}/lib/python{py_version_short}
-platstdlib = {userbase}/lib/python{py_version_short}
-purelib = {userbase}/lib/python{py_version_short}/site-packages
-platlib = {userbase}/lib/python{py_version_short}/site-packages
-include = {userbase}/include/python{py_version_short}
-scripts = {userbase}/bin
-data = {userbase}
-
-[nt_user]
-stdlib = {userbase}/Python{py_version_nodot}
-platstdlib = {userbase}/Python{py_version_nodot}
-purelib = {userbase}/Python{py_version_nodot}/site-packages
-platlib = {userbase}/Python{py_version_nodot}/site-packages
-include = {userbase}/Python{py_version_nodot}/Include
-scripts = {userbase}/Scripts
-data = {userbase}
-
-[posix_user]
-stdlib = {userbase}/lib/python{py_version_short}
-platstdlib = {userbase}/lib/python{py_version_short}
-purelib = {userbase}/lib/python{py_version_short}/site-packages
-platlib = {userbase}/lib/python{py_version_short}/site-packages
-include = {userbase}/include/python{py_version_short}
-scripts = {userbase}/bin
-data = {userbase}
-
-[osx_framework_user]
-stdlib = {userbase}/lib/python
-platstdlib = {userbase}/lib/python
-purelib = {userbase}/lib/python/site-packages
-platlib = {userbase}/lib/python/site-packages
-include = {userbase}/include
-scripts = {userbase}/bin
-data = {userbase}
diff --git a/pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.py b/pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.py
deleted file mode 100644
index b470a373c8..0000000000
--- a/pipenv/patched/notpip/_vendor/distlib/_backport/sysconfig.py
+++ /dev/null
@@ -1,786 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012 The Python Software Foundation.
-# See LICENSE.txt and CONTRIBUTORS.txt.
-#
-"""Access to Python's configuration information."""
-
-import codecs
-import os
-import re
-import sys
-from os.path import pardir, realpath
-try:
- import configparser
-except ImportError:
- import ConfigParser as configparser
-
-
-__all__ = [
- 'get_config_h_filename',
- 'get_config_var',
- 'get_config_vars',
- 'get_makefile_filename',
- 'get_path',
- 'get_path_names',
- 'get_paths',
- 'get_platform',
- 'get_python_version',
- 'get_scheme_names',
- 'parse_config_h',
-]
-
-
-def _safe_realpath(path):
- try:
- return realpath(path)
- except OSError:
- return path
-
-
-if sys.executable:
- _PROJECT_BASE = os.path.dirname(_safe_realpath(sys.executable))
-else:
- # sys.executable can be empty if argv[0] has been changed and Python is
- # unable to retrieve the real program name
- _PROJECT_BASE = _safe_realpath(os.getcwd())
-
-if os.name == "nt" and "pcbuild" in _PROJECT_BASE[-8:].lower():
- _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir))
-# PC/VS7.1
-if os.name == "nt" and "\\pc\\v" in _PROJECT_BASE[-10:].lower():
- _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
-# PC/AMD64
-if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower():
- _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir))
-
-
-def is_python_build():
- for fn in ("Setup.dist", "Setup.local"):
- if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)):
- return True
- return False
-
-_PYTHON_BUILD = is_python_build()
-
-_cfg_read = False
-
-def _ensure_cfg_read():
- global _cfg_read
- if not _cfg_read:
- from ..resources import finder
- backport_package = __name__.rsplit('.', 1)[0]
- _finder = finder(backport_package)
- _cfgfile = _finder.find('sysconfig.cfg')
- assert _cfgfile, 'sysconfig.cfg exists'
- with _cfgfile.as_stream() as s:
- _SCHEMES.readfp(s)
- if _PYTHON_BUILD:
- for scheme in ('posix_prefix', 'posix_home'):
- _SCHEMES.set(scheme, 'include', '{srcdir}/Include')
- _SCHEMES.set(scheme, 'platinclude', '{projectbase}/.')
-
- _cfg_read = True
-
-
-_SCHEMES = configparser.RawConfigParser()
-_VAR_REPL = re.compile(r'\{([^{]*?)\}')
-
-def _expand_globals(config):
- _ensure_cfg_read()
- if config.has_section('globals'):
- globals = config.items('globals')
- else:
- globals = tuple()
-
- sections = config.sections()
- for section in sections:
- if section == 'globals':
- continue
- for option, value in globals:
- if config.has_option(section, option):
- continue
- config.set(section, option, value)
- config.remove_section('globals')
-
- # now expanding local variables defined in the cfg file
- #
- for section in config.sections():
- variables = dict(config.items(section))
-
- def _replacer(matchobj):
- name = matchobj.group(1)
- if name in variables:
- return variables[name]
- return matchobj.group(0)
-
- for option, value in config.items(section):
- config.set(section, option, _VAR_REPL.sub(_replacer, value))
-
-#_expand_globals(_SCHEMES)
-
-_PY_VERSION = '%s.%s.%s' % sys.version_info[:3]
-_PY_VERSION_SHORT = '%s.%s' % sys.version_info[:2]
-_PY_VERSION_SHORT_NO_DOT = '%s%s' % sys.version_info[:2]
-_PREFIX = os.path.normpath(sys.prefix)
-_EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
-_CONFIG_VARS = None
-_USER_BASE = None
-
-
-def _subst_vars(path, local_vars):
- """In the string `path`, replace tokens like {some.thing} with the
- corresponding value from the map `local_vars`.
-
- If there is no corresponding value, leave the token unchanged.
- """
- def _replacer(matchobj):
- name = matchobj.group(1)
- if name in local_vars:
- return local_vars[name]
- elif name in os.environ:
- return os.environ[name]
- return matchobj.group(0)
- return _VAR_REPL.sub(_replacer, path)
-
-
-def _extend_dict(target_dict, other_dict):
- target_keys = target_dict.keys()
- for key, value in other_dict.items():
- if key in target_keys:
- continue
- target_dict[key] = value
-
-
-def _expand_vars(scheme, vars):
- res = {}
- if vars is None:
- vars = {}
- _extend_dict(vars, get_config_vars())
-
- for key, value in _SCHEMES.items(scheme):
- if os.name in ('posix', 'nt'):
- value = os.path.expanduser(value)
- res[key] = os.path.normpath(_subst_vars(value, vars))
- return res
-
-
-def format_value(value, vars):
- def _replacer(matchobj):
- name = matchobj.group(1)
- if name in vars:
- return vars[name]
- return matchobj.group(0)
- return _VAR_REPL.sub(_replacer, value)
-
-
-def _get_default_scheme():
- if os.name == 'posix':
- # the default scheme for posix is posix_prefix
- return 'posix_prefix'
- return os.name
-
-
-def _getuserbase():
- env_base = os.environ.get("PYTHONUSERBASE", None)
-
- def joinuser(*args):
- return os.path.expanduser(os.path.join(*args))
-
- # what about 'os2emx', 'riscos' ?
- if os.name == "nt":
- base = os.environ.get("APPDATA") or "~"
- if env_base:
- return env_base
- else:
- return joinuser(base, "Python")
-
- if sys.platform == "darwin":
- framework = get_config_var("PYTHONFRAMEWORK")
- if framework:
- if env_base:
- return env_base
- else:
- return joinuser("~", "Library", framework, "%d.%d" %
- sys.version_info[:2])
-
- if env_base:
- return env_base
- else:
- return joinuser("~", ".local")
-
-
-def _parse_makefile(filename, vars=None):
- """Parse a Makefile-style file.
-
- A dictionary containing name/value pairs is returned. If an
- optional dictionary is passed in as the second argument, it is
- used instead of a new dictionary.
- """
- # Regexes needed for parsing Makefile (and similar syntaxes,
- # like old-style Setup files).
- _variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
- _findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
- _findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
-
- if vars is None:
- vars = {}
- done = {}
- notdone = {}
-
- with codecs.open(filename, encoding='utf-8', errors="surrogateescape") as f:
- lines = f.readlines()
-
- for line in lines:
- if line.startswith('#') or line.strip() == '':
- continue
- m = _variable_rx.match(line)
- if m:
- n, v = m.group(1, 2)
- v = v.strip()
- # `$$' is a literal `$' in make
- tmpv = v.replace('$$', '')
-
- if "$" in tmpv:
- notdone[n] = v
- else:
- try:
- v = int(v)
- except ValueError:
- # insert literal `$'
- done[n] = v.replace('$$', '$')
- else:
- done[n] = v
-
- # do variable interpolation here
- variables = list(notdone.keys())
-
- # Variables with a 'PY_' prefix in the makefile. These need to
- # be made available without that prefix through sysconfig.
- # Special care is needed to ensure that variable expansion works, even
- # if the expansion uses the name without a prefix.
- renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')
-
- while len(variables) > 0:
- for name in tuple(variables):
- value = notdone[name]
- m = _findvar1_rx.search(value) or _findvar2_rx.search(value)
- if m is not None:
- n = m.group(1)
- found = True
- if n in done:
- item = str(done[n])
- elif n in notdone:
- # get it on a subsequent round
- found = False
- elif n in os.environ:
- # do it like make: fall back to environment
- item = os.environ[n]
-
- elif n in renamed_variables:
- if (name.startswith('PY_') and
- name[3:] in renamed_variables):
- item = ""
-
- elif 'PY_' + n in notdone:
- found = False
-
- else:
- item = str(done['PY_' + n])
-
- else:
- done[n] = item = ""
-
- if found:
- after = value[m.end():]
- value = value[:m.start()] + item + after
- if "$" in after:
- notdone[name] = value
- else:
- try:
- value = int(value)
- except ValueError:
- done[name] = value.strip()
- else:
- done[name] = value
- variables.remove(name)
-
- if (name.startswith('PY_') and
- name[3:] in renamed_variables):
-
- name = name[3:]
- if name not in done:
- done[name] = value
-
- else:
- # bogus variable reference (e.g. "prefix=$/opt/python");
- # just drop it since we can't deal
- done[name] = value
- variables.remove(name)
-
- # strip spurious spaces
- for k, v in done.items():
- if isinstance(v, str):
- done[k] = v.strip()
-
- # save the results in the global dictionary
- vars.update(done)
- return vars
-
-
-def get_makefile_filename():
- """Return the path of the Makefile."""
- if _PYTHON_BUILD:
- return os.path.join(_PROJECT_BASE, "Makefile")
- if hasattr(sys, 'abiflags'):
- config_dir_name = 'config-%s%s' % (_PY_VERSION_SHORT, sys.abiflags)
- else:
- config_dir_name = 'config'
- return os.path.join(get_path('stdlib'), config_dir_name, 'Makefile')
-
-
-def _init_posix(vars):
- """Initialize the module as appropriate for POSIX systems."""
- # load the installed Makefile:
- makefile = get_makefile_filename()
- try:
- _parse_makefile(makefile, vars)
- except IOError as e:
- msg = "invalid Python installation: unable to open %s" % makefile
- if hasattr(e, "strerror"):
- msg = msg + " (%s)" % e.strerror
- raise IOError(msg)
- # load the installed pyconfig.h:
- config_h = get_config_h_filename()
- try:
- with open(config_h) as f:
- parse_config_h(f, vars)
- except IOError as e:
- msg = "invalid Python installation: unable to open %s" % config_h
- if hasattr(e, "strerror"):
- msg = msg + " (%s)" % e.strerror
- raise IOError(msg)
- # On AIX, there are wrong paths to the linker scripts in the Makefile
- # -- these paths are relative to the Python source, but when installed
- # the scripts are in another directory.
- if _PYTHON_BUILD:
- vars['LDSHARED'] = vars['BLDSHARED']
-
-
-def _init_non_posix(vars):
- """Initialize the module as appropriate for NT"""
- # set basic install directories
- vars['LIBDEST'] = get_path('stdlib')
- vars['BINLIBDEST'] = get_path('platstdlib')
- vars['INCLUDEPY'] = get_path('include')
- vars['SO'] = '.pyd'
- vars['EXE'] = '.exe'
- vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
- vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
-
-#
-# public APIs
-#
-
-
-def parse_config_h(fp, vars=None):
- """Parse a config.h-style file.
-
- A dictionary containing name/value pairs is returned. If an
- optional dictionary is passed in as the second argument, it is
- used instead of a new dictionary.
- """
- if vars is None:
- vars = {}
- define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
- undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
-
- while True:
- line = fp.readline()
- if not line:
- break
- m = define_rx.match(line)
- if m:
- n, v = m.group(1, 2)
- try:
- v = int(v)
- except ValueError:
- pass
- vars[n] = v
- else:
- m = undef_rx.match(line)
- if m:
- vars[m.group(1)] = 0
- return vars
-
-
-def get_config_h_filename():
- """Return the path of pyconfig.h."""
- if _PYTHON_BUILD:
- if os.name == "nt":
- inc_dir = os.path.join(_PROJECT_BASE, "PC")
- else:
- inc_dir = _PROJECT_BASE
- else:
- inc_dir = get_path('platinclude')
- return os.path.join(inc_dir, 'pyconfig.h')
-
-
-def get_scheme_names():
- """Return a tuple containing the schemes names."""
- return tuple(sorted(_SCHEMES.sections()))
-
-
-def get_path_names():
- """Return a tuple containing the paths names."""
- # xxx see if we want a static list
- return _SCHEMES.options('posix_prefix')
-
-
-def get_paths(scheme=_get_default_scheme(), vars=None, expand=True):
- """Return a mapping containing an install scheme.
-
- ``scheme`` is the install scheme name. If not provided, it will
- return the default scheme for the current platform.
- """
- _ensure_cfg_read()
- if expand:
- return _expand_vars(scheme, vars)
- else:
- return dict(_SCHEMES.items(scheme))
-
-
-def get_path(name, scheme=_get_default_scheme(), vars=None, expand=True):
- """Return a path corresponding to the scheme.
-
- ``scheme`` is the install scheme name.
- """
- return get_paths(scheme, vars, expand)[name]
-
-
-def get_config_vars(*args):
- """With no arguments, return a dictionary of all configuration
- variables relevant for the current platform.
-
- On Unix, this means every variable defined in Python's installed Makefile;
- On Windows and Mac OS it's a much smaller set.
-
- With arguments, return a list of values that result from looking up
- each argument in the configuration variable dictionary.
- """
- global _CONFIG_VARS
- if _CONFIG_VARS is None:
- _CONFIG_VARS = {}
- # Normalized versions of prefix and exec_prefix are handy to have;
- # in fact, these are the standard versions used most places in the
- # distutils2 module.
- _CONFIG_VARS['prefix'] = _PREFIX
- _CONFIG_VARS['exec_prefix'] = _EXEC_PREFIX
- _CONFIG_VARS['py_version'] = _PY_VERSION
- _CONFIG_VARS['py_version_short'] = _PY_VERSION_SHORT
- _CONFIG_VARS['py_version_nodot'] = _PY_VERSION[0] + _PY_VERSION[2]
- _CONFIG_VARS['base'] = _PREFIX
- _CONFIG_VARS['platbase'] = _EXEC_PREFIX
- _CONFIG_VARS['projectbase'] = _PROJECT_BASE
- try:
- _CONFIG_VARS['abiflags'] = sys.abiflags
- except AttributeError:
- # sys.abiflags may not be defined on all platforms.
- _CONFIG_VARS['abiflags'] = ''
-
- if os.name in ('nt', 'os2'):
- _init_non_posix(_CONFIG_VARS)
- if os.name == 'posix':
- _init_posix(_CONFIG_VARS)
- # Setting 'userbase' is done below the call to the
- # init function to enable using 'get_config_var' in
- # the init-function.
- if sys.version >= '2.6':
- _CONFIG_VARS['userbase'] = _getuserbase()
-
- if 'srcdir' not in _CONFIG_VARS:
- _CONFIG_VARS['srcdir'] = _PROJECT_BASE
- else:
- _CONFIG_VARS['srcdir'] = _safe_realpath(_CONFIG_VARS['srcdir'])
-
- # Convert srcdir into an absolute path if it appears necessary.
- # Normally it is relative to the build directory. However, during
- # testing, for example, we might be running a non-installed python
- # from a different directory.
- if _PYTHON_BUILD and os.name == "posix":
- base = _PROJECT_BASE
- try:
- cwd = os.getcwd()
- except OSError:
- cwd = None
- if (not os.path.isabs(_CONFIG_VARS['srcdir']) and
- base != cwd):
- # srcdir is relative and we are not in the same directory
- # as the executable. Assume executable is in the build
- # directory and make srcdir absolute.
- srcdir = os.path.join(base, _CONFIG_VARS['srcdir'])
- _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir)
-
- if sys.platform == 'darwin':
- kernel_version = os.uname()[2] # Kernel version (8.4.3)
- major_version = int(kernel_version.split('.')[0])
-
- if major_version < 8:
- # On Mac OS X before 10.4, check if -arch and -isysroot
- # are in CFLAGS or LDFLAGS and remove them if they are.
- # This is needed when building extensions on a 10.3 system
- # using a universal build of python.
- for key in ('LDFLAGS', 'BASECFLAGS',
- # a number of derived variables. These need to be
- # patched up as well.
- 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
- flags = _CONFIG_VARS[key]
- flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
- flags = re.sub('-isysroot [^ \t]*', ' ', flags)
- _CONFIG_VARS[key] = flags
- else:
- # Allow the user to override the architecture flags using
- # an environment variable.
- # NOTE: This name was introduced by Apple in OSX 10.5 and
- # is used by several scripting languages distributed with
- # that OS release.
- if 'ARCHFLAGS' in os.environ:
- arch = os.environ['ARCHFLAGS']
- for key in ('LDFLAGS', 'BASECFLAGS',
- # a number of derived variables. These need to be
- # patched up as well.
- 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
-
- flags = _CONFIG_VARS[key]
- flags = re.sub(r'-arch\s+\w+\s', ' ', flags)
- flags = flags + ' ' + arch
- _CONFIG_VARS[key] = flags
-
- # If we're on OSX 10.5 or later and the user tries to
- # compiles an extension using an SDK that is not present
- # on the current machine it is better to not use an SDK
- # than to fail.
- #
- # The major usecase for this is users using a Python.org
- # binary installer on OSX 10.6: that installer uses
- # the 10.4u SDK, but that SDK is not installed by default
- # when you install Xcode.
- #
- CFLAGS = _CONFIG_VARS.get('CFLAGS', '')
- m = re.search(r'-isysroot\s+(\S+)', CFLAGS)
- if m is not None:
- sdk = m.group(1)
- if not os.path.exists(sdk):
- for key in ('LDFLAGS', 'BASECFLAGS',
- # a number of derived variables. These need to be
- # patched up as well.
- 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'):
-
- flags = _CONFIG_VARS[key]
- flags = re.sub(r'-isysroot\s+\S+(\s|$)', ' ', flags)
- _CONFIG_VARS[key] = flags
-
- if args:
- vals = []
- for name in args:
- vals.append(_CONFIG_VARS.get(name))
- return vals
- else:
- return _CONFIG_VARS
-
-
-def get_config_var(name):
- """Return the value of a single variable using the dictionary returned by
- 'get_config_vars()'.
-
- Equivalent to get_config_vars().get(name)
- """
- return get_config_vars().get(name)
-
-
-def get_platform():
- """Return a string that identifies the current platform.
-
- This is used mainly to distinguish platform-specific build directories and
- platform-specific built distributions. Typically includes the OS name
- and version and the architecture (as supplied by 'os.uname()'),
- although the exact information included depends on the OS; eg. for IRIX
- the architecture isn't particularly important (IRIX only runs on SGI
- hardware), but for Linux the kernel version isn't particularly
- important.
-
- Examples of returned values:
- linux-i586
- linux-alpha (?)
- solaris-2.6-sun4u
- irix-5.3
- irix64-6.2
-
- Windows will return one of:
- win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
- win-ia64 (64bit Windows on Itanium)
- win32 (all others - specifically, sys.platform is returned)
-
- For other non-POSIX platforms, currently just returns 'sys.platform'.
- """
- if os.name == 'nt':
- # sniff sys.version for architecture.
- prefix = " bit ("
- i = sys.version.find(prefix)
- if i == -1:
- return sys.platform
- j = sys.version.find(")", i)
- look = sys.version[i+len(prefix):j].lower()
- if look == 'amd64':
- return 'win-amd64'
- if look == 'itanium':
- return 'win-ia64'
- return sys.platform
-
- if os.name != "posix" or not hasattr(os, 'uname'):
- # XXX what about the architecture? NT is Intel or Alpha,
- # Mac OS is M68k or PPC, etc.
- return sys.platform
-
- # Try to distinguish various flavours of Unix
- osname, host, release, version, machine = os.uname()
-
- # Convert the OS name to lowercase, remove '/' characters
- # (to accommodate BSD/OS), and translate spaces (for "Power Macintosh")
- osname = osname.lower().replace('/', '')
- machine = machine.replace(' ', '_')
- machine = machine.replace('/', '-')
-
- if osname[:5] == "linux":
- # At least on Linux/Intel, 'machine' is the processor --
- # i386, etc.
- # XXX what about Alpha, SPARC, etc?
- return "%s-%s" % (osname, machine)
- elif osname[:5] == "sunos":
- if release[0] >= "5": # SunOS 5 == Solaris 2
- osname = "solaris"
- release = "%d.%s" % (int(release[0]) - 3, release[2:])
- # fall through to standard osname-release-machine representation
- elif osname[:4] == "irix": # could be "irix64"!
- return "%s-%s" % (osname, release)
- elif osname[:3] == "aix":
- return "%s-%s.%s" % (osname, version, release)
- elif osname[:6] == "cygwin":
- osname = "cygwin"
- rel_re = re.compile(r'[\d.]+')
- m = rel_re.match(release)
- if m:
- release = m.group()
- elif osname[:6] == "darwin":
- #
- # For our purposes, we'll assume that the system version from
- # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set
- # to. This makes the compatibility story a bit more sane because the
- # machine is going to compile and link as if it were
- # MACOSX_DEPLOYMENT_TARGET.
- cfgvars = get_config_vars()
- macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET')
-
- if True:
- # Always calculate the release of the running machine,
- # needed to determine if we can build fat binaries or not.
-
- macrelease = macver
- # Get the system version. Reading this plist is a documented
- # way to get the system version (see the documentation for
- # the Gestalt Manager)
- try:
- f = open('/System/Library/CoreServices/SystemVersion.plist')
- except IOError:
- # We're on a plain darwin box, fall back to the default
- # behaviour.
- pass
- else:
- try:
- m = re.search(r'ProductUserVisibleVersion\s*'
- r'(.*?)', f.read())
- finally:
- f.close()
- if m is not None:
- macrelease = '.'.join(m.group(1).split('.')[:2])
- # else: fall back to the default behaviour
-
- if not macver:
- macver = macrelease
-
- if macver:
- release = macver
- osname = "macosx"
-
- if ((macrelease + '.') >= '10.4.' and
- '-arch' in get_config_vars().get('CFLAGS', '').strip()):
- # The universal build will build fat binaries, but not on
- # systems before 10.4
- #
- # Try to detect 4-way universal builds, those have machine-type
- # 'universal' instead of 'fat'.
-
- machine = 'fat'
- cflags = get_config_vars().get('CFLAGS')
-
- archs = re.findall(r'-arch\s+(\S+)', cflags)
- archs = tuple(sorted(set(archs)))
-
- if len(archs) == 1:
- machine = archs[0]
- elif archs == ('i386', 'ppc'):
- machine = 'fat'
- elif archs == ('i386', 'x86_64'):
- machine = 'intel'
- elif archs == ('i386', 'ppc', 'x86_64'):
- machine = 'fat3'
- elif archs == ('ppc64', 'x86_64'):
- machine = 'fat64'
- elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
- machine = 'universal'
- else:
- raise ValueError(
- "Don't know machine value for archs=%r" % (archs,))
-
- elif machine == 'i386':
- # On OSX the machine type returned by uname is always the
- # 32-bit variant, even if the executable architecture is
- # the 64-bit variant
- if sys.maxsize >= 2**32:
- machine = 'x86_64'
-
- elif machine in ('PowerPC', 'Power_Macintosh'):
- # Pick a sane name for the PPC architecture.
- # See 'i386' case
- if sys.maxsize >= 2**32:
- machine = 'ppc64'
- else:
- machine = 'ppc'
-
- return "%s-%s-%s" % (osname, release, machine)
-
-
-def get_python_version():
- return _PY_VERSION_SHORT
-
-
-def _print_dict(title, data):
- for index, (key, value) in enumerate(sorted(data.items())):
- if index == 0:
- print('%s: ' % (title))
- print('\t%s = "%s"' % (key, value))
-
-
-def _main():
- """Display all information sysconfig detains."""
- print('Platform: "%s"' % get_platform())
- print('Python version: "%s"' % get_python_version())
- print('Current installation scheme: "%s"' % _get_default_scheme())
- print()
- _print_dict('Paths', get_paths())
- print()
- _print_dict('Variables', get_config_vars())
-
-
-if __name__ == '__main__':
- _main()
diff --git a/pipenv/patched/notpip/_vendor/distlib/_backport/tarfile.py b/pipenv/patched/notpip/_vendor/distlib/_backport/tarfile.py
deleted file mode 100644
index d66d856637..0000000000
--- a/pipenv/patched/notpip/_vendor/distlib/_backport/tarfile.py
+++ /dev/null
@@ -1,2607 +0,0 @@
-#-------------------------------------------------------------------
-# tarfile.py
-#-------------------------------------------------------------------
-# Copyright (C) 2002 Lars Gustaebel
-# All rights reserved.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation
-# files (the "Software"), to deal in the Software without
-# restriction, including without limitation the rights to use,
-# copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the
-# Software is furnished to do so, subject to the following
-# conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-# OTHER DEALINGS IN THE SOFTWARE.
-#
-from __future__ import print_function
-
-"""Read from and write to tar format archives.
-"""
-
-__version__ = "$Revision$"
-
-version = "0.9.0"
-__author__ = "Lars Gust\u00e4bel (lars@gustaebel.de)"
-__date__ = "$Date: 2011-02-25 17:42:01 +0200 (Fri, 25 Feb 2011) $"
-__cvsid__ = "$Id: tarfile.py 88586 2011-02-25 15:42:01Z marc-andre.lemburg $"
-__credits__ = "Gustavo Niemeyer, Niels Gust\u00e4bel, Richard Townsend."
-
-#---------
-# Imports
-#---------
-import sys
-import os
-import stat
-import errno
-import time
-import struct
-import copy
-import re
-
-try:
- import grp, pwd
-except ImportError:
- grp = pwd = None
-
-# os.symlink on Windows prior to 6.0 raises NotImplementedError
-symlink_exception = (AttributeError, NotImplementedError)
-try:
- # WindowsError (1314) will be raised if the caller does not hold the
- # SeCreateSymbolicLinkPrivilege privilege
- symlink_exception += (WindowsError,)
-except NameError:
- pass
-
-# from tarfile import *
-__all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError"]
-
-if sys.version_info[0] < 3:
- import __builtin__ as builtins
-else:
- import builtins
-
-_open = builtins.open # Since 'open' is TarFile.open
-
-#---------------------------------------------------------
-# tar constants
-#---------------------------------------------------------
-NUL = b"\0" # the null character
-BLOCKSIZE = 512 # length of processing blocks
-RECORDSIZE = BLOCKSIZE * 20 # length of records
-GNU_MAGIC = b"ustar \0" # magic gnu tar string
-POSIX_MAGIC = b"ustar\x0000" # magic posix tar string
-
-LENGTH_NAME = 100 # maximum length of a filename
-LENGTH_LINK = 100 # maximum length of a linkname
-LENGTH_PREFIX = 155 # maximum length of the prefix field
-
-REGTYPE = b"0" # regular file
-AREGTYPE = b"\0" # regular file
-LNKTYPE = b"1" # link (inside tarfile)
-SYMTYPE = b"2" # symbolic link
-CHRTYPE = b"3" # character special device
-BLKTYPE = b"4" # block special device
-DIRTYPE = b"5" # directory
-FIFOTYPE = b"6" # fifo special device
-CONTTYPE = b"7" # contiguous file
-
-GNUTYPE_LONGNAME = b"L" # GNU tar longname
-GNUTYPE_LONGLINK = b"K" # GNU tar longlink
-GNUTYPE_SPARSE = b"S" # GNU tar sparse file
-
-XHDTYPE = b"x" # POSIX.1-2001 extended header
-XGLTYPE = b"g" # POSIX.1-2001 global header
-SOLARIS_XHDTYPE = b"X" # Solaris extended header
-
-USTAR_FORMAT = 0 # POSIX.1-1988 (ustar) format
-GNU_FORMAT = 1 # GNU tar format
-PAX_FORMAT = 2 # POSIX.1-2001 (pax) format
-DEFAULT_FORMAT = GNU_FORMAT
-
-#---------------------------------------------------------
-# tarfile constants
-#---------------------------------------------------------
-# File types that tarfile supports:
-SUPPORTED_TYPES = (REGTYPE, AREGTYPE, LNKTYPE,
- SYMTYPE, DIRTYPE, FIFOTYPE,
- CONTTYPE, CHRTYPE, BLKTYPE,
- GNUTYPE_LONGNAME, GNUTYPE_LONGLINK,
- GNUTYPE_SPARSE)
-
-# File types that will be treated as a regular file.
-REGULAR_TYPES = (REGTYPE, AREGTYPE,
- CONTTYPE, GNUTYPE_SPARSE)
-
-# File types that are part of the GNU tar format.
-GNU_TYPES = (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK,
- GNUTYPE_SPARSE)
-
-# Fields from a pax header that override a TarInfo attribute.
-PAX_FIELDS = ("path", "linkpath", "size", "mtime",
- "uid", "gid", "uname", "gname")
-
-# Fields from a pax header that are affected by hdrcharset.
-PAX_NAME_FIELDS = set(("path", "linkpath", "uname", "gname"))
-
-# Fields in a pax header that are numbers, all other fields
-# are treated as strings.
-PAX_NUMBER_FIELDS = {
- "atime": float,
- "ctime": float,
- "mtime": float,
- "uid": int,
- "gid": int,
- "size": int
-}
-
-#---------------------------------------------------------
-# Bits used in the mode field, values in octal.
-#---------------------------------------------------------
-S_IFLNK = 0o120000 # symbolic link
-S_IFREG = 0o100000 # regular file
-S_IFBLK = 0o060000 # block device
-S_IFDIR = 0o040000 # directory
-S_IFCHR = 0o020000 # character device
-S_IFIFO = 0o010000 # fifo
-
-TSUID = 0o4000 # set UID on execution
-TSGID = 0o2000 # set GID on execution
-TSVTX = 0o1000 # reserved
-
-TUREAD = 0o400 # read by owner
-TUWRITE = 0o200 # write by owner
-TUEXEC = 0o100 # execute/search by owner
-TGREAD = 0o040 # read by group
-TGWRITE = 0o020 # write by group
-TGEXEC = 0o010 # execute/search by group
-TOREAD = 0o004 # read by other
-TOWRITE = 0o002 # write by other
-TOEXEC = 0o001 # execute/search by other
-
-#---------------------------------------------------------
-# initialization
-#---------------------------------------------------------
-if os.name in ("nt", "ce"):
- ENCODING = "utf-8"
-else:
- ENCODING = sys.getfilesystemencoding()
-
-#---------------------------------------------------------
-# Some useful functions
-#---------------------------------------------------------
-
-def stn(s, length, encoding, errors):
- """Convert a string to a null-terminated bytes object.
- """
- s = s.encode(encoding, errors)
- return s[:length] + (length - len(s)) * NUL
-
-def nts(s, encoding, errors):
- """Convert a null-terminated bytes object to a string.
- """
- p = s.find(b"\0")
- if p != -1:
- s = s[:p]
- return s.decode(encoding, errors)
-
-def nti(s):
- """Convert a number field to a python number.
- """
- # There are two possible encodings for a number field, see
- # itn() below.
- if s[0] != chr(0o200):
- try:
- n = int(nts(s, "ascii", "strict") or "0", 8)
- except ValueError:
- raise InvalidHeaderError("invalid header")
- else:
- n = 0
- for i in range(len(s) - 1):
- n <<= 8
- n += ord(s[i + 1])
- return n
-
-def itn(n, digits=8, format=DEFAULT_FORMAT):
- """Convert a python number to a number field.
- """
- # POSIX 1003.1-1988 requires numbers to be encoded as a string of
- # octal digits followed by a null-byte, this allows values up to
- # (8**(digits-1))-1. GNU tar allows storing numbers greater than
- # that if necessary. A leading 0o200 byte indicates this particular
- # encoding, the following digits-1 bytes are a big-endian
- # representation. This allows values up to (256**(digits-1))-1.
- if 0 <= n < 8 ** (digits - 1):
- s = ("%0*o" % (digits - 1, n)).encode("ascii") + NUL
- else:
- if format != GNU_FORMAT or n >= 256 ** (digits - 1):
- raise ValueError("overflow in number field")
-
- if n < 0:
- # XXX We mimic GNU tar's behaviour with negative numbers,
- # this could raise OverflowError.
- n = struct.unpack("L", struct.pack("l", n))[0]
-
- s = bytearray()
- for i in range(digits - 1):
- s.insert(0, n & 0o377)
- n >>= 8
- s.insert(0, 0o200)
- return s
-
-def calc_chksums(buf):
- """Calculate the checksum for a member's header by summing up all
- characters except for the chksum field which is treated as if
- it was filled with spaces. According to the GNU tar sources,
- some tars (Sun and NeXT) calculate chksum with signed char,
- which will be different if there are chars in the buffer with
- the high bit set. So we calculate two checksums, unsigned and
- signed.
- """
- unsigned_chksum = 256 + sum(struct.unpack("148B", buf[:148]) + struct.unpack("356B", buf[156:512]))
- signed_chksum = 256 + sum(struct.unpack("148b", buf[:148]) + struct.unpack("356b", buf[156:512]))
- return unsigned_chksum, signed_chksum
-
-def copyfileobj(src, dst, length=None):
- """Copy length bytes from fileobj src to fileobj dst.
- If length is None, copy the entire content.
- """
- if length == 0:
- return
- if length is None:
- while True:
- buf = src.read(16*1024)
- if not buf:
- break
- dst.write(buf)
- return
-
- BUFSIZE = 16 * 1024
- blocks, remainder = divmod(length, BUFSIZE)
- for b in range(blocks):
- buf = src.read(BUFSIZE)
- if len(buf) < BUFSIZE:
- raise IOError("end of file reached")
- dst.write(buf)
-
- if remainder != 0:
- buf = src.read(remainder)
- if len(buf) < remainder:
- raise IOError("end of file reached")
- dst.write(buf)
- return
-
-filemode_table = (
- ((S_IFLNK, "l"),
- (S_IFREG, "-"),
- (S_IFBLK, "b"),
- (S_IFDIR, "d"),
- (S_IFCHR, "c"),
- (S_IFIFO, "p")),
-
- ((TUREAD, "r"),),
- ((TUWRITE, "w"),),
- ((TUEXEC|TSUID, "s"),
- (TSUID, "S"),
- (TUEXEC, "x")),
-
- ((TGREAD, "r"),),
- ((TGWRITE, "w"),),
- ((TGEXEC|TSGID, "s"),
- (TSGID, "S"),
- (TGEXEC, "x")),
-
- ((TOREAD, "r"),),
- ((TOWRITE, "w"),),
- ((TOEXEC|TSVTX, "t"),
- (TSVTX, "T"),
- (TOEXEC, "x"))
-)
-
-def filemode(mode):
- """Convert a file's mode to a string of the form
- -rwxrwxrwx.
- Used by TarFile.list()
- """
- perm = []
- for table in filemode_table:
- for bit, char in table:
- if mode & bit == bit:
- perm.append(char)
- break
- else:
- perm.append("-")
- return "".join(perm)
-
-class TarError(Exception):
- """Base exception."""
- pass
-class ExtractError(TarError):
- """General exception for extract errors."""
- pass
-class ReadError(TarError):
- """Exception for unreadable tar archives."""
- pass
-class CompressionError(TarError):
- """Exception for unavailable compression methods."""
- pass
-class StreamError(TarError):
- """Exception for unsupported operations on stream-like TarFiles."""
- pass
-class HeaderError(TarError):
- """Base exception for header errors."""
- pass
-class EmptyHeaderError(HeaderError):
- """Exception for empty headers."""
- pass
-class TruncatedHeaderError(HeaderError):
- """Exception for truncated headers."""
- pass
-class EOFHeaderError(HeaderError):
- """Exception for end of file headers."""
- pass
-class InvalidHeaderError(HeaderError):
- """Exception for invalid headers."""
- pass
-class SubsequentHeaderError(HeaderError):
- """Exception for missing and invalid extended headers."""
- pass
-
-#---------------------------
-# internal stream interface
-#---------------------------
-class _LowLevelFile(object):
- """Low-level file object. Supports reading and writing.
- It is used instead of a regular file object for streaming
- access.
- """
-
- def __init__(self, name, mode):
- mode = {
- "r": os.O_RDONLY,
- "w": os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
- }[mode]
- if hasattr(os, "O_BINARY"):
- mode |= os.O_BINARY
- self.fd = os.open(name, mode, 0o666)
-
- def close(self):
- os.close(self.fd)
-
- def read(self, size):
- return os.read(self.fd, size)
-
- def write(self, s):
- os.write(self.fd, s)
-
-class _Stream(object):
- """Class that serves as an adapter between TarFile and
- a stream-like object. The stream-like object only
- needs to have a read() or write() method and is accessed
- blockwise. Use of gzip or bzip2 compression is possible.
- A stream-like object could be for example: sys.stdin,
- sys.stdout, a socket, a tape device etc.
-
- _Stream is intended to be used only internally.
- """
-
- def __init__(self, name, mode, comptype, fileobj, bufsize):
- """Construct a _Stream object.
- """
- self._extfileobj = True
- if fileobj is None:
- fileobj = _LowLevelFile(name, mode)
- self._extfileobj = False
-
- if comptype == '*':
- # Enable transparent compression detection for the
- # stream interface
- fileobj = _StreamProxy(fileobj)
- comptype = fileobj.getcomptype()
-
- self.name = name or ""
- self.mode = mode
- self.comptype = comptype
- self.fileobj = fileobj
- self.bufsize = bufsize
- self.buf = b""
- self.pos = 0
- self.closed = False
-
- try:
- if comptype == "gz":
- try:
- import zlib
- except ImportError:
- raise CompressionError("zlib module is not available")
- self.zlib = zlib
- self.crc = zlib.crc32(b"")
- if mode == "r":
- self._init_read_gz()
- else:
- self._init_write_gz()
-
- if comptype == "bz2":
- try:
- import bz2
- except ImportError:
- raise CompressionError("bz2 module is not available")
- if mode == "r":
- self.dbuf = b""
- self.cmp = bz2.BZ2Decompressor()
- else:
- self.cmp = bz2.BZ2Compressor()
- except:
- if not self._extfileobj:
- self.fileobj.close()
- self.closed = True
- raise
-
- def __del__(self):
- if hasattr(self, "closed") and not self.closed:
- self.close()
-
- def _init_write_gz(self):
- """Initialize for writing with gzip compression.
- """
- self.cmp = self.zlib.compressobj(9, self.zlib.DEFLATED,
- -self.zlib.MAX_WBITS,
- self.zlib.DEF_MEM_LEVEL,
- 0)
- timestamp = struct.pack(" self.bufsize:
- self.fileobj.write(self.buf[:self.bufsize])
- self.buf = self.buf[self.bufsize:]
-
- def close(self):
- """Close the _Stream object. No operation should be
- done on it afterwards.
- """
- if self.closed:
- return
-
- if self.mode == "w" and self.comptype != "tar":
- self.buf += self.cmp.flush()
-
- if self.mode == "w" and self.buf:
- self.fileobj.write(self.buf)
- self.buf = b""
- if self.comptype == "gz":
- # The native zlib crc is an unsigned 32-bit integer, but
- # the Python wrapper implicitly casts that to a signed C
- # long. So, on a 32-bit box self.crc may "look negative",
- # while the same crc on a 64-bit box may "look positive".
- # To avoid irksome warnings from the `struct` module, force
- # it to look positive on all boxes.
- self.fileobj.write(struct.pack("= 0:
- blocks, remainder = divmod(pos - self.pos, self.bufsize)
- for i in range(blocks):
- self.read(self.bufsize)
- self.read(remainder)
- else:
- raise StreamError("seeking backwards is not allowed")
- return self.pos
-
- def read(self, size=None):
- """Return the next size number of bytes from the stream.
- If size is not defined, return all bytes of the stream
- up to EOF.
- """
- if size is None:
- t = []
- while True:
- buf = self._read(self.bufsize)
- if not buf:
- break
- t.append(buf)
- buf = "".join(t)
- else:
- buf = self._read(size)
- self.pos += len(buf)
- return buf
-
- def _read(self, size):
- """Return size bytes from the stream.
- """
- if self.comptype == "tar":
- return self.__read(size)
-
- c = len(self.dbuf)
- while c < size:
- buf = self.__read(self.bufsize)
- if not buf:
- break
- try:
- buf = self.cmp.decompress(buf)
- except IOError:
- raise ReadError("invalid compressed data")
- self.dbuf += buf
- c += len(buf)
- buf = self.dbuf[:size]
- self.dbuf = self.dbuf[size:]
- return buf
-
- def __read(self, size):
- """Return size bytes from stream. If internal buffer is empty,
- read another block from the stream.
- """
- c = len(self.buf)
- while c < size:
- buf = self.fileobj.read(self.bufsize)
- if not buf:
- break
- self.buf += buf
- c += len(buf)
- buf = self.buf[:size]
- self.buf = self.buf[size:]
- return buf
-# class _Stream
-
-class _StreamProxy(object):
- """Small proxy class that enables transparent compression
- detection for the Stream interface (mode 'r|*').
- """
-
- def __init__(self, fileobj):
- self.fileobj = fileobj
- self.buf = self.fileobj.read(BLOCKSIZE)
-
- def read(self, size):
- self.read = self.fileobj.read
- return self.buf
-
- def getcomptype(self):
- if self.buf.startswith(b"\037\213\010"):
- return "gz"
- if self.buf.startswith(b"BZh91"):
- return "bz2"
- return "tar"
-
- def close(self):
- self.fileobj.close()
-# class StreamProxy
-
-class _BZ2Proxy(object):
- """Small proxy class that enables external file object
- support for "r:bz2" and "w:bz2" modes. This is actually
- a workaround for a limitation in bz2 module's BZ2File
- class which (unlike gzip.GzipFile) has no support for
- a file object argument.
- """
-
- blocksize = 16 * 1024
-
- def __init__(self, fileobj, mode):
- self.fileobj = fileobj
- self.mode = mode
- self.name = getattr(self.fileobj, "name", None)
- self.init()
-
- def init(self):
- import bz2
- self.pos = 0
- if self.mode == "r":
- self.bz2obj = bz2.BZ2Decompressor()
- self.fileobj.seek(0)
- self.buf = b""
- else:
- self.bz2obj = bz2.BZ2Compressor()
-
- def read(self, size):
- x = len(self.buf)
- while x < size:
- raw = self.fileobj.read(self.blocksize)
- if not raw:
- break
- data = self.bz2obj.decompress(raw)
- self.buf += data
- x += len(data)
-
- buf = self.buf[:size]
- self.buf = self.buf[size:]
- self.pos += len(buf)
- return buf
-
- def seek(self, pos):
- if pos < self.pos:
- self.init()
- self.read(pos - self.pos)
-
- def tell(self):
- return self.pos
-
- def write(self, data):
- self.pos += len(data)
- raw = self.bz2obj.compress(data)
- self.fileobj.write(raw)
-
- def close(self):
- if self.mode == "w":
- raw = self.bz2obj.flush()
- self.fileobj.write(raw)
-# class _BZ2Proxy
-
-#------------------------
-# Extraction file object
-#------------------------
-class _FileInFile(object):
- """A thin wrapper around an existing file object that
- provides a part of its data as an individual file
- object.
- """
-
- def __init__(self, fileobj, offset, size, blockinfo=None):
- self.fileobj = fileobj
- self.offset = offset
- self.size = size
- self.position = 0
-
- if blockinfo is None:
- blockinfo = [(0, size)]
-
- # Construct a map with data and zero blocks.
- self.map_index = 0
- self.map = []
- lastpos = 0
- realpos = self.offset
- for offset, size in blockinfo:
- if offset > lastpos:
- self.map.append((False, lastpos, offset, None))
- self.map.append((True, offset, offset + size, realpos))
- realpos += size
- lastpos = offset + size
- if lastpos < self.size:
- self.map.append((False, lastpos, self.size, None))
-
- def seekable(self):
- if not hasattr(self.fileobj, "seekable"):
- # XXX gzip.GzipFile and bz2.BZ2File
- return True
- return self.fileobj.seekable()
-
- def tell(self):
- """Return the current file position.
- """
- return self.position
-
- def seek(self, position):
- """Seek to a position in the file.
- """
- self.position = position
-
- def read(self, size=None):
- """Read data from the file.
- """
- if size is None:
- size = self.size - self.position
- else:
- size = min(size, self.size - self.position)
-
- buf = b""
- while size > 0:
- while True:
- data, start, stop, offset = self.map[self.map_index]
- if start <= self.position < stop:
- break
- else:
- self.map_index += 1
- if self.map_index == len(self.map):
- self.map_index = 0
- length = min(size, stop - self.position)
- if data:
- self.fileobj.seek(offset + (self.position - start))
- buf += self.fileobj.read(length)
- else:
- buf += NUL * length
- size -= length
- self.position += length
- return buf
-#class _FileInFile
-
-
-class ExFileObject(object):
- """File-like object for reading an archive member.
- Is returned by TarFile.extractfile().
- """
- blocksize = 1024
-
- def __init__(self, tarfile, tarinfo):
- self.fileobj = _FileInFile(tarfile.fileobj,
- tarinfo.offset_data,
- tarinfo.size,
- tarinfo.sparse)
- self.name = tarinfo.name
- self.mode = "r"
- self.closed = False
- self.size = tarinfo.size
-
- self.position = 0
- self.buffer = b""
-
- def readable(self):
- return True
-
- def writable(self):
- return False
-
- def seekable(self):
- return self.fileobj.seekable()
-
- def read(self, size=None):
- """Read at most size bytes from the file. If size is not
- present or None, read all data until EOF is reached.
- """
- if self.closed:
- raise ValueError("I/O operation on closed file")
-
- buf = b""
- if self.buffer:
- if size is None:
- buf = self.buffer
- self.buffer = b""
- else:
- buf = self.buffer[:size]
- self.buffer = self.buffer[size:]
-
- if size is None:
- buf += self.fileobj.read()
- else:
- buf += self.fileobj.read(size - len(buf))
-
- self.position += len(buf)
- return buf
-
- # XXX TextIOWrapper uses the read1() method.
- read1 = read
-
- def readline(self, size=-1):
- """Read one entire line from the file. If size is present
- and non-negative, return a string with at most that
- size, which may be an incomplete line.
- """
- if self.closed:
- raise ValueError("I/O operation on closed file")
-
- pos = self.buffer.find(b"\n") + 1
- if pos == 0:
- # no newline found.
- while True:
- buf = self.fileobj.read(self.blocksize)
- self.buffer += buf
- if not buf or b"\n" in buf:
- pos = self.buffer.find(b"\n") + 1
- if pos == 0:
- # no newline found.
- pos = len(self.buffer)
- break
-
- if size != -1:
- pos = min(size, pos)
-
- buf = self.buffer[:pos]
- self.buffer = self.buffer[pos:]
- self.position += len(buf)
- return buf
-
- def readlines(self):
- """Return a list with all remaining lines.
- """
- result = []
- while True:
- line = self.readline()
- if not line: break
- result.append(line)
- return result
-
- def tell(self):
- """Return the current file position.
- """
- if self.closed:
- raise ValueError("I/O operation on closed file")
-
- return self.position
-
- def seek(self, pos, whence=os.SEEK_SET):
- """Seek to a position in the file.
- """
- if self.closed:
- raise ValueError("I/O operation on closed file")
-
- if whence == os.SEEK_SET:
- self.position = min(max(pos, 0), self.size)
- elif whence == os.SEEK_CUR:
- if pos < 0:
- self.position = max(self.position + pos, 0)
- else:
- self.position = min(self.position + pos, self.size)
- elif whence == os.SEEK_END:
- self.position = max(min(self.size + pos, self.size), 0)
- else:
- raise ValueError("Invalid argument")
-
- self.buffer = b""
- self.fileobj.seek(self.position)
-
- def close(self):
- """Close the file object.
- """
- self.closed = True
-
- def __iter__(self):
- """Get an iterator over the file's lines.
- """
- while True:
- line = self.readline()
- if not line:
- break
- yield line
-#class ExFileObject
-
-#------------------
-# Exported Classes
-#------------------
-class TarInfo(object):
- """Informational class which holds the details about an
- archive member given by a tar header block.
- TarInfo objects are returned by TarFile.getmember(),
- TarFile.getmembers() and TarFile.gettarinfo() and are
- usually created internally.
- """
-
- __slots__ = ("name", "mode", "uid", "gid", "size", "mtime",
- "chksum", "type", "linkname", "uname", "gname",
- "devmajor", "devminor",
- "offset", "offset_data", "pax_headers", "sparse",
- "tarfile", "_sparse_structs", "_link_target")
-
- def __init__(self, name=""):
- """Construct a TarInfo object. name is the optional name
- of the member.
- """
- self.name = name # member name
- self.mode = 0o644 # file permissions
- self.uid = 0 # user id
- self.gid = 0 # group id
- self.size = 0 # file size
- self.mtime = 0 # modification time
- self.chksum = 0 # header checksum
- self.type = REGTYPE # member type
- self.linkname = "" # link name
- self.uname = "" # user name
- self.gname = "" # group name
- self.devmajor = 0 # device major number
- self.devminor = 0 # device minor number
-
- self.offset = 0 # the tar header starts here
- self.offset_data = 0 # the file's data starts here
-
- self.sparse = None # sparse member information
- self.pax_headers = {} # pax header information
-
- # In pax headers the "name" and "linkname" field are called
- # "path" and "linkpath".
- def _getpath(self):
- return self.name
- def _setpath(self, name):
- self.name = name
- path = property(_getpath, _setpath)
-
- def _getlinkpath(self):
- return self.linkname
- def _setlinkpath(self, linkname):
- self.linkname = linkname
- linkpath = property(_getlinkpath, _setlinkpath)
-
- def __repr__(self):
- return "<%s %r at %#x>" % (self.__class__.__name__,self.name,id(self))
-
- def get_info(self):
- """Return the TarInfo's attributes as a dictionary.
- """
- info = {
- "name": self.name,
- "mode": self.mode & 0o7777,
- "uid": self.uid,
- "gid": self.gid,
- "size": self.size,
- "mtime": self.mtime,
- "chksum": self.chksum,
- "type": self.type,
- "linkname": self.linkname,
- "uname": self.uname,
- "gname": self.gname,
- "devmajor": self.devmajor,
- "devminor": self.devminor
- }
-
- if info["type"] == DIRTYPE and not info["name"].endswith("/"):
- info["name"] += "/"
-
- return info
-
- def tobuf(self, format=DEFAULT_FORMAT, encoding=ENCODING, errors="surrogateescape"):
- """Return a tar header as a string of 512 byte blocks.
- """
- info = self.get_info()
-
- if format == USTAR_FORMAT:
- return self.create_ustar_header(info, encoding, errors)
- elif format == GNU_FORMAT:
- return self.create_gnu_header(info, encoding, errors)
- elif format == PAX_FORMAT:
- return self.create_pax_header(info, encoding)
- else:
- raise ValueError("invalid format")
-
- def create_ustar_header(self, info, encoding, errors):
- """Return the object as a ustar header block.
- """
- info["magic"] = POSIX_MAGIC
-
- if len(info["linkname"]) > LENGTH_LINK:
- raise ValueError("linkname is too long")
-
- if len(info["name"]) > LENGTH_NAME:
- info["prefix"], info["name"] = self._posix_split_name(info["name"])
-
- return self._create_header(info, USTAR_FORMAT, encoding, errors)
-
- def create_gnu_header(self, info, encoding, errors):
- """Return the object as a GNU header block sequence.
- """
- info["magic"] = GNU_MAGIC
-
- buf = b""
- if len(info["linkname"]) > LENGTH_LINK:
- buf += self._create_gnu_long_header(info["linkname"], GNUTYPE_LONGLINK, encoding, errors)
-
- if len(info["name"]) > LENGTH_NAME:
- buf += self._create_gnu_long_header(info["name"], GNUTYPE_LONGNAME, encoding, errors)
-
- return buf + self._create_header(info, GNU_FORMAT, encoding, errors)
-
- def create_pax_header(self, info, encoding):
- """Return the object as a ustar header block. If it cannot be
- represented this way, prepend a pax extended header sequence
- with supplement information.
- """
- info["magic"] = POSIX_MAGIC
- pax_headers = self.pax_headers.copy()
-
- # Test string fields for values that exceed the field length or cannot
- # be represented in ASCII encoding.
- for name, hname, length in (
- ("name", "path", LENGTH_NAME), ("linkname", "linkpath", LENGTH_LINK),
- ("uname", "uname", 32), ("gname", "gname", 32)):
-
- if hname in pax_headers:
- # The pax header has priority.
- continue
-
- # Try to encode the string as ASCII.
- try:
- info[name].encode("ascii", "strict")
- except UnicodeEncodeError:
- pax_headers[hname] = info[name]
- continue
-
- if len(info[name]) > length:
- pax_headers[hname] = info[name]
-
- # Test number fields for values that exceed the field limit or values
- # that like to be stored as float.
- for name, digits in (("uid", 8), ("gid", 8), ("size", 12), ("mtime", 12)):
- if name in pax_headers:
- # The pax header has priority. Avoid overflow.
- info[name] = 0
- continue
-
- val = info[name]
- if not 0 <= val < 8 ** (digits - 1) or isinstance(val, float):
- pax_headers[name] = str(val)
- info[name] = 0
-
- # Create a pax extended header if necessary.
- if pax_headers:
- buf = self._create_pax_generic_header(pax_headers, XHDTYPE, encoding)
- else:
- buf = b""
-
- return buf + self._create_header(info, USTAR_FORMAT, "ascii", "replace")
-
- @classmethod
- def create_pax_global_header(cls, pax_headers):
- """Return the object as a pax global header block sequence.
- """
- return cls._create_pax_generic_header(pax_headers, XGLTYPE, "utf8")
-
- def _posix_split_name(self, name):
- """Split a name longer than 100 chars into a prefix
- and a name part.
- """
- prefix = name[:LENGTH_PREFIX + 1]
- while prefix and prefix[-1] != "/":
- prefix = prefix[:-1]
-
- name = name[len(prefix):]
- prefix = prefix[:-1]
-
- if not prefix or len(name) > LENGTH_NAME:
- raise ValueError("name is too long")
- return prefix, name
-
- @staticmethod
- def _create_header(info, format, encoding, errors):
- """Return a header block. info is a dictionary with file
- information, format must be one of the *_FORMAT constants.
- """
- parts = [
- stn(info.get("name", ""), 100, encoding, errors),
- itn(info.get("mode", 0) & 0o7777, 8, format),
- itn(info.get("uid", 0), 8, format),
- itn(info.get("gid", 0), 8, format),
- itn(info.get("size", 0), 12, format),
- itn(info.get("mtime", 0), 12, format),
- b" ", # checksum field
- info.get("type", REGTYPE),
- stn(info.get("linkname", ""), 100, encoding, errors),
- info.get("magic", POSIX_MAGIC),
- stn(info.get("uname", ""), 32, encoding, errors),
- stn(info.get("gname", ""), 32, encoding, errors),
- itn(info.get("devmajor", 0), 8, format),
- itn(info.get("devminor", 0), 8, format),
- stn(info.get("prefix", ""), 155, encoding, errors)
- ]
-
- buf = struct.pack("%ds" % BLOCKSIZE, b"".join(parts))
- chksum = calc_chksums(buf[-BLOCKSIZE:])[0]
- buf = buf[:-364] + ("%06o\0" % chksum).encode("ascii") + buf[-357:]
- return buf
-
- @staticmethod
- def _create_payload(payload):
- """Return the string payload filled with zero bytes
- up to the next 512 byte border.
- """
- blocks, remainder = divmod(len(payload), BLOCKSIZE)
- if remainder > 0:
- payload += (BLOCKSIZE - remainder) * NUL
- return payload
-
- @classmethod
- def _create_gnu_long_header(cls, name, type, encoding, errors):
- """Return a GNUTYPE_LONGNAME or GNUTYPE_LONGLINK sequence
- for name.
- """
- name = name.encode(encoding, errors) + NUL
-
- info = {}
- info["name"] = "././@LongLink"
- info["type"] = type
- info["size"] = len(name)
- info["magic"] = GNU_MAGIC
-
- # create extended header + name blocks.
- return cls._create_header(info, USTAR_FORMAT, encoding, errors) + \
- cls._create_payload(name)
-
- @classmethod
- def _create_pax_generic_header(cls, pax_headers, type, encoding):
- """Return a POSIX.1-2008 extended or global header sequence
- that contains a list of keyword, value pairs. The values
- must be strings.
- """
- # Check if one of the fields contains surrogate characters and thereby
- # forces hdrcharset=BINARY, see _proc_pax() for more information.
- binary = False
- for keyword, value in pax_headers.items():
- try:
- value.encode("utf8", "strict")
- except UnicodeEncodeError:
- binary = True
- break
-
- records = b""
- if binary:
- # Put the hdrcharset field at the beginning of the header.
- records += b"21 hdrcharset=BINARY\n"
-
- for keyword, value in pax_headers.items():
- keyword = keyword.encode("utf8")
- if binary:
- # Try to restore the original byte representation of `value'.
- # Needless to say, that the encoding must match the string.
- value = value.encode(encoding, "surrogateescape")
- else:
- value = value.encode("utf8")
-
- l = len(keyword) + len(value) + 3 # ' ' + '=' + '\n'
- n = p = 0
- while True:
- n = l + len(str(p))
- if n == p:
- break
- p = n
- records += bytes(str(p), "ascii") + b" " + keyword + b"=" + value + b"\n"
-
- # We use a hardcoded "././@PaxHeader" name like star does
- # instead of the one that POSIX recommends.
- info = {}
- info["name"] = "././@PaxHeader"
- info["type"] = type
- info["size"] = len(records)
- info["magic"] = POSIX_MAGIC
-
- # Create pax header + record blocks.
- return cls._create_header(info, USTAR_FORMAT, "ascii", "replace") + \
- cls._create_payload(records)
-
- @classmethod
- def frombuf(cls, buf, encoding, errors):
- """Construct a TarInfo object from a 512 byte bytes object.
- """
- if len(buf) == 0:
- raise EmptyHeaderError("empty header")
- if len(buf) != BLOCKSIZE:
- raise TruncatedHeaderError("truncated header")
- if buf.count(NUL) == BLOCKSIZE:
- raise EOFHeaderError("end of file header")
-
- chksum = nti(buf[148:156])
- if chksum not in calc_chksums(buf):
- raise InvalidHeaderError("bad checksum")
-
- obj = cls()
- obj.name = nts(buf[0:100], encoding, errors)
- obj.mode = nti(buf[100:108])
- obj.uid = nti(buf[108:116])
- obj.gid = nti(buf[116:124])
- obj.size = nti(buf[124:136])
- obj.mtime = nti(buf[136:148])
- obj.chksum = chksum
- obj.type = buf[156:157]
- obj.linkname = nts(buf[157:257], encoding, errors)
- obj.uname = nts(buf[265:297], encoding, errors)
- obj.gname = nts(buf[297:329], encoding, errors)
- obj.devmajor = nti(buf[329:337])
- obj.devminor = nti(buf[337:345])
- prefix = nts(buf[345:500], encoding, errors)
-
- # Old V7 tar format represents a directory as a regular
- # file with a trailing slash.
- if obj.type == AREGTYPE and obj.name.endswith("/"):
- obj.type = DIRTYPE
-
- # The old GNU sparse format occupies some of the unused
- # space in the buffer for up to 4 sparse structures.
- # Save the them for later processing in _proc_sparse().
- if obj.type == GNUTYPE_SPARSE:
- pos = 386
- structs = []
- for i in range(4):
- try:
- offset = nti(buf[pos:pos + 12])
- numbytes = nti(buf[pos + 12:pos + 24])
- except ValueError:
- break
- structs.append((offset, numbytes))
- pos += 24
- isextended = bool(buf[482])
- origsize = nti(buf[483:495])
- obj._sparse_structs = (structs, isextended, origsize)
-
- # Remove redundant slashes from directories.
- if obj.isdir():
- obj.name = obj.name.rstrip("/")
-
- # Reconstruct a ustar longname.
- if prefix and obj.type not in GNU_TYPES:
- obj.name = prefix + "/" + obj.name
- return obj
-
- @classmethod
- def fromtarfile(cls, tarfile):
- """Return the next TarInfo object from TarFile object
- tarfile.
- """
- buf = tarfile.fileobj.read(BLOCKSIZE)
- obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors)
- obj.offset = tarfile.fileobj.tell() - BLOCKSIZE
- return obj._proc_member(tarfile)
-
- #--------------------------------------------------------------------------
- # The following are methods that are called depending on the type of a
- # member. The entry point is _proc_member() which can be overridden in a
- # subclass to add custom _proc_*() methods. A _proc_*() method MUST
- # implement the following
- # operations:
- # 1. Set self.offset_data to the position where the data blocks begin,
- # if there is data that follows.
- # 2. Set tarfile.offset to the position where the next member's header will
- # begin.
- # 3. Return self or another valid TarInfo object.
- def _proc_member(self, tarfile):
- """Choose the right processing method depending on
- the type and call it.
- """
- if self.type in (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK):
- return self._proc_gnulong(tarfile)
- elif self.type == GNUTYPE_SPARSE:
- return self._proc_sparse(tarfile)
- elif self.type in (XHDTYPE, XGLTYPE, SOLARIS_XHDTYPE):
- return self._proc_pax(tarfile)
- else:
- return self._proc_builtin(tarfile)
-
- def _proc_builtin(self, tarfile):
- """Process a builtin type or an unknown type which
- will be treated as a regular file.
- """
- self.offset_data = tarfile.fileobj.tell()
- offset = self.offset_data
- if self.isreg() or self.type not in SUPPORTED_TYPES:
- # Skip the following data blocks.
- offset += self._block(self.size)
- tarfile.offset = offset
-
- # Patch the TarInfo object with saved global
- # header information.
- self._apply_pax_info(tarfile.pax_headers, tarfile.encoding, tarfile.errors)
-
- return self
-
- def _proc_gnulong(self, tarfile):
- """Process the blocks that hold a GNU longname
- or longlink member.
- """
- buf = tarfile.fileobj.read(self._block(self.size))
-
- # Fetch the next header and process it.
- try:
- next = self.fromtarfile(tarfile)
- except HeaderError:
- raise SubsequentHeaderError("missing or bad subsequent header")
-
- # Patch the TarInfo object from the next header with
- # the longname information.
- next.offset = self.offset
- if self.type == GNUTYPE_LONGNAME:
- next.name = nts(buf, tarfile.encoding, tarfile.errors)
- elif self.type == GNUTYPE_LONGLINK:
- next.linkname = nts(buf, tarfile.encoding, tarfile.errors)
-
- return next
-
- def _proc_sparse(self, tarfile):
- """Process a GNU sparse header plus extra headers.
- """
- # We already collected some sparse structures in frombuf().
- structs, isextended, origsize = self._sparse_structs
- del self._sparse_structs
-
- # Collect sparse structures from extended header blocks.
- while isextended:
- buf = tarfile.fileobj.read(BLOCKSIZE)
- pos = 0
- for i in range(21):
- try:
- offset = nti(buf[pos:pos + 12])
- numbytes = nti(buf[pos + 12:pos + 24])
- except ValueError:
- break
- if offset and numbytes:
- structs.append((offset, numbytes))
- pos += 24
- isextended = bool(buf[504])
- self.sparse = structs
-
- self.offset_data = tarfile.fileobj.tell()
- tarfile.offset = self.offset_data + self._block(self.size)
- self.size = origsize
- return self
-
- def _proc_pax(self, tarfile):
- """Process an extended or global header as described in
- POSIX.1-2008.
- """
- # Read the header information.
- buf = tarfile.fileobj.read(self._block(self.size))
-
- # A pax header stores supplemental information for either
- # the following file (extended) or all following files
- # (global).
- if self.type == XGLTYPE:
- pax_headers = tarfile.pax_headers
- else:
- pax_headers = tarfile.pax_headers.copy()
-
- # Check if the pax header contains a hdrcharset field. This tells us
- # the encoding of the path, linkpath, uname and gname fields. Normally,
- # these fields are UTF-8 encoded but since POSIX.1-2008 tar
- # implementations are allowed to store them as raw binary strings if
- # the translation to UTF-8 fails.
- match = re.search(br"\d+ hdrcharset=([^\n]+)\n", buf)
- if match is not None:
- pax_headers["hdrcharset"] = match.group(1).decode("utf8")
-
- # For the time being, we don't care about anything other than "BINARY".
- # The only other value that is currently allowed by the standard is
- # "ISO-IR 10646 2000 UTF-8" in other words UTF-8.
- hdrcharset = pax_headers.get("hdrcharset")
- if hdrcharset == "BINARY":
- encoding = tarfile.encoding
- else:
- encoding = "utf8"
-
- # Parse pax header information. A record looks like that:
- # "%d %s=%s\n" % (length, keyword, value). length is the size
- # of the complete record including the length field itself and
- # the newline. keyword and value are both UTF-8 encoded strings.
- regex = re.compile(br"(\d+) ([^=]+)=")
- pos = 0
- while True:
- match = regex.match(buf, pos)
- if not match:
- break
-
- length, keyword = match.groups()
- length = int(length)
- value = buf[match.end(2) + 1:match.start(1) + length - 1]
-
- # Normally, we could just use "utf8" as the encoding and "strict"
- # as the error handler, but we better not take the risk. For
- # example, GNU tar <= 1.23 is known to store filenames it cannot
- # translate to UTF-8 as raw strings (unfortunately without a
- # hdrcharset=BINARY header).
- # We first try the strict standard encoding, and if that fails we
- # fall back on the user's encoding and error handler.
- keyword = self._decode_pax_field(keyword, "utf8", "utf8",
- tarfile.errors)
- if keyword in PAX_NAME_FIELDS:
- value = self._decode_pax_field(value, encoding, tarfile.encoding,
- tarfile.errors)
- else:
- value = self._decode_pax_field(value, "utf8", "utf8",
- tarfile.errors)
-
- pax_headers[keyword] = value
- pos += length
-
- # Fetch the next header.
- try:
- next = self.fromtarfile(tarfile)
- except HeaderError:
- raise SubsequentHeaderError("missing or bad subsequent header")
-
- # Process GNU sparse information.
- if "GNU.sparse.map" in pax_headers:
- # GNU extended sparse format version 0.1.
- self._proc_gnusparse_01(next, pax_headers)
-
- elif "GNU.sparse.size" in pax_headers:
- # GNU extended sparse format version 0.0.
- self._proc_gnusparse_00(next, pax_headers, buf)
-
- elif pax_headers.get("GNU.sparse.major") == "1" and pax_headers.get("GNU.sparse.minor") == "0":
- # GNU extended sparse format version 1.0.
- self._proc_gnusparse_10(next, pax_headers, tarfile)
-
- if self.type in (XHDTYPE, SOLARIS_XHDTYPE):
- # Patch the TarInfo object with the extended header info.
- next._apply_pax_info(pax_headers, tarfile.encoding, tarfile.errors)
- next.offset = self.offset
-
- if "size" in pax_headers:
- # If the extended header replaces the size field,
- # we need to recalculate the offset where the next
- # header starts.
- offset = next.offset_data
- if next.isreg() or next.type not in SUPPORTED_TYPES:
- offset += next._block(next.size)
- tarfile.offset = offset
-
- return next
-
- def _proc_gnusparse_00(self, next, pax_headers, buf):
- """Process a GNU tar extended sparse header, version 0.0.
- """
- offsets = []
- for match in re.finditer(br"\d+ GNU.sparse.offset=(\d+)\n", buf):
- offsets.append(int(match.group(1)))
- numbytes = []
- for match in re.finditer(br"\d+ GNU.sparse.numbytes=(\d+)\n", buf):
- numbytes.append(int(match.group(1)))
- next.sparse = list(zip(offsets, numbytes))
-
- def _proc_gnusparse_01(self, next, pax_headers):
- """Process a GNU tar extended sparse header, version 0.1.
- """
- sparse = [int(x) for x in pax_headers["GNU.sparse.map"].split(",")]
- next.sparse = list(zip(sparse[::2], sparse[1::2]))
-
- def _proc_gnusparse_10(self, next, pax_headers, tarfile):
- """Process a GNU tar extended sparse header, version 1.0.
- """
- fields = None
- sparse = []
- buf = tarfile.fileobj.read(BLOCKSIZE)
- fields, buf = buf.split(b"\n", 1)
- fields = int(fields)
- while len(sparse) < fields * 2:
- if b"\n" not in buf:
- buf += tarfile.fileobj.read(BLOCKSIZE)
- number, buf = buf.split(b"\n", 1)
- sparse.append(int(number))
- next.offset_data = tarfile.fileobj.tell()
- next.sparse = list(zip(sparse[::2], sparse[1::2]))
-
- def _apply_pax_info(self, pax_headers, encoding, errors):
- """Replace fields with supplemental information from a previous
- pax extended or global header.
- """
- for keyword, value in pax_headers.items():
- if keyword == "GNU.sparse.name":
- setattr(self, "path", value)
- elif keyword == "GNU.sparse.size":
- setattr(self, "size", int(value))
- elif keyword == "GNU.sparse.realsize":
- setattr(self, "size", int(value))
- elif keyword in PAX_FIELDS:
- if keyword in PAX_NUMBER_FIELDS:
- try:
- value = PAX_NUMBER_FIELDS[keyword](value)
- except ValueError:
- value = 0
- if keyword == "path":
- value = value.rstrip("/")
- setattr(self, keyword, value)
-
- self.pax_headers = pax_headers.copy()
-
- def _decode_pax_field(self, value, encoding, fallback_encoding, fallback_errors):
- """Decode a single field from a pax record.
- """
- try:
- return value.decode(encoding, "strict")
- except UnicodeDecodeError:
- return value.decode(fallback_encoding, fallback_errors)
-
- def _block(self, count):
- """Round up a byte count by BLOCKSIZE and return it,
- e.g. _block(834) => 1024.
- """
- blocks, remainder = divmod(count, BLOCKSIZE)
- if remainder:
- blocks += 1
- return blocks * BLOCKSIZE
-
- def isreg(self):
- return self.type in REGULAR_TYPES
- def isfile(self):
- return self.isreg()
- def isdir(self):
- return self.type == DIRTYPE
- def issym(self):
- return self.type == SYMTYPE
- def islnk(self):
- return self.type == LNKTYPE
- def ischr(self):
- return self.type == CHRTYPE
- def isblk(self):
- return self.type == BLKTYPE
- def isfifo(self):
- return self.type == FIFOTYPE
- def issparse(self):
- return self.sparse is not None
- def isdev(self):
- return self.type in (CHRTYPE, BLKTYPE, FIFOTYPE)
-# class TarInfo
-
-class TarFile(object):
- """The TarFile Class provides an interface to tar archives.
- """
-
- debug = 0 # May be set from 0 (no msgs) to 3 (all msgs)
-
- dereference = False # If true, add content of linked file to the
- # tar file, else the link.
-
- ignore_zeros = False # If true, skips empty or invalid blocks and
- # continues processing.
-
- errorlevel = 1 # If 0, fatal errors only appear in debug
- # messages (if debug >= 0). If > 0, errors
- # are passed to the caller as exceptions.
-
- format = DEFAULT_FORMAT # The format to use when creating an archive.
-
- encoding = ENCODING # Encoding for 8-bit character strings.
-
- errors = None # Error handler for unicode conversion.
-
- tarinfo = TarInfo # The default TarInfo class to use.
-
- fileobject = ExFileObject # The default ExFileObject class to use.
-
- def __init__(self, name=None, mode="r", fileobj=None, format=None,
- tarinfo=None, dereference=None, ignore_zeros=None, encoding=None,
- errors="surrogateescape", pax_headers=None, debug=None, errorlevel=None):
- """Open an (uncompressed) tar archive `name'. `mode' is either 'r' to
- read from an existing archive, 'a' to append data to an existing
- file or 'w' to create a new file overwriting an existing one. `mode'
- defaults to 'r'.
- If `fileobj' is given, it is used for reading or writing data. If it
- can be determined, `mode' is overridden by `fileobj's mode.
- `fileobj' is not closed, when TarFile is closed.
- """
- if len(mode) > 1 or mode not in "raw":
- raise ValueError("mode must be 'r', 'a' or 'w'")
- self.mode = mode
- self._mode = {"r": "rb", "a": "r+b", "w": "wb"}[mode]
-
- if not fileobj:
- if self.mode == "a" and not os.path.exists(name):
- # Create nonexistent files in append mode.
- self.mode = "w"
- self._mode = "wb"
- fileobj = bltn_open(name, self._mode)
- self._extfileobj = False
- else:
- if name is None and hasattr(fileobj, "name"):
- name = fileobj.name
- if hasattr(fileobj, "mode"):
- self._mode = fileobj.mode
- self._extfileobj = True
- self.name = os.path.abspath(name) if name else None
- self.fileobj = fileobj
-
- # Init attributes.
- if format is not None:
- self.format = format
- if tarinfo is not None:
- self.tarinfo = tarinfo
- if dereference is not None:
- self.dereference = dereference
- if ignore_zeros is not None:
- self.ignore_zeros = ignore_zeros
- if encoding is not None:
- self.encoding = encoding
- self.errors = errors
-
- if pax_headers is not None and self.format == PAX_FORMAT:
- self.pax_headers = pax_headers
- else:
- self.pax_headers = {}
-
- if debug is not None:
- self.debug = debug
- if errorlevel is not None:
- self.errorlevel = errorlevel
-
- # Init datastructures.
- self.closed = False
- self.members = [] # list of members as TarInfo objects
- self._loaded = False # flag if all members have been read
- self.offset = self.fileobj.tell()
- # current position in the archive file
- self.inodes = {} # dictionary caching the inodes of
- # archive members already added
-
- try:
- if self.mode == "r":
- self.firstmember = None
- self.firstmember = self.next()
-
- if self.mode == "a":
- # Move to the end of the archive,
- # before the first empty block.
- while True:
- self.fileobj.seek(self.offset)
- try:
- tarinfo = self.tarinfo.fromtarfile(self)
- self.members.append(tarinfo)
- except EOFHeaderError:
- self.fileobj.seek(self.offset)
- break
- except HeaderError as e:
- raise ReadError(str(e))
-
- if self.mode in "aw":
- self._loaded = True
-
- if self.pax_headers:
- buf = self.tarinfo.create_pax_global_header(self.pax_headers.copy())
- self.fileobj.write(buf)
- self.offset += len(buf)
- except:
- if not self._extfileobj:
- self.fileobj.close()
- self.closed = True
- raise
-
- #--------------------------------------------------------------------------
- # Below are the classmethods which act as alternate constructors to the
- # TarFile class. The open() method is the only one that is needed for
- # public use; it is the "super"-constructor and is able to select an
- # adequate "sub"-constructor for a particular compression using the mapping
- # from OPEN_METH.
- #
- # This concept allows one to subclass TarFile without losing the comfort of
- # the super-constructor. A sub-constructor is registered and made available
- # by adding it to the mapping in OPEN_METH.
-
- @classmethod
- def open(cls, name=None, mode="r", fileobj=None, bufsize=RECORDSIZE, **kwargs):
- """Open a tar archive for reading, writing or appending. Return
- an appropriate TarFile class.
-
- mode:
- 'r' or 'r:*' open for reading with transparent compression
- 'r:' open for reading exclusively uncompressed
- 'r:gz' open for reading with gzip compression
- 'r:bz2' open for reading with bzip2 compression
- 'a' or 'a:' open for appending, creating the file if necessary
- 'w' or 'w:' open for writing without compression
- 'w:gz' open for writing with gzip compression
- 'w:bz2' open for writing with bzip2 compression
-
- 'r|*' open a stream of tar blocks with transparent compression
- 'r|' open an uncompressed stream of tar blocks for reading
- 'r|gz' open a gzip compressed stream of tar blocks
- 'r|bz2' open a bzip2 compressed stream of tar blocks
- 'w|' open an uncompressed stream for writing
- 'w|gz' open a gzip compressed stream for writing
- 'w|bz2' open a bzip2 compressed stream for writing
- """
-
- if not name and not fileobj:
- raise ValueError("nothing to open")
-
- if mode in ("r", "r:*"):
- # Find out which *open() is appropriate for opening the file.
- for comptype in cls.OPEN_METH:
- func = getattr(cls, cls.OPEN_METH[comptype])
- if fileobj is not None:
- saved_pos = fileobj.tell()
- try:
- return func(name, "r", fileobj, **kwargs)
- except (ReadError, CompressionError) as e:
- if fileobj is not None:
- fileobj.seek(saved_pos)
- continue
- raise ReadError("file could not be opened successfully")
-
- elif ":" in mode:
- filemode, comptype = mode.split(":", 1)
- filemode = filemode or "r"
- comptype = comptype or "tar"
-
- # Select the *open() function according to
- # given compression.
- if comptype in cls.OPEN_METH:
- func = getattr(cls, cls.OPEN_METH[comptype])
- else:
- raise CompressionError("unknown compression type %r" % comptype)
- return func(name, filemode, fileobj, **kwargs)
-
- elif "|" in mode:
- filemode, comptype = mode.split("|", 1)
- filemode = filemode or "r"
- comptype = comptype or "tar"
-
- if filemode not in "rw":
- raise ValueError("mode must be 'r' or 'w'")
-
- stream = _Stream(name, filemode, comptype, fileobj, bufsize)
- try:
- t = cls(name, filemode, stream, **kwargs)
- except:
- stream.close()
- raise
- t._extfileobj = False
- return t
-
- elif mode in "aw":
- return cls.taropen(name, mode, fileobj, **kwargs)
-
- raise ValueError("undiscernible mode")
-
- @classmethod
- def taropen(cls, name, mode="r", fileobj=None, **kwargs):
- """Open uncompressed tar archive name for reading or writing.
- """
- if len(mode) > 1 or mode not in "raw":
- raise ValueError("mode must be 'r', 'a' or 'w'")
- return cls(name, mode, fileobj, **kwargs)
-
- @classmethod
- def gzopen(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs):
- """Open gzip compressed tar archive name for reading or writing.
- Appending is not allowed.
- """
- if len(mode) > 1 or mode not in "rw":
- raise ValueError("mode must be 'r' or 'w'")
-
- try:
- import gzip
- gzip.GzipFile
- except (ImportError, AttributeError):
- raise CompressionError("gzip module is not available")
-
- extfileobj = fileobj is not None
- try:
- fileobj = gzip.GzipFile(name, mode + "b", compresslevel, fileobj)
- t = cls.taropen(name, mode, fileobj, **kwargs)
- except IOError:
- if not extfileobj and fileobj is not None:
- fileobj.close()
- if fileobj is None:
- raise
- raise ReadError("not a gzip file")
- except:
- if not extfileobj and fileobj is not None:
- fileobj.close()
- raise
- t._extfileobj = extfileobj
- return t
-
- @classmethod
- def bz2open(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs):
- """Open bzip2 compressed tar archive name for reading or writing.
- Appending is not allowed.
- """
- if len(mode) > 1 or mode not in "rw":
- raise ValueError("mode must be 'r' or 'w'.")
-
- try:
- import bz2
- except ImportError:
- raise CompressionError("bz2 module is not available")
-
- if fileobj is not None:
- fileobj = _BZ2Proxy(fileobj, mode)
- else:
- fileobj = bz2.BZ2File(name, mode, compresslevel=compresslevel)
-
- try:
- t = cls.taropen(name, mode, fileobj, **kwargs)
- except (IOError, EOFError):
- fileobj.close()
- raise ReadError("not a bzip2 file")
- t._extfileobj = False
- return t
-
- # All *open() methods are registered here.
- OPEN_METH = {
- "tar": "taropen", # uncompressed tar
- "gz": "gzopen", # gzip compressed tar
- "bz2": "bz2open" # bzip2 compressed tar
- }
-
- #--------------------------------------------------------------------------
- # The public methods which TarFile provides:
-
- def close(self):
- """Close the TarFile. In write-mode, two finishing zero blocks are
- appended to the archive.
- """
- if self.closed:
- return
-
- if self.mode in "aw":
- self.fileobj.write(NUL * (BLOCKSIZE * 2))
- self.offset += (BLOCKSIZE * 2)
- # fill up the end with zero-blocks
- # (like option -b20 for tar does)
- blocks, remainder = divmod(self.offset, RECORDSIZE)
- if remainder > 0:
- self.fileobj.write(NUL * (RECORDSIZE - remainder))
-
- if not self._extfileobj:
- self.fileobj.close()
- self.closed = True
-
- def getmember(self, name):
- """Return a TarInfo object for member `name'. If `name' can not be
- found in the archive, KeyError is raised. If a member occurs more
- than once in the archive, its last occurrence is assumed to be the
- most up-to-date version.
- """
- tarinfo = self._getmember(name)
- if tarinfo is None:
- raise KeyError("filename %r not found" % name)
- return tarinfo
-
- def getmembers(self):
- """Return the members of the archive as a list of TarInfo objects. The
- list has the same order as the members in the archive.
- """
- self._check()
- if not self._loaded: # if we want to obtain a list of
- self._load() # all members, we first have to
- # scan the whole archive.
- return self.members
-
- def getnames(self):
- """Return the members of the archive as a list of their names. It has
- the same order as the list returned by getmembers().
- """
- return [tarinfo.name for tarinfo in self.getmembers()]
-
- def gettarinfo(self, name=None, arcname=None, fileobj=None):
- """Create a TarInfo object for either the file `name' or the file
- object `fileobj' (using os.fstat on its file descriptor). You can
- modify some of the TarInfo's attributes before you add it using
- addfile(). If given, `arcname' specifies an alternative name for the
- file in the archive.
- """
- self._check("aw")
-
- # When fileobj is given, replace name by
- # fileobj's real name.
- if fileobj is not None:
- name = fileobj.name
-
- # Building the name of the member in the archive.
- # Backward slashes are converted to forward slashes,
- # Absolute paths are turned to relative paths.
- if arcname is None:
- arcname = name
- drv, arcname = os.path.splitdrive(arcname)
- arcname = arcname.replace(os.sep, "/")
- arcname = arcname.lstrip("/")
-
- # Now, fill the TarInfo object with
- # information specific for the file.
- tarinfo = self.tarinfo()
- tarinfo.tarfile = self
-
- # Use os.stat or os.lstat, depending on platform
- # and if symlinks shall be resolved.
- if fileobj is None:
- if hasattr(os, "lstat") and not self.dereference:
- statres = os.lstat(name)
- else:
- statres = os.stat(name)
- else:
- statres = os.fstat(fileobj.fileno())
- linkname = ""
-
- stmd = statres.st_mode
- if stat.S_ISREG(stmd):
- inode = (statres.st_ino, statres.st_dev)
- if not self.dereference and statres.st_nlink > 1 and \
- inode in self.inodes and arcname != self.inodes[inode]:
- # Is it a hardlink to an already
- # archived file?
- type = LNKTYPE
- linkname = self.inodes[inode]
- else:
- # The inode is added only if its valid.
- # For win32 it is always 0.
- type = REGTYPE
- if inode[0]:
- self.inodes[inode] = arcname
- elif stat.S_ISDIR(stmd):
- type = DIRTYPE
- elif stat.S_ISFIFO(stmd):
- type = FIFOTYPE
- elif stat.S_ISLNK(stmd):
- type = SYMTYPE
- linkname = os.readlink(name)
- elif stat.S_ISCHR(stmd):
- type = CHRTYPE
- elif stat.S_ISBLK(stmd):
- type = BLKTYPE
- else:
- return None
-
- # Fill the TarInfo object with all
- # information we can get.
- tarinfo.name = arcname
- tarinfo.mode = stmd
- tarinfo.uid = statres.st_uid
- tarinfo.gid = statres.st_gid
- if type == REGTYPE:
- tarinfo.size = statres.st_size
- else:
- tarinfo.size = 0
- tarinfo.mtime = statres.st_mtime
- tarinfo.type = type
- tarinfo.linkname = linkname
- if pwd:
- try:
- tarinfo.uname = pwd.getpwuid(tarinfo.uid)[0]
- except KeyError:
- pass
- if grp:
- try:
- tarinfo.gname = grp.getgrgid(tarinfo.gid)[0]
- except KeyError:
- pass
-
- if type in (CHRTYPE, BLKTYPE):
- if hasattr(os, "major") and hasattr(os, "minor"):
- tarinfo.devmajor = os.major(statres.st_rdev)
- tarinfo.devminor = os.minor(statres.st_rdev)
- return tarinfo
-
- def list(self, verbose=True):
- """Print a table of contents to sys.stdout. If `verbose' is False, only
- the names of the members are printed. If it is True, an `ls -l'-like
- output is produced.
- """
- self._check()
-
- for tarinfo in self:
- if verbose:
- print(filemode(tarinfo.mode), end=' ')
- print("%s/%s" % (tarinfo.uname or tarinfo.uid,
- tarinfo.gname or tarinfo.gid), end=' ')
- if tarinfo.ischr() or tarinfo.isblk():
- print("%10s" % ("%d,%d" \
- % (tarinfo.devmajor, tarinfo.devminor)), end=' ')
- else:
- print("%10d" % tarinfo.size, end=' ')
- print("%d-%02d-%02d %02d:%02d:%02d" \
- % time.localtime(tarinfo.mtime)[:6], end=' ')
-
- print(tarinfo.name + ("/" if tarinfo.isdir() else ""), end=' ')
-
- if verbose:
- if tarinfo.issym():
- print("->", tarinfo.linkname, end=' ')
- if tarinfo.islnk():
- print("link to", tarinfo.linkname, end=' ')
- print()
-
- def add(self, name, arcname=None, recursive=True, exclude=None, filter=None):
- """Add the file `name' to the archive. `name' may be any type of file
- (directory, fifo, symbolic link, etc.). If given, `arcname'
- specifies an alternative name for the file in the archive.
- Directories are added recursively by default. This can be avoided by
- setting `recursive' to False. `exclude' is a function that should
- return True for each filename to be excluded. `filter' is a function
- that expects a TarInfo object argument and returns the changed
- TarInfo object, if it returns None the TarInfo object will be
- excluded from the archive.
- """
- self._check("aw")
-
- if arcname is None:
- arcname = name
-
- # Exclude pathnames.
- if exclude is not None:
- import warnings
- warnings.warn("use the filter argument instead",
- DeprecationWarning, 2)
- if exclude(name):
- self._dbg(2, "tarfile: Excluded %r" % name)
- return
-
- # Skip if somebody tries to archive the archive...
- if self.name is not None and os.path.abspath(name) == self.name:
- self._dbg(2, "tarfile: Skipped %r" % name)
- return
-
- self._dbg(1, name)
-
- # Create a TarInfo object from the file.
- tarinfo = self.gettarinfo(name, arcname)
-
- if tarinfo is None:
- self._dbg(1, "tarfile: Unsupported type %r" % name)
- return
-
- # Change or exclude the TarInfo object.
- if filter is not None:
- tarinfo = filter(tarinfo)
- if tarinfo is None:
- self._dbg(2, "tarfile: Excluded %r" % name)
- return
-
- # Append the tar header and data to the archive.
- if tarinfo.isreg():
- f = bltn_open(name, "rb")
- self.addfile(tarinfo, f)
- f.close()
-
- elif tarinfo.isdir():
- self.addfile(tarinfo)
- if recursive:
- for f in os.listdir(name):
- self.add(os.path.join(name, f), os.path.join(arcname, f),
- recursive, exclude, filter=filter)
-
- else:
- self.addfile(tarinfo)
-
- def addfile(self, tarinfo, fileobj=None):
- """Add the TarInfo object `tarinfo' to the archive. If `fileobj' is
- given, tarinfo.size bytes are read from it and added to the archive.
- You can create TarInfo objects using gettarinfo().
- On Windows platforms, `fileobj' should always be opened with mode
- 'rb' to avoid irritation about the file size.
- """
- self._check("aw")
-
- tarinfo = copy.copy(tarinfo)
-
- buf = tarinfo.tobuf(self.format, self.encoding, self.errors)
- self.fileobj.write(buf)
- self.offset += len(buf)
-
- # If there's data to follow, append it.
- if fileobj is not None:
- copyfileobj(fileobj, self.fileobj, tarinfo.size)
- blocks, remainder = divmod(tarinfo.size, BLOCKSIZE)
- if remainder > 0:
- self.fileobj.write(NUL * (BLOCKSIZE - remainder))
- blocks += 1
- self.offset += blocks * BLOCKSIZE
-
- self.members.append(tarinfo)
-
- def extractall(self, path=".", members=None):
- """Extract all members from the archive to the current working
- directory and set owner, modification time and permissions on
- directories afterwards. `path' specifies a different directory
- to extract to. `members' is optional and must be a subset of the
- list returned by getmembers().
- """
- directories = []
-
- if members is None:
- members = self
-
- for tarinfo in members:
- if tarinfo.isdir():
- # Extract directories with a safe mode.
- directories.append(tarinfo)
- tarinfo = copy.copy(tarinfo)
- tarinfo.mode = 0o700
- # Do not set_attrs directories, as we will do that further down
- self.extract(tarinfo, path, set_attrs=not tarinfo.isdir())
-
- # Reverse sort directories.
- directories.sort(key=lambda a: a.name)
- directories.reverse()
-
- # Set correct owner, mtime and filemode on directories.
- for tarinfo in directories:
- dirpath = os.path.join(path, tarinfo.name)
- try:
- self.chown(tarinfo, dirpath)
- self.utime(tarinfo, dirpath)
- self.chmod(tarinfo, dirpath)
- except ExtractError as e:
- if self.errorlevel > 1:
- raise
- else:
- self._dbg(1, "tarfile: %s" % e)
-
- def extract(self, member, path="", set_attrs=True):
- """Extract a member from the archive to the current working directory,
- using its full name. Its file information is extracted as accurately
- as possible. `member' may be a filename or a TarInfo object. You can
- specify a different directory using `path'. File attributes (owner,
- mtime, mode) are set unless `set_attrs' is False.
- """
- self._check("r")
-
- if isinstance(member, str):
- tarinfo = self.getmember(member)
- else:
- tarinfo = member
-
- # Prepare the link target for makelink().
- if tarinfo.islnk():
- tarinfo._link_target = os.path.join(path, tarinfo.linkname)
-
- try:
- self._extract_member(tarinfo, os.path.join(path, tarinfo.name),
- set_attrs=set_attrs)
- except EnvironmentError as e:
- if self.errorlevel > 0:
- raise
- else:
- if e.filename is None:
- self._dbg(1, "tarfile: %s" % e.strerror)
- else:
- self._dbg(1, "tarfile: %s %r" % (e.strerror, e.filename))
- except ExtractError as e:
- if self.errorlevel > 1:
- raise
- else:
- self._dbg(1, "tarfile: %s" % e)
-
- def extractfile(self, member):
- """Extract a member from the archive as a file object. `member' may be
- a filename or a TarInfo object. If `member' is a regular file, a
- file-like object is returned. If `member' is a link, a file-like
- object is constructed from the link's target. If `member' is none of
- the above, None is returned.
- The file-like object is read-only and provides the following
- methods: read(), readline(), readlines(), seek() and tell()
- """
- self._check("r")
-
- if isinstance(member, str):
- tarinfo = self.getmember(member)
- else:
- tarinfo = member
-
- if tarinfo.isreg():
- return self.fileobject(self, tarinfo)
-
- elif tarinfo.type not in SUPPORTED_TYPES:
- # If a member's type is unknown, it is treated as a
- # regular file.
- return self.fileobject(self, tarinfo)
-
- elif tarinfo.islnk() or tarinfo.issym():
- if isinstance(self.fileobj, _Stream):
- # A small but ugly workaround for the case that someone tries
- # to extract a (sym)link as a file-object from a non-seekable
- # stream of tar blocks.
- raise StreamError("cannot extract (sym)link as file object")
- else:
- # A (sym)link's file object is its target's file object.
- return self.extractfile(self._find_link_target(tarinfo))
- else:
- # If there's no data associated with the member (directory, chrdev,
- # blkdev, etc.), return None instead of a file object.
- return None
-
- def _extract_member(self, tarinfo, targetpath, set_attrs=True):
- """Extract the TarInfo object tarinfo to a physical
- file called targetpath.
- """
- # Fetch the TarInfo object for the given name
- # and build the destination pathname, replacing
- # forward slashes to platform specific separators.
- targetpath = targetpath.rstrip("/")
- targetpath = targetpath.replace("/", os.sep)
-
- # Create all upper directories.
- upperdirs = os.path.dirname(targetpath)
- if upperdirs and not os.path.exists(upperdirs):
- # Create directories that are not part of the archive with
- # default permissions.
- os.makedirs(upperdirs)
-
- if tarinfo.islnk() or tarinfo.issym():
- self._dbg(1, "%s -> %s" % (tarinfo.name, tarinfo.linkname))
- else:
- self._dbg(1, tarinfo.name)
-
- if tarinfo.isreg():
- self.makefile(tarinfo, targetpath)
- elif tarinfo.isdir():
- self.makedir(tarinfo, targetpath)
- elif tarinfo.isfifo():
- self.makefifo(tarinfo, targetpath)
- elif tarinfo.ischr() or tarinfo.isblk():
- self.makedev(tarinfo, targetpath)
- elif tarinfo.islnk() or tarinfo.issym():
- self.makelink(tarinfo, targetpath)
- elif tarinfo.type not in SUPPORTED_TYPES:
- self.makeunknown(tarinfo, targetpath)
- else:
- self.makefile(tarinfo, targetpath)
-
- if set_attrs:
- self.chown(tarinfo, targetpath)
- if not tarinfo.issym():
- self.chmod(tarinfo, targetpath)
- self.utime(tarinfo, targetpath)
-
- #--------------------------------------------------------------------------
- # Below are the different file methods. They are called via
- # _extract_member() when extract() is called. They can be replaced in a
- # subclass to implement other functionality.
-
- def makedir(self, tarinfo, targetpath):
- """Make a directory called targetpath.
- """
- try:
- # Use a safe mode for the directory, the real mode is set
- # later in _extract_member().
- os.mkdir(targetpath, 0o700)
- except EnvironmentError as e:
- if e.errno != errno.EEXIST:
- raise
-
- def makefile(self, tarinfo, targetpath):
- """Make a file called targetpath.
- """
- source = self.fileobj
- source.seek(tarinfo.offset_data)
- target = bltn_open(targetpath, "wb")
- if tarinfo.sparse is not None:
- for offset, size in tarinfo.sparse:
- target.seek(offset)
- copyfileobj(source, target, size)
- else:
- copyfileobj(source, target, tarinfo.size)
- target.seek(tarinfo.size)
- target.truncate()
- target.close()
-
- def makeunknown(self, tarinfo, targetpath):
- """Make a file from a TarInfo object with an unknown type
- at targetpath.
- """
- self.makefile(tarinfo, targetpath)
- self._dbg(1, "tarfile: Unknown file type %r, " \
- "extracted as regular file." % tarinfo.type)
-
- def makefifo(self, tarinfo, targetpath):
- """Make a fifo called targetpath.
- """
- if hasattr(os, "mkfifo"):
- os.mkfifo(targetpath)
- else:
- raise ExtractError("fifo not supported by system")
-
- def makedev(self, tarinfo, targetpath):
- """Make a character or block device called targetpath.
- """
- if not hasattr(os, "mknod") or not hasattr(os, "makedev"):
- raise ExtractError("special devices not supported by system")
-
- mode = tarinfo.mode
- if tarinfo.isblk():
- mode |= stat.S_IFBLK
- else:
- mode |= stat.S_IFCHR
-
- os.mknod(targetpath, mode,
- os.makedev(tarinfo.devmajor, tarinfo.devminor))
-
- def makelink(self, tarinfo, targetpath):
- """Make a (symbolic) link called targetpath. If it cannot be created
- (platform limitation), we try to make a copy of the referenced file
- instead of a link.
- """
- try:
- # For systems that support symbolic and hard links.
- if tarinfo.issym():
- os.symlink(tarinfo.linkname, targetpath)
- else:
- # See extract().
- if os.path.exists(tarinfo._link_target):
- os.link(tarinfo._link_target, targetpath)
- else:
- self._extract_member(self._find_link_target(tarinfo),
- targetpath)
- except symlink_exception:
- if tarinfo.issym():
- linkpath = os.path.join(os.path.dirname(tarinfo.name),
- tarinfo.linkname)
- else:
- linkpath = tarinfo.linkname
- else:
- try:
- self._extract_member(self._find_link_target(tarinfo),
- targetpath)
- except KeyError:
- raise ExtractError("unable to resolve link inside archive")
-
- def chown(self, tarinfo, targetpath):
- """Set owner of targetpath according to tarinfo.
- """
- if pwd and hasattr(os, "geteuid") and os.geteuid() == 0:
- # We have to be root to do so.
- try:
- g = grp.getgrnam(tarinfo.gname)[2]
- except KeyError:
- g = tarinfo.gid
- try:
- u = pwd.getpwnam(tarinfo.uname)[2]
- except KeyError:
- u = tarinfo.uid
- try:
- if tarinfo.issym() and hasattr(os, "lchown"):
- os.lchown(targetpath, u, g)
- else:
- if sys.platform != "os2emx":
- os.chown(targetpath, u, g)
- except EnvironmentError as e:
- raise ExtractError("could not change owner")
-
- def chmod(self, tarinfo, targetpath):
- """Set file permissions of targetpath according to tarinfo.
- """
- if hasattr(os, 'chmod'):
- try:
- os.chmod(targetpath, tarinfo.mode)
- except EnvironmentError as e:
- raise ExtractError("could not change mode")
-
- def utime(self, tarinfo, targetpath):
- """Set modification time of targetpath according to tarinfo.
- """
- if not hasattr(os, 'utime'):
- return
- try:
- os.utime(targetpath, (tarinfo.mtime, tarinfo.mtime))
- except EnvironmentError as e:
- raise ExtractError("could not change modification time")
-
- #--------------------------------------------------------------------------
- def next(self):
- """Return the next member of the archive as a TarInfo object, when
- TarFile is opened for reading. Return None if there is no more
- available.
- """
- self._check("ra")
- if self.firstmember is not None:
- m = self.firstmember
- self.firstmember = None
- return m
-
- # Read the next block.
- self.fileobj.seek(self.offset)
- tarinfo = None
- while True:
- try:
- tarinfo = self.tarinfo.fromtarfile(self)
- except EOFHeaderError as e:
- if self.ignore_zeros:
- self._dbg(2, "0x%X: %s" % (self.offset, e))
- self.offset += BLOCKSIZE
- continue
- except InvalidHeaderError as e:
- if self.ignore_zeros:
- self._dbg(2, "0x%X: %s" % (self.offset, e))
- self.offset += BLOCKSIZE
- continue
- elif self.offset == 0:
- raise ReadError(str(e))
- except EmptyHeaderError:
- if self.offset == 0:
- raise ReadError("empty file")
- except TruncatedHeaderError as e:
- if self.offset == 0:
- raise ReadError(str(e))
- except SubsequentHeaderError as e:
- raise ReadError(str(e))
- break
-
- if tarinfo is not None:
- self.members.append(tarinfo)
- else:
- self._loaded = True
-
- return tarinfo
-
- #--------------------------------------------------------------------------
- # Little helper methods:
-
- def _getmember(self, name, tarinfo=None, normalize=False):
- """Find an archive member by name from bottom to top.
- If tarinfo is given, it is used as the starting point.
- """
- # Ensure that all members have been loaded.
- members = self.getmembers()
-
- # Limit the member search list up to tarinfo.
- if tarinfo is not None:
- members = members[:members.index(tarinfo)]
-
- if normalize:
- name = os.path.normpath(name)
-
- for member in reversed(members):
- if normalize:
- member_name = os.path.normpath(member.name)
- else:
- member_name = member.name
-
- if name == member_name:
- return member
-
- def _load(self):
- """Read through the entire archive file and look for readable
- members.
- """
- while True:
- tarinfo = self.next()
- if tarinfo is None:
- break
- self._loaded = True
-
- def _check(self, mode=None):
- """Check if TarFile is still open, and if the operation's mode
- corresponds to TarFile's mode.
- """
- if self.closed:
- raise IOError("%s is closed" % self.__class__.__name__)
- if mode is not None and self.mode not in mode:
- raise IOError("bad operation for mode %r" % self.mode)
-
- def _find_link_target(self, tarinfo):
- """Find the target member of a symlink or hardlink member in the
- archive.
- """
- if tarinfo.issym():
- # Always search the entire archive.
- linkname = os.path.dirname(tarinfo.name) + "/" + tarinfo.linkname
- limit = None
- else:
- # Search the archive before the link, because a hard link is
- # just a reference to an already archived file.
- linkname = tarinfo.linkname
- limit = tarinfo
-
- member = self._getmember(linkname, tarinfo=limit, normalize=True)
- if member is None:
- raise KeyError("linkname %r not found" % linkname)
- return member
-
- def __iter__(self):
- """Provide an iterator object.
- """
- if self._loaded:
- return iter(self.members)
- else:
- return TarIter(self)
-
- def _dbg(self, level, msg):
- """Write debugging output to sys.stderr.
- """
- if level <= self.debug:
- print(msg, file=sys.stderr)
-
- def __enter__(self):
- self._check()
- return self
-
- def __exit__(self, type, value, traceback):
- if type is None:
- self.close()
- else:
- # An exception occurred. We must not call close() because
- # it would try to write end-of-archive blocks and padding.
- if not self._extfileobj:
- self.fileobj.close()
- self.closed = True
-# class TarFile
-
-class TarIter(object):
- """Iterator Class.
-
- for tarinfo in TarFile(...):
- suite...
- """
-
- def __init__(self, tarfile):
- """Construct a TarIter object.
- """
- self.tarfile = tarfile
- self.index = 0
- def __iter__(self):
- """Return iterator object.
- """
- return self
-
- def __next__(self):
- """Return the next item using TarFile's next() method.
- When all members have been read, set TarFile as _loaded.
- """
- # Fix for SF #1100429: Under rare circumstances it can
- # happen that getmembers() is called during iteration,
- # which will cause TarIter to stop prematurely.
- if not self.tarfile._loaded:
- tarinfo = self.tarfile.next()
- if not tarinfo:
- self.tarfile._loaded = True
- raise StopIteration
- else:
- try:
- tarinfo = self.tarfile.members[self.index]
- except IndexError:
- raise StopIteration
- self.index += 1
- return tarinfo
-
- next = __next__ # for Python 2.x
-
-#--------------------
-# exported functions
-#--------------------
-def is_tarfile(name):
- """Return True if name points to a tar archive that we
- are able to handle, else return False.
- """
- try:
- t = open(name)
- t.close()
- return True
- except TarError:
- return False
-
-bltn_open = open
-open = TarFile.open
diff --git a/pipenv/patched/notpip/_vendor/distlib/compat.py b/pipenv/patched/notpip/_vendor/distlib/compat.py
index c316fd973a..1fe3d225ac 100644
--- a/pipenv/patched/notpip/_vendor/distlib/compat.py
+++ b/pipenv/patched/notpip/_vendor/distlib/compat.py
@@ -22,7 +22,6 @@
from types import FileType as file_type
import __builtin__ as builtins
import ConfigParser as configparser
- from ._backport import shutil
from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit
from urllib import (urlretrieve, quote as _quote, unquote, url2pathname,
pathname2url, ContentTooShortError, splittype)
@@ -48,17 +47,18 @@ def quote(s):
from itertools import ifilter as filter
from itertools import ifilterfalse as filterfalse
- _userprog = None
- def splituser(host):
- """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'."""
- global _userprog
- if _userprog is None:
- import re
- _userprog = re.compile('^(.*)@(.*)$')
+ # Leaving this around for now, in case it needs resurrecting in some way
+ # _userprog = None
+ # def splituser(host):
+ # """splituser('user[:passwd]@host[:port]') --> 'user[:passwd]', 'host[:port]'."""
+ # global _userprog
+ # if _userprog is None:
+ # import re
+ # _userprog = re.compile('^(.*)@(.*)$')
- match = _userprog.match(host)
- if match: return match.group(1, 2)
- return None, host
+ # match = _userprog.match(host)
+ # if match: return match.group(1, 2)
+ # return None, host
else: # pragma: no cover
from io import StringIO
@@ -68,7 +68,7 @@ def splituser(host):
import builtins
import configparser
import shutil
- from urllib.parse import (urlparse, urlunparse, urljoin, splituser, quote,
+ from urllib.parse import (urlparse, urlunparse, urljoin, quote,
unquote, urlsplit, urlunsplit, splittype)
from urllib.request import (urlopen, urlretrieve, Request, url2pathname,
pathname2url,
@@ -88,6 +88,7 @@ def splituser(host):
from itertools import filterfalse
filter = filter
+
try:
from ssl import match_hostname, CertificateError
except ImportError: # pragma: no cover
@@ -311,10 +312,8 @@ def python_implementation():
return 'IronPython'
return 'CPython'
-try:
- import sysconfig
-except ImportError: # pragma: no cover
- from ._backport import sysconfig
+import shutil
+import sysconfig
try:
callable = callable
@@ -616,18 +615,15 @@ def clear(self):
try:
from importlib.util import cache_from_source # Python >= 3.4
except ImportError: # pragma: no cover
- try:
- from imp import cache_from_source
- except ImportError: # pragma: no cover
- def cache_from_source(path, debug_override=None):
- assert path.endswith('.py')
- if debug_override is None:
- debug_override = __debug__
- if debug_override:
- suffix = 'c'
- else:
- suffix = 'o'
- return path + suffix
+ def cache_from_source(path, debug_override=None):
+ assert path.endswith('.py')
+ if debug_override is None:
+ debug_override = __debug__
+ if debug_override:
+ suffix = 'c'
+ else:
+ suffix = 'o'
+ return path + suffix
try:
from collections import OrderedDict
diff --git a/pipenv/patched/notpip/_vendor/distlib/database.py b/pipenv/patched/notpip/_vendor/distlib/database.py
index 0a90c300ba..f486994416 100644
--- a/pipenv/patched/notpip/_vendor/distlib/database.py
+++ b/pipenv/patched/notpip/_vendor/distlib/database.py
@@ -132,29 +132,35 @@ def _yield_distributions(self):
r = finder.find(entry)
if not r or r.path in seen:
continue
- if self._include_dist and entry.endswith(DISTINFO_EXT):
- possible_filenames = [METADATA_FILENAME,
- WHEEL_METADATA_FILENAME,
- LEGACY_METADATA_FILENAME]
- for metadata_filename in possible_filenames:
- metadata_path = posixpath.join(entry, metadata_filename)
- pydist = finder.find(metadata_path)
- if pydist:
- break
- else:
- continue
+ try:
+ if self._include_dist and entry.endswith(DISTINFO_EXT):
+ possible_filenames = [METADATA_FILENAME,
+ WHEEL_METADATA_FILENAME,
+ LEGACY_METADATA_FILENAME]
+ for metadata_filename in possible_filenames:
+ metadata_path = posixpath.join(entry, metadata_filename)
+ pydist = finder.find(metadata_path)
+ if pydist:
+ break
+ else:
+ continue
- with contextlib.closing(pydist.as_stream()) as stream:
- metadata = Metadata(fileobj=stream, scheme='legacy')
- logger.debug('Found %s', r.path)
- seen.add(r.path)
- yield new_dist_class(r.path, metadata=metadata,
- env=self)
- elif self._include_egg and entry.endswith(('.egg-info',
- '.egg')):
- logger.debug('Found %s', r.path)
- seen.add(r.path)
- yield old_dist_class(r.path, self)
+ with contextlib.closing(pydist.as_stream()) as stream:
+ metadata = Metadata(fileobj=stream, scheme='legacy')
+ logger.debug('Found %s', r.path)
+ seen.add(r.path)
+ yield new_dist_class(r.path, metadata=metadata,
+ env=self)
+ elif self._include_egg and entry.endswith(('.egg-info',
+ '.egg')):
+ logger.debug('Found %s', r.path)
+ seen.add(r.path)
+ yield old_dist_class(r.path, self)
+ except Exception as e:
+ msg = 'Unable to read distribution at %s, perhaps due to bad metadata: %s'
+ logger.warning(msg, r.path, e)
+ import warnings
+ warnings.warn(msg % (r.path, e), stacklevel=2)
def _generate_cache(self):
"""
diff --git a/pipenv/patched/notpip/_vendor/distlib/locators.py b/pipenv/patched/notpip/_vendor/distlib/locators.py
index 0c7d639143..c78bc9e23a 100644
--- a/pipenv/patched/notpip/_vendor/distlib/locators.py
+++ b/pipenv/patched/notpip/_vendor/distlib/locators.py
@@ -633,7 +633,7 @@ def _prepare_threads(self):
self._threads = []
for i in range(self.num_workers):
t = threading.Thread(target=self._fetch)
- t.setDaemon(True)
+ t.daemon = True
t.start()
self._threads.append(t)
diff --git a/pipenv/patched/notpip/_vendor/distlib/markers.py b/pipenv/patched/notpip/_vendor/distlib/markers.py
index 923a832b2a..9dc6841033 100644
--- a/pipenv/patched/notpip/_vendor/distlib/markers.py
+++ b/pipenv/patched/notpip/_vendor/distlib/markers.py
@@ -13,19 +13,29 @@
# as ~= and === which aren't in Python, necessitating a different approach.
import os
+import re
import sys
import platform
from .compat import string_types
from .util import in_venv, parse_marker
+from .version import NormalizedVersion as NV
__all__ = ['interpret']
+_VERSION_PATTERN = re.compile(r'((\d+(\.\d+)*\w*)|\'(\d+(\.\d+)*\w*)\'|\"(\d+(\.\d+)*\w*)\")')
+
def _is_literal(o):
if not isinstance(o, string_types) or not o:
return False
return o[0] in '\'"'
+def _get_versions(s):
+ result = []
+ for m in _VERSION_PATTERN.finditer(s):
+ result.append(NV(m.groups()[0]))
+ return set(result)
+
class Evaluator(object):
"""
This class is used to evaluate marker expessions.
@@ -70,9 +80,18 @@ def evaluate(self, expr, context):
lhs = self.evaluate(elhs, context)
rhs = self.evaluate(erhs, context)
+ if ((elhs == 'python_version' or erhs == 'python_version') and
+ op in ('<', '<=', '>', '>=', '===', '==', '!=', '~=')):
+ lhs = NV(lhs)
+ rhs = NV(rhs)
+ elif elhs == 'python_version' and op in ('in', 'not in'):
+ lhs = NV(lhs)
+ rhs = _get_versions(rhs)
result = self.operations[op](lhs, rhs)
return result
+_DIGITS = re.compile(r'\d+\.\d+')
+
def default_context():
def format_full_version(info):
version = '%s.%s.%s' % (info.major, info.minor, info.micro)
@@ -88,6 +107,9 @@ def format_full_version(info):
implementation_version = '0'
implementation_name = ''
+ ppv = platform.python_version()
+ m = _DIGITS.match(ppv)
+ pv = m.group(0)
result = {
'implementation_name': implementation_name,
'implementation_version': implementation_version,
@@ -98,8 +120,8 @@ def format_full_version(info):
'platform_system': platform.system(),
'platform_version': platform.version(),
'platform_in_venv': str(in_venv()),
- 'python_full_version': platform.python_version(),
- 'python_version': platform.python_version()[:3],
+ 'python_full_version': ppv,
+ 'python_version': pv,
'sys_platform': sys.platform,
}
return result
diff --git a/pipenv/patched/notpip/_vendor/distlib/scripts.py b/pipenv/patched/notpip/_vendor/distlib/scripts.py
index 1ac01dde51..913912c7b8 100644
--- a/pipenv/patched/notpip/_vendor/distlib/scripts.py
+++ b/pipenv/patched/notpip/_vendor/distlib/scripts.py
@@ -14,7 +14,7 @@
from .compat import sysconfig, detect_encoding, ZipFile
from .resources import finder
from .util import (FileOperator, get_export_entry, convert_path,
- get_executable, in_venv)
+ get_executable, get_platform, in_venv)
logger = logging.getLogger(__name__)
@@ -170,6 +170,11 @@ def _get_shebang(self, encoding, post_interp=b'', options=None):
sysconfig.get_config_var('BINDIR'),
'python%s%s' % (sysconfig.get_config_var('VERSION'),
sysconfig.get_config_var('EXE')))
+ if not os.path.isfile(executable):
+ # for Python builds from source on Windows, no Python executables with
+ # a version suffix are created, so we use python.exe
+ executable = os.path.join(sysconfig.get_config_var('BINDIR'),
+ 'python%s' % (sysconfig.get_config_var('EXE')))
if options:
executable = self._get_alternate_executable(executable, options)
@@ -379,7 +384,8 @@ def _get_launcher(self, kind):
bits = '64'
else:
bits = '32'
- name = '%s%s.exe' % (kind, bits)
+ platform_suffix = '-arm' if get_platform() == 'win-arm64' else ''
+ name = '%s%s%s.exe' % (kind, bits, platform_suffix)
# Issue 31: don't hardcode an absolute package name, but
# determine it relative to the current package
distlib_package = __name__.rsplit('.', 1)[0]
diff --git a/pipenv/patched/notpip/_vendor/distlib/t32.exe b/pipenv/patched/notpip/_vendor/distlib/t32.exe
index 8932a18e4596952373a38c60b81b7116d4ef9ee8..31baab6190610b37f70732602175f954c2832ed2 100644
GIT binary patch
delta 17097
zcmeHudt8)N_wVxz!$lngOly36-|D
z%1Tqq%*rH{$`ryt%?sK^QY(62s%==|n<*(W=ewUltM~lQ=lpg4JA6KC_S$Q&eP3(s
zwby>?x^K7h5uKUlD$#L1dFSk?o^4$Bh7EZ*I06v(XN22%<
zKIscZWCEYe&moKX0X??&;J6((IIdq+LQHsR5YL(KS7T{A$Iaxc5~6zycg$bz0O3h;
zgCE3ONt9?1-%3V`9^yNyt3-?VDrvy;VtHI+KaO*q2(2vdyzTK=Z0S8ma`b!8KnJ?DFJ(_o)^Dg6D&N$~k;0zZyZREVTAOAhME*>}NTu+WuoYFT2ZK|{F
z0P`do=ka*#qCAhYlTJdnES(ZXX8I23o8-;f#5~8@`CK|8j8yrK;Oj`8Z$eOAjKFKP
z^B(6-dXC)iHSuSO%rB;Ip10&UtCq*=%_Xz^OuhZQWPuVEO5XC*3@z!;aqi6a{MoZk
z)lYR_cU_lQ?ZZ9J0L85eX9pdRCC$$-uXXgd+Ln_*|G`7bgeGpfJJ=xq3>Idt?{bc-
z%9p{`W{yrZ|><5AfRBRNl4KkzAP&0dbyjy2c62;jJ#Wr-s>
z#fgftq$cFtnE|=hJjIpV+=`5(NKcqq=tfbi|2=4Ptt}D*tZ*d7TWHC!PLC%)zcM39
zVzCz^DYn>qSUq7%@MFFb*UBVGvC|j5wTTq9wp3KK&_A)kYo1Fgh4gSzwlYG^1>Twx
z)U;&_d>6oH&p@N8xfXi4z$+dZ=++aK4zAl7N!cZw9|miff^1N^8|}GO8NltgJYU)W
zz|i|4y(vuLD~}^7(%p{MX43s#9#5k=LLJYh*F^r#lFeaF}zh(J7Rn{C>CnMh@gD1}A}yp1@L
zAW|I2XCTm%APsIq_6;-l(KImSLbN@XAa|sxM>M~UwDm|GU&!cmE`Ms=O9jnQJQr
zX%fBgr^i$IRAkT*f7gk~B1lDuJ-|ZK`*iEXec{Npoe;Wz?N2hdXJ~X_ce(SYBA1DC
z-*!X=9r0roZ;}l?BOkefB@$FBKv7#%;pb`)<_6NkiF
z=nxD7t5)RFCprhBip!Xo8?>p`CJkikCPAsUof8r?<*FCC8b$UXXkYJMi6gcNJfC&2
z79Ma^e(gUS~jtZ@B09{e5D{saSZIp4d#aaQX
zJ!nR59u|NECiGbzCYYbf^6OY(XrG0A8aTk;yL2Jsx4<}l3JL06IVmZK&1Uu=u)~MG
zK#QJ3SA}2JdY)|>O0CvhnhI;^Iw8%a^&p?QwhtCWAIPkaQ}lcNwWJ7`Xo<}n4D+ui
zzx0lfoP%v`!BAwnDPnk9L*06U~
zX0}^wtabWMoG^>FofeNX7~`lyPY?;RGa)49Yt*QRP}5nySm=7U2QqJUI|E=?Lueuj
z?cuX3q_ly26qL;WhkPF-FPXze7=$84Ef^IZ{cWrr#uP^P;I!!;a27lMPI}rVV3Aiz
z1O~@<70v)FRYB?VF=8tXjEC83uFMR!!01b?bY23RUMohp0VS>z{TeenVPrfM>juNz
zO20#+^}cMhg=A}R|0o*-r@Yn~X{DhkakqQziiMsaXM^MVpGsvkNG|3QsmttigwW4%
z`ap(z($l0zp9KCg8QN!1LWLUL`nku5^}Z=7BRNO7drW{kFn;hzt~{$4eNnQs&sa$$
zs&>!qBht`kX`c;g92az?oh>so947)9-*>uXIJT&p?E*Q_cU{P@Myz2h)fza?=D|E@
zRvO6(iJrQE34KSo$2rh0nWgi6UyGgtbLoG;jsw&AW}83Fio(F8bfl2D8)wgI3(ch$
z7y}&!S|cYxdy3i_1@$#t{79~TXA3zV5;eHRI+C-CEi`i^IP-U%7=ww_I%Dl(YlVuH
z(bVn&!iR?QCrD)IAbtlihmMFoIhx~24J+j+clgpxNIWW5oraK#(4kp=@Tk^IlyIeJ
zx2>nEAsz=pF{XhdSCAW=ovx11RpGAMU<>^^gYB7QO_rA*ESVBlEjz-BNoVNr-bc_^
zenGvLkMCijuz&f1r4$pNx81i44(@tlQk0Xxodx
zjGWOUEgPe;P{RZ=J}O$2l|hQ4O#Bn%^(Yx%N}8jFh@!KJI68rUgba*U^SekvbTZb=
z6`j%NQ&`aaih|BtV~?ete&b{G{6aD_W-$LGc_wBwzr6Z%Od=n(
zA&ZG-K3pW-Z1F0eirHeEl2$s}N__jLquQwcOZ)A>{z2K@K6O9(-O7-POC2lhB0HSB
z4!Vq7=&uxwoJ#y-C-Sw#8as^dLCRu>`A%fkj=1yV$yc$P`ESUAxPBSUOBObTT`)pe3#jPtfMN%8?#yC+tuQnUG-QeaN~5Mek9+quK67
zH0&UsCB%hh&E>dVbU0+=;WhSRNuHP+U-~5N?&ep
z_JN+`pg2YhiAlY=KB(LPU)JtVAJULTiIXM2&*HesV%{#Y(q80j;=}y=L^@z|=Ff7D
zv${KIfAndkdm)w>%6C}uX{{CD0VLked{_-tD=5fzN<*WF{Ke^6W@pQ
zOd8Fb$i$@NoafsAHl6F3g9y^TXBcya1L&_q?<;Co3Wg66wTIfwY^+bS6aJP?RSS)9
z_OillhWwY3+N7M44Y0$|u>gBNS^$HUPn83^3Bfd5AWWPOU54{ogl;hC0DBDW7=rda
zVJji%yN)s~BZXMgR<@?+=5=!g(2IXi4YCUKjBiMPmjSWxV;DG7j-yxOB{jyT7
z2?upWW{b|*>h!ZvHCSl@e%gk+Gq1Y?+&wCCu39G17ud#NL+4X>=2ft}R<&}BzaQgI
zA3|;3R$=9|9Qm9Ei4~aEUx&Yk|59jDf@Kd1_}owjN@x
z))N+rGN^l5b?-qFOXwFJ=iEMBzuOybi
zS-6S3Fjzf&4qN$YK3MrH3QxtVR`hXs1p0u+u`5Q4UNC7vm8W3}cb*{k1`jIvYB~-|1&lwe@Xw`YL9uc=XDiOm
zpnHfsH7T?VT(i7-=~0EkYHB5
z=X>~N6D2t3e9kK*T8fM1rD2QvtZ(s>tpYiXt|s}z2PD*B88}D0jivN+h_UO07^)WJ
zo{9~h>=|nSPGvAY(9Z>?alOvTTb}55-e@%S}(^r;*b11b#DlEnOjb>9+?1
zF_Q1oWr~6e>~z|=c%+$|VZy}(@H0STHglr^>j2NA%+3inBc^-1Nv2^a&ymIWT|izo
ztma=N{ftX-m~AvpSesiVAkJ1;yZyvv9){lEGe`3^o)8sFOS|D+07Ip4Dtcn_0LFlhH~s-
zG@DO<{^|Z=w~1ZzTi}ee&|>QG)VmD(nMgbP+V4>-{LB1CUq-+X;Y@k8Dc9;&?I(>H
zFN#@5g9vKs7JphXP8F(z*@lyN@ZM4vr1yWOYZi7&z7CBl56lb0LnJ`y65kB%9W<~I8u8v9K)r21#9vp{C`L4!bPfmcd(Mp5Kpt1RWwQQZuD7C}YLR>*=G-2uM^!wO*^v{V2;r3&k1E4!Sp>=hhLbGYFspv&0`S9kaD?MZ
zo!xDNELl-WXTabJ6k5A>0Ok`$zJ+z4(TW>a_8;u@Xd;zaW_~|8mo+Ns4$MHk!ZQ|E
z3VR@(g$ntQS2|TdFffQz7EzAV^V>=BxUi^Z*7`)Tr^2tThn4PXz+@G-!1D!pWn6Me
zKworp?_XGAc9RJz>%i)`$0WgQ;k2|eX7`v>Z!XK5Q+ji0-dwsjm+H-#y}4L#F2kD(
z_U5v@IgvLv({LaFyCX+0H$Fu#m`MPefNOpy=U#0q_c)?4#s>=L@yd4|J@F}VjJNtP
zV0CC3xiCH@qKU09yC3wR%6sgtI=dc1d6fiD(DNrr_Jo9iY>RfWz1hi@y3yARv|);w
zt59VD+vVMhT0$m_K%k<2Lc0HUu-Trq62I&Td^4Gxt>p(2ceV=Q&${fXeOQN>Oqeb5
zmT+!gC4+LtM-BMKXD!x*SE$0pVcJA>U5_kEd)jayv3}TuT7MX(WL0Q`@gx+#IB967>&g^ir>vv}7I&SS4vEXS{jzN4bY!j)@9e^G#<&Fr+U<2)Nc|49$P$DCg
zh?1x&!}v{P?36@**19LGl@w3u7cu-8w7w%BiZe*(TZ3EmvN%k%o%}W>ntVJ(1`p^r
z{Ej0DQ)2`?n&eH5j@^?F1JS|O)ZG~aTWZw5EgDmvTvygFS5PdwEtu9aK%9MXPj@L02fx?`|$Pd9RDq})1Z!3g9m)IJBa+s*kgwJgHSt~sH$Vm8LQFJUg2uUxU}D%|B!
zkV_VC`G>pgf7X&tlNoZt{!;Sdk_nCYnE;;4$Lop4(xwCANrgf&MbaWQPA4ftHRjkpGP@X^xrFR{Bx3%ujPx$Gx^DiPcPm-)2R(80!-I7
zGSj{)7%?k}{;nfR|2yJPul&U}l6;##g@2bQrjH2h{TKPY3uMuBY2ORbQLkrs?OC)#
z>n@P@riTrk@hL3R$IubPZCxE*&areGQ)X?VGoP*pR-tXPxFhqdwi%z28`EP_3%g5`
z>2y{a*@nC5ikJpJoQqw~V44YHBbUfJpiM+OBbUEPo|`dj$fU~bq=2C(UEc@!)Ww|Y
zRhNJ5j*SJxyQ%*7sjW8h^NiSji<($7EEeXPD2Zot9|1kvYjy6j;IuKU~xZV`ez-Bmo5j_yUqzAP?v;}GRXfTphz8sg!;$YjAD+5tD*-Hh8}d-)Y-Ed3U2
zUa_M;3Mj7hEKGTAp%_-3#~~xc05qg-_8WW$d34U9ULBBWEL>0Kehi*#;x~{-=el_p
z`D1QQ)}e=)tqsN*GPI!m0!4ULf(L?jxa-&M!6^&VqHaazO;lIj4^5`Ph0J&5T=(G7
zOrI%X{>Y)X$-0L>{6|L*&6|SA^6q)#BeT$i-FFrqz#{0XllPY#$FE=Ez!^16&0Yf4
zT@c{9+vSX**~qZnkv(8#vpbAgmS0&OpcgFll4zRT&DQe)8-2SGD|pdek6mFqLN=5|
zkXH(OM|=x9Yq1`wS1j6&$NK;wZ5D^-!$B1(2o_UZuuSbp8
zAeJGFPI%A^{k4JU<|hq`2E`j>J_aYs-O!fhS`~~atj;M}(#MKTWKo%YMp8C^fT*c~
zyg6UfV>tv25z1>@$hG-D#sBms?pv?Hb*d=e1WyUk-l3Er+bzUu(q1$k!mEFTh3w`&
zQXvW}slNV5m`L0(g5$_LN297^?UQ(ZI4ORtitj~43zi~IwroKQpGxu`m*eP@)kakB
ze7u$Cw^l#1=pkM_XDsTK)Q6MbiVOHjBxmsm(FbG5>cy$JBfhoR0{hsxc(N&FyKuLA
zcbS=c$6@Ba1*AL8+*$wud=2RF1WvX45qB$9PsMWK|A163i7M&D?hD5r^Qf38
zFjLPw3JbmZ0TU!d*kc4ua*G8mf<8fwDp|NN{$6+VE7;b|hGTn7V*zl|7Rs`>={I~S|%D_C>GN~~?@ts@58}s@8BZG#op|wS
zd7p=IJm%+D_&~l!TJtP)Ci&*+{)6^aVk47Tq?dCIz%643d;!c9PI&%|*|X~L#43ky
zm5~HjL@yCE@eI2}hKb2&*D%8h9DH71ED$sfZZ$-5#;kh0sZ#$0HbF_-j}H_e&q`Nj
zcs0Ob*|~$f?TRaz1cIGM>^F;*AeijvXQk;3hl4l;Gwe3af}TJ*;9hSV&3O-(f2c%$
z19XCe?b4<^amR97BVg|RAeK0s4QZzaHd7mGDZdQQKa3H
zE&=*a!x_bYOWrJt6-DeNkJt3;MZn;df0&}O{`^+byWEoSLn2$l0^GW7B~|P)%%ZCI
z;}2FEeWJRgd^F$F8>;Z7reoyHGjZW-+2e4f!;fF*S61u%*)ATNM@O6^{IfBMc_=D1
z8uBZr@s33H%?6K{5BAd^`)RV`HZhYJpN$zbp@Quvi!yVrLI2Z!7P9?JCZ*3t;GJXJ
zv(x!#^82%`2wFC;P2~HMyKBb>ut7O}>Bl6eBEl~K+q&~8u~$qGDL*4eD#q|)(p52t
zmym($V);EJb6rBJ@fi#S;ct8cAV@)GXIqbMoNd7j_CjN=k>X+Tn3UA4iw&^WXjw*!
z%+$gCnBX`&6;4D+BY`X0Jz>pkwUCAidG-j0&kKm!t>;&gCGHIVJ95N5I_&li=7P9-
zd+23g-ctq|`P|vCum>d_uih`&PHsH+gtAqSmH7;T1$?QHK%YV>HoPL1&H~oRA<)m!
zT%d05+8hrOywDip$@WTn3G-t9ZhyP(pZPz$g4xp#@Qa&HX2-Q6F;n&y++n-|oZ(jg
zpKN${8}4>1eU-H$R1@U>mh){|1X3dxh#|G@aFO!$QGJ(dA^d*sCRa;b-5peLC4S7hUw*$vP}z8_nYY$F(z$UMJ{K5L)n8wQfV9w8P}wIq!W%NXL~TZ)?I*k?Z7()v
zw*L0faa_G;xa$!utIRM6JuDo{t+F58=QrK5)L?
zc@YcX=RV0+=TBd^q>^n}T?@`j9_M8j1{|k?eqGy%2rl;ITsIS`d_G!AUVv@G6)Q3X
z>QRD*gew+%0*}1^03Jg7g6@^F!{pNQb9pn-kpV%hm%qCBh&)E-lT+kXKP{TFwUdRL
zj^SJzws{23rGm{$Oyl{@F8qdXnTN;6C%3Tay}U&^d>_;2kCu0b+lE`|3ygp(h9l8R
zD}lpxvvzyabQSSzi61=E%i(c=+wwQ=jQ`?Rf;)oD-r8eu$lq%C|E&gd`~Tj48>!k_
zAew%Hv~M+r?On!>O%|k6csxfq*cMONHZpqKFZ@qL_Cg`AC0ky29KYfh)%-Wr`WI7q
zWNa^u53|=|q5tdob_QvDNrxK}zx}bu1IJO`iF=)`(VsonE@a~=71AWirsP?cLYsv&l{N{fl%5n)C9M_G
zezZnNwe*0HrqjJhT_?<*l4WN4vOvqAn}sxsRtjl0Ef-P?T_vPbs8dL%(Z_{!CY>*&
zb7+B(7SgFgYNI(qx`1X1X)!ejse`J7bU7U+q$_DM(h@d^!==npMlkZ>GKNY7bS*=9
z0ez04x3G$y!|W1I+XR&PptMCmw=wif0o~5f(*nAKp~nSuH$&eS(7g-9W9_O4Alx~D?^6~
zCK`=vKUsS>x3}yIB0y&bQ8wIo!vIe
zD9e<3)dGQHg;zA{wd4}Dmor%Jo7K&kw&aRhxG`$@9y}N4sSR^#8IuqQB!kxSL6p#RtvYMx-~9
z-B^a71As3`!k!fHjo-t*iZ0&MKY0$=a3RV=k^x8uC;_Q}BtSnv=s|LDPgV%ybbDB{
z%K>G8Z6xsZ`I63RM!)?v`r1!czaAaVfq)F7Zw$td5-_uxyz_b--$~BBJ{Z3pugj8H
zIeI>Tqy|t6I0cJih*ek+v(!N)U@M6N7CAjMEd^1`y;dQ(nXSOntxeww=Y-!YfC^3tD1CD5#TKx~Yy4bsmwWQdxt(
zQG1tA*9pIo<#0E{N1S4-=hCj-aI4`@vOE5LpV%%&IQ=NDRd{P`#~tcHeSsNQW?y^c
zv{~f2w|YfvW0T#u29phX4O)g5Cl)yABvr8y{`*0$$A?j8-ZF?R+exqeQG=3hU{`SH
z1(h+Y;Wk{wSZsTHuuohsv5zP2zzVrl+sK6d`B;}d`}gt{)f3;|%!e8aF`5EAMmgDs
zb%-to&^O5KcVY*ouol^i)&0-e%vPGc3(UBH^mlc&Ec*;sw_Qa4ZvQA2%CJz)Diq=Y
z?_YEgFyZq(viRL%k^V6G_q$KQ>zRGPWgY~-%WYnuIHk+N$Mo?(cs%&R*5dZ@DE-|x
zJYiCFijU#zg39KI@Dh8&A<Nqp}&xC(s_rtmF9e{dQe|2G|coJU6V
z{@3`u`u=$o?>{7sV2>dzw&HUr1r6xGE%~gCeOpd`KV**YE(>#)vohD74E%BxA<^cIPHggJoT=+$o@?Bo|!SAD1!@#o3OqY3=m
z)juB%@!@|UUB?lrs+QG8`G~T2k?i^z{BLA$eRE!*55JidV~M^*z7`L8G6G>i#lm+GaH+$Wiz
z-7bWJD|Zl0qY9qe?>UCUmf
ziZNUE?dij4yk2ny?W1-h>3=HH{@3k+F?K(GMB21$ELY}X-E&%nTf$X)FZ#j;K@pq*
zEFL=-V*zdKMmC03ofWH^T^@5#rJgCG@?+L4d$j%l1I8ClZt=%E$g8K~_%3q%R1BV7
zuAEAWXQR3`M$9{6aE)d?yBf@GF88Wtx3dM~`;^3;4#U!@PA5cNSchKn@s$^3dv2vf
zXug7H4|9u78^H7S=@D!{zCHbkUpnM**K+duXVduz!k-z84}OQ9aYH?hoaxVJkuT50
ziw3{+JZ
z7>2VGMkB!9gARY1^gdUJgK6ox%+M#__jtTt6K<+T1h;lY9u<=_=Z5sVUXC7iv5*A5
zbh}(3)d}GyXNX-)FOa_HN8urF>iLxZnuB2TskhLb;77zMU*F}J4in6*a143zypk^@
z=g&t59X5kc=j?FwW+i*cEtG5}Lz_m6a?43^lL|5a*PG(_$)usFAz&t!75ln?Y`XB&
z{fv!7T%3-RammG0EXb=DbFhHFT>O?lK+b%=B=89nHof}+<}otii`6JS{lx;2_^E2`
zmnxorfh_z=hA)D4e5FHh^72W&aen1VN0q_OjTflz+qG)su7y?iOvH(`VLclV>Gl1u#
z^LTDIkYj*mz^?!?MjHzl4#)t^0xSTm1grzR2-pv(1vCM!1IA#M?C&yWegg0#nRF#&
zKwP|;E1Cok;fR^*ykzF;s>}ix`-yLb6B2K(N{9-(|G#B8YPcN0$sjCu^#@nxMFq@T
zGJme)(M1c1^p8lA@<$mt@W+(cd5ac0mMmHjZ=1WYaKZc~@k{4B9FH!1B)(|wqYLI2
zR*P<}_vs&xz?To&nw*=FyXf%~v&EIViMu}h!O=zA14fXy{~X=39glgz>G)^0omC0p
zB=}CG&%GEk>2;^S&+UHRv}T|;{eF-)4IJuCqlSCa=SB!Ae~$FH8{?NOH*-2U8FDw)
zCs!jBh-TyvJF>$jd9&|LA^Yye23?(M<`CZTV02vDR5N+^ZdC7A^8TXj%OhQPqeZ5f
ztSwQ`d900~O@(I0FZ|6ZG?Sn^QKE<<#(_Bg&dxZ|(oz!L8OK*KZhi~l@5T5{*ohAt
zccRZ|-&*)=C&-WhyA+$=lsgd
zZD&`Z?!Uv>VfL?l6uNW&?-pp;H?WldU!(pXY1GFaXGF0$cfKP_ko$29ixw?;T+j|~
zvy6|3xFgP+`>12gq9wUdh6VE>TBl}B~*Sa1JyCr8P!*+tE!(=zo2Etwm{~Ty&yX&+auSg|E0ES
z3N$5}XEc4ZgS9(#VQGibR;9n5zRxhkC^wEaK4dI;#8`*_DB+|W$3h`CX^YfX)>jrM
z8!FSt46=u0d9sIPZ_3`0)yV!MyDjUIiRCNgCGv8)TV5sKDt|@3N4{TPE&otnCqF4~
zk~hm+P?kIJwiRHL_Jge
zy!tiu8TB{npVc?iye32wsj+FE)J(_HE!MuPJ*=(Qp3?g15_OqazAD{2I#JrywAQrF
zvdchIm7UahNIHlxM;$%p6yU#34VT;8j^FhiZ%JF-;4Wo$9XZ+I2T|
z9lB0kue6A?g0zx(X^*Fg^d`Mk-;jPOy^kT*kYZ39OonlWRfe^O=M8Te4jE1wt{Vmz
zrx_nJUNLqUN16_p&YL(3`}bmw%aB^6^Q2DcM(H-`4(SnTlk~du59wWOMh{t}%p|kP
zR>&%4U&~VD#qtyKXBCGO!<1u{)09=pI^{{_7bVKe${&^2m47JjD#fZ`Rk$ivm86oY
z)T+^{996EWKvkmJr20^GN5!d!sHJMPI$K?+UZ)mo25DrPM>UHzA8KkfUumvr{;j#D
zY1iD++|&4IdujV>BeZea6s=OL(;BrI+AQrvZLT(7J4^ex)}^h`R%y3rU(xQ;mb|4s
zpgp2((0-=9puMcUqvdpwx=XrmbU*5T(FLWIr9GGSYTCZEchl<9K1*v#yPkF{O`?y`
z%OJ8D`YHP9`p5LU_3!Av&|lI2q3_fWN)Ur9U
zMWZSn@iHN{OugtAEaw(@-_ag*{V
zVO?WY$tt62vTB-YE@WYws#%qaRjkw;)=bux9MB!neF}kEqTi)YF??&FhV90;jESZZ
zOpuDP$4JD|-qLrlud%W*vK?}r;;Q0~B3@YvTd_}hTysb}4y#m?_I29F>0zcQQ-4!}
zDcLmGG|VJ36`7Wri0N(9cP6gX+m^p{ozw}5cu)4BvP-2a(X7<$)tuH;>tfRr(+8#x
zNgtjrPgkd>rO!7QjTYl^W2do~X`(6LG|%KPm6%>KU19^<$Z;%^>MI>3&6XBQE2YP!
zKS@7<*2h5M9#$++ysfBFe4_YV@vY(&)w`-As*hEls7|Vs>NK@gy;^-*{gwI$=vyyM
zNw7w($=4LYYCWlWTC-krL~|U&aX?9Cr!7gVO52roIPGLwfIdZk&kzS4C^5cjeA8HK
z{J|JtI%MK@2#ds>6gKOgw2v%8_MGg7?4Hai-yq*6|61ND4_9au`HI&RZHn#6*OYse
zHOhMBAF6rk)tW}loD!{6w@-IgSClr%IM3)bK5KjdbG~5wxA707KNfSS$zXcOG#ATQ
zZraLbet_dxfHYc`Ei05gCR-|7E?X%pg^4;Z`$cwB)*w>1{+QtfJO8P0$gwEMINwKdvLv?sL}wawapY1_27O0;*iVqI@tlx~V{
zp>C~Cq7TtW=@;pp`Zf9*eUpB6`myx6hBb!8#utsh8@XB-9L#>MR4?l#PnS=Y6Zt{;
zLy9$uHx&bvIm*W|@VAwZsCwc^ny5aAeQs9=q{GKoA{J{s2j4Y^F=QfyJYp*W-PS4J!4%E`(-%Cj(3BGm}hOw}W*
z%{UMDtFGe&OjFNRFHmn%H>l64B^plas}0vFb)V?2>H4P)OZzSD4;*kk_5JlT^)~$z
zdY689dU^VbuoAzfUpM@2Sd?X4hTZNmX0wsCa9j$KFzH}u{XzMk*snlEv_hu{R)#BM
za7fFQ%amYf=eR5+Hk@_EG6y7}OtyCGH;Z|F{!vq{i9>T3XubuX)<7@w8Ej67Jt;-xK;g{eG|OegAp?dUIWC_S$Q&eO_zr
zwb#BWyA@J)E2PTAH&>hUydRyKd+h9{tVb~BO+(gkO
zvrZ#Dc6M{tD{Bkd*K
z_@hKG8Or}$Qz4nfuL@SAO-tc8-_h90@|HV-V0W6@_Ti{23bb0AogRuH!-8Y@r^%FH
zZQ3TsBhLGba~|X59&vsaCvpanGr=kRVsbrrV7KWQuJX9CA!_vz=LRqjqiKR5xFp4b
zr-j}~<~Ww#N+4rHx^y@i$U1|0j&t!v^h_+N4$0-mk|QA*Q4<4&4i_(YZqb?KMu?Sn
z5_xEHhdW7PE1$Az#jGArN<*#f-U*OJ%AOdqJyhE>EtTW^`OPKcOOMx$^k4T~mpWX%
z1y8v0c7^9I{Z}~0m6Vj%x>FraCy5N}-Xl}&^OpZE8|16e;>?W?Jp(KA@7h0&j_AX&
zSu~|H_6cE~;!0x~htyc_xzH4(yxpJG3MC}0o426Z#`&AeZ+oOdoDI0|Zm5Uv@2@W%
zCyrxbIOplG(V5?eL+@=|oLz`Z0hhAY7ASALyZHJQjBewS&V4xL(TXLRmyq-4hZi}D
zl|pfGMP6o#5T|3ET&m2=jJCUGBbjG+wJ8$fWX(1&Ygg)9kQqJO6M|NoB+6Q60>*Er
zF)f0yczNbhCQC9lf3d1NP*sW+ojQ@P3ES<;D|MhXQ0$cF9u58V(D73)O9ORID;Mx6jq6a^8NleMuV^D3xzR84U1M32Svt$TvtI~vU>{=Cf0qRnhagt&BYgN79@1m+R~
zcmivKt#u%qjjS_R%9ivqG*eOnaBt=d;NW-WGja0sn5VbGNcQj!twlBsNZl`_Pw~FYaT`Y307W)hn
zhc5LsNKk{Zw`1%bTG>6nO!Zkg{~dQ?)V@$w@E4YWiF#j8xUXI^RUhj4rM2X0Yl%Bt
zSv$2N6s5&97cx?G=aTajx=I#Y+^EIBIL&>ys5P~Ax{{b9I;L@vY~s)SB?y69N2RFf
ztCL_nt|(^#c{ZYRu2bZZfd@K*-qOj=zc5a;5tZfFA>PpQ
zLeisMmu`Xen+IN4VR02IgU8!jnJUX0v1s766L-5x?=7*nqkqDT?zw=zggsZpU)FE2
zGdrpe=WrBJc^lS`tcZ3+-BZ`LVGE6s%zStxH|wr-?+6Pkbw)?SdfPfd_=Jjujmld8
zxNzeS7&de$mVOCi*}%m^N9bIj%G#zEz~LcfQ5_3jfo-kG4+mc)eO2U30AJJbiiIsx
z^+r!RUGliH>tSVnv)#$MrZs8e#I6NpR3a6!@D1R>n?`-Oyq-Td`+xIeXT-3g1
zwwBOvJ541Yb{H*HVjo)Bz9w-o%e>1iSgua=YZJ#g1x$nftQR%?vOCycheHYHxr-Cj
z=?Z4pCNq_|TP}FIx}>H0klngIoIA3JR-p~p2G4sE`L=3U*Ye
zSs88kuYwR)(x}op<@p1wou-hDTgd#_ZhRTp6q}ngsUOFcnHONtoI8Z(A`#TAJsM4#
zVtW>B%;UJlL!?|8I%;a`t50_baZb$b(T?Dt^xchJ81Ji%wo^Bl{rS?x1p$6+TE5g*
zn-2AIlG3=|?S`YTl5uqb(hTxiTt-}%|01E`ahN9gBJNn|lg!kQBOBj{As@zf;giXi
z@kRVP(yimDxHnioM~!vvlm_n9DJVyKJH19$chpF3^&_V`8smGwjvNn&jPH6h6ryr)
zGj)*EgnfJg`6^*H{GR@aW7>TC1jb{h-wh($5|bpfAE{5Y@*PQNk{lkME~$s);1gtW
zQU*VTyp*KjHj1PuK<)A-}BvVpNY&%k)O)0~%2<`U=
z9r-!*cAhM_%>BGe05z7SUr0`+rSKET
zwY1fI7Fm>@jo)|Eckx5X=nQ#!Gh1gn-8~E0&V0vth~lQ%kNwmZwLNAg~ptDMcdBo26Ty}CTf
z4<=Pz*5vyX9Ov-grJ3l{K?@*|b~*yR!h1l+A-*$0QwM+l6sUNFoxXD&W_@Z3SK^y0
z<(LkMvuTbduFQVCnS79$HF#O`e@y2(<{*KbTY5pxVLx8(`AC`9W@GpePFJimx;@7F
zBJ&$=>(v{e3!VrEd}B!dJd&O@*qaT540Q{4rOb5#PL?>DOzMv8Ih|O~0Dd$`wwZVqA0xrY=@%Lq{`$i>GonJY&5Z
z=7}Fb{+*Rl(B+zV+E?UX)q5H}p>}$vJ8aTz{519U=U?}S``c6uzG5FnH?WPthEAdW
z{3~GhEo|f%{~pHwRd+J1YYcyiOzHZ0-cmL=8*BhNT!Z12K=9(0fav@L&Xo{X2GnO}
z3>;?9(`OiuvrRBpE5ya342r&>W@fjuJpTc)c2}f79T+0kD#BOacJWCLPGeIaxDA)k
zoP!P_)!jSsxnyVe0{#N|tGlMRjII2rAgugl@z_Kk&*q{JXd1gmTs4Z;EvoVa2ZwzN
z8P}tmcUEtd!@XOwTBKoR1=UUmt@;%kQ?ij+GJo*S+cv4sEwwx7FCw{rQ?>stPUWUa
zE**_$E9{SCoE5{)P6Dk4LSPolVK;q}9@a
z6GKbvY2$rlbWb&JB2_&*@*c9a=Pn&vU>DAXz^Yc3UxMp^MPuuVMT*98A@$uC++js@
zfT&o5TgEF-kD(jLs+`Q&1>hS)VVqV4@m`--apvO^>r==0=2?=BhZune3#-N{n=
zCB)cwG#IKDF6&Z(PBzrw45Usr67^K|WQi;}dH}DdiG=LCrFC8ZXJ8Otx#P`4@><`w}emQLU5z
zjr7u8;8$;!=p~Xq6KaBuQ9S<#i8aZCO*)PvRg2=ucvA;{HJM`?APE^qJ~nlhWdBKi
zFlmFI(t=^4H=fAM@%*P`fH?y$(sZ*@DuGF99mU&Zt68r6^c*{Hsk$5X@{TnpBMCB~
zyjA4My+)es{4Q4mfk8;AG8}7SYq>rZtn_N)l{uM_>MejR2P1&QvJ--{iEdr^NPe
za?h5Tvo#=IQHvjPnQ%kG95nYVliuuX2=4%=Uo9e@Mi{s)WKf@8DNmxfA~h2t=6m?i
z)2ShZp*-|Uh}%g>pU(UavbWEsoZVu(QA->ss?4VtRosSFk`^Q<&*=S9957j#WJ%wA
zTscnkZHYdAQXFcmb2b}l+OOp6elycs$JpNA-)Rnv(oVxpW0V!8&LY2g3z?j^HldX%
zMjZKLwjrlTOulMB!YOdmVf4|~GzI&nT!lMwm0kp`&SzfEMBCHB^?*9Ch9wOljDR7+
z#|mgwk;AXvLRRFzhBfWp|I_}@oq#dl`w2K`;#elZ{+D(m0~66vpWunHQ^(&7E5|Sf
zVed}HZTXw7JVAO7NbA)B6#q+bRk%;f@YA!wPKD!)bhb5j136)DEpqf{GJim-bWJgq
zd+!BCI7BuN$n3Tb93}ekE$$Bf0l^qU1jc~VU@;^68lr{4;hqKuokM;bP!d|jR`y*o
zxIm6I_ZIXuR$*W2gt+}kJ`3jjf
zw!WL$!fs>>)evH1Yg}aGD%1tQHW!LZzMqUP%!RdHR%i-iYvQaONlq6&!Jj3GgLMcw
z4j-)M8_3eZBikQCDaIM+Ot>u`w-?B7g9jxp`%VxXw97c=FtrnZgCLF$dYud#q8wZ$
zn)E~`OX;fqkW;kK7T%8#$4Dh~_#i0jJRHf+3HYHKA&Jaddqy~logcgc%4xK@e-z3N
zk0s}ZB+FN`7RQ&mlbsBs*{y9#U7YBv#xbh07QR%vlj{J6izIcZHeo5KezmygU;8gr
zj5}L?+3gh6OUR6&x(HS%fXwnA4$TT*fZi4_Y+OpN3{4DQE@rM)kq1ME^Vf*oemuqs
z=dr|hmycQ!1{HP%(cYybXPBISosoQWuh8jPHA2)OoSAPJ+Dq6-KaTfOcD_d*5-nm7
zJ<7}sX625dQWrc~C1GSr&i@M{wFsr)s5@I!lyo6g2ZKW
zrPEVb4DqBL<_mF?V@3TV7{`K5O`-l&H%T1PBk^iLg3SIqzMs?l4M*;V;HD9zDla2r
zN2K!0$@~#r(Ds`na${KN(SO&6m|Wj-!EJ#CUL{vY^a4-2k(~{!T_NrYc$Br{#~|qI
zJ0>*U4g?sAmA9F5U3C~1?rsUZnr`}#{kQ?oO~cHu6W7Qmc=nq_&W`Muy0e5sHyE_w
z$Dn$85vP0Ad}!`O{?;Ryd7(LK|r>~!Zpa`~d{
zG#UpZmQ-*D3HgmaA$CFbe|vTmGY~1vtDAoi%<$f>I}#rY~$#Z38b)i
zLWTx8J3Ti6v^$qdl51JCm)$vHoi;X}?61k`;*?xApr}e8Y`55QW$;eD&Ry!~L^sp{
zH*_gByre{VzHi0I7Ic>|YDin=e~O-KHJLpsjen0+jj~~>&yC9L#D?fNydOrl*l)pB
z#P}8k^X|xE;mcw=WIu^1$&Z==7V+xiWW%T=(@F~YCbGLE(-iPrJspeb!hPtJ?){P-
zkSw%NF|M&GqQcS?R!pB{MgI)ZbiF`gN9*tqFnDy9G)Jr^My$?|MWd6YA%7FLlD9{X
z;P;YyqjMuY(79IEDB(QOjFEYDk15w)W~W&;@`5Kt@FWPHSmXVDxa>ir-c3qtU}jh_a_v$GOi9V*5_FSoJ0>jiN1xn=69qgTkB-e
zBdktBQ%VvpQppHdGOlR41JnktGaJXyb3_ZU&-BM~1aT`3}NVxg*ydE+HvjT6-;37lD^_Jv}CX2=?pnM;Vi|4;0$HsNx50UHR
z6p_!LdAz16XGqUdoutcjGNp6^SnEqGykj8YzH|f|k+zNoBUu>2aYZa3yCB@CX39Dz
zjmp7Mkw7~>W~2wIs`^0!F$iF+b$LDsQwZ4o0R*Ctx|-%0l;
z{rpd4&y#}-;&7Elpa2oZp5vO&Q5GgF5)cp
zD_a+ljV*N3z9Kk?zA0Vpzrj-q>GDF|iO7f<5k;odz
z^r_l5Gax&NC0@Ht-kbVMdf#oh$w}Z;Rg|yB9R_jQp7bVMrdA{I3fVbr5Cr-Dv@HB)
zI4dM07uD==#z}&^;*cio4kgxn?;6JQD~N1HHUAX(Y{s*=9ciAv%4d;|GZi=-z5|9Y`z_~d89tF%D^p%lVE*vC~;lhJt@Uw~DyO0F&fMl)|
z6AN}4FmJ<7P3$1SMk6r5w8|eWY7}$}DpkqCdg%{ZqgBz)txRSNsPI^3nhpeH<}A{`
zHG1_wWX3{|w(`CAn2#0${STpGYJ4-$tv@X4Fnab~<=yo~w9`n^#hs-d`ghIZKx6-#
z%+#SMATv|hB9OCkUD7}tVZQuuiO(XGc%ppP9-e5I)n7;Dhe&5dPeR;7(wAY4>7e~=
z>)9n<+FYzCfi1||xKY3>U
z8p#*mknk7e{OTIR3(*qZU-QI5+~dt;%p$quabp+TxD|kF0QHMDt`Fd4
zz#?KS%Xsp_j>n^Z4k+dzc(478{lHradwNKsyZ+`F`Iw^)9L1k9X5$ZY3|XiXaGV
zA4jpBj@*C^#Y0N1XP~bxxKg!fs?WlCJ{Qzt-1p5&lx8uM^L4>JrVQ>1&N?`MC1b{y
z)(Pqv3P|xhk&B_shBx8aE8uqD--?GacI%Ae;L78v@VJp|@nxAKFouAK7>TkLrcVuU
zChPE^OSufT7}BPnAM0STgL?7;O6az4EF+;y(!7H}aPbHi_hBW7Kf6;LRLyYQO$*T=
z=EF`3QB|Rz@cyg&Vfp@tR+iL5IXHM%(qeiA!xgm``YeW&qn*c?lHLUUv65~<#^;4)
zuK@D5mM+1f{Fh~4$oRna>ADjxsO5}4!WbR=2Ce-zHJ~X
z5<<@%B3Un|#V>A;E-T%k{Ibv`wVq#H!2^ovk<(=K%gJ6lipnhJlFCuMyEFS1gy6u2
z1cgC@td1f`P>Q%?Sn&bAhTx!^Wssj^f?AAmTPv9SFqHFk``_eeGLxS~k&B`KEkEnG
zz|Xk#axou63YRwWy@|Y{Gao@lR1C^uY)nzIFor5?J7reU3PnpN?zyxa7uLk4n~%L6y&`zB6KJP9ip9
ztn*}0H%cK}6`g1?um+C36w^YWe(u@?0SR7`%qis0N|%>;bpLt&JbdK;eE%k9Pj}(h
zkC&&r*QN$Dt8^8&y@2-Q`8EGjTLNcTYa9M%2VKWn5vz$xciZzm9S>3i7l|P?ZSaxr
zS0r|L1`$_(sQ;|*YTA)|A^zAkWu(o@G@Se0SN4v27Lj3`rufF^z9*4>Po}QSgn{?3
zOpH0t!~yg=_h+I%sG+en`&PE)d;P9r1)*)gUTGh^-nz7Skh?Y>qGJ{wB0!j2?2@2D53;d3k&h-H
zSEnINd%3zZhWW5M1Hb93boeb<)fEqKWvkM>DcEG}#$$)_-PbUYQ2#MD%fCYW(n_}U
zN3P=B=W*^oMB52!DA~1*IG3=tU;ND!*q@5vc7E)`NI2JGB!Mn}71k41Ov-G~JhcAk
zlLAiy9_L9X5*85wYCwQKBr-CQ*OOJGOBCz)?_PJ4qhtz^tzH)z9@B?&R*^4OALezW
za!oE?Cl0MqAt?998Xtb=zA}k-lW$*PQ-1J@s`nt=7fL>!*BbQe?Vx%_z=g`)*+F{&
zhlk^6y)@wQeEiz<9xco?uv<5{n*+GW-3a_Yxi6e16V}F)PuI5TUh^MS-u;g%um7Wp
zpWIkGPI9k~*w-D%0PF(*~g8fsJw;1ocoeg2^iP_f>-jrmguad^~
zg)koSH?ra{KNIL%{MwEA!R#OpBQxHZh(5k}!=`C>_+Jl9GB)4hczWWZ%)`t5p
zAr5x1GT4f*2a4#o4J7Z)&dS-)Jt$)lZ95Nb*t2k4q_^$ZN)U2O2jml-ou!Mxe4?SmTG8|m`$fYiD@QfQxfK8(=Wv|hn^Hu
z8Lbmj75!LDQ)rEt>gaYcHPOvTeMfDAcdm`D6KQ$0N=ysrQZX&0WnyZl^Tc!noh_!L
zs8dYG(g|WZfsPW>$3df6cMmN6^XfsyyJ
z)0l>d=u(EZVCjUt%Ncq@M5`G3vxqYPnqC&sbqqZ#qU#y@xrlCH=s^+P!q5*zbSp!*
zi0CI?hQBJ}wG3S$qDL9(6VYP~ohPCYDvmx4R2=hJmK!e?>}Ti*5xvCFfg*Z^p%xLn
z%21_Fm!~7${0FOL{$v6h^UUCN)a_NG+RV%JRZzws)*+?at9GDU}zXnanOY<_t#7|
zP&-3^6VVY2{YgYeG4zs%j%Da+5uL!$&xm$Y=Yem5fLDiLV~7d%hQ$ji1C(%Mh^>|K
zLVzMOhB#X((?!Z$l~FxIq&R3X*|aIkD-Uo+8$$|OInx4^SYt?GDnc%xfY+pVugWaEUT
zjZ1Lx97#UdniFQ*%{=fb^5fR2ZP*J4dm1Sq_V;9b8?yL4FTxo7wj^oe+j#2EZ)761
zl62cPMNK8y(H+6$|RXO9^`?>9)PUS9lMCz~YGUmHpuL%&{Qk)k3ee
zQr)>n1c|*;wI6+>_GYoJ2RC
zBz8-`fnC9&7qx_04ZryU#^T)C1|RDR7ukm#_h5zm>MF8*M+w#?RRvIh@ytmO{Dn4PF*uui|k{&M_=069Q0qCz>J$os_)^|xhEa;t4-wf4^tD_
zMbJ*Q3sH#o#s5QR0TVwrBn3mX)7vi$Xiy!?CS7FYsZ20@H?-~UM
zwebJqEG3QP7@jG+_MRd$Ngr%ub5-+R|Ig;{4^T%=dO4
zez}Tr9g+Z3&0f;BshD{Lu4v~{V&5l+^Y-FC_I=;Beb3`5J9B?`P=@Shj`qy`>IfMI
z4uOeaJ$$gg3<3V!19ALtl7HZNgg8GvkS}>|JGnHCd4GYZ+|Yv?`KX#32Q?CY7Rf%8
z!4Iw(bSNf>Uqni45rM0zu1yS*T;D`?)Q#a+lJKLKI`3t2aOeu+eS$l0cNwKBb9|YOU{S=%pdQ5fYAXIP|6A9<{42eY1+m-MAK(PZU2hs_ZYt-EcXP9nZ-K
zxa&GbaALM%v=)umDKDda)Sf^V9#3%neVbs6tq(s5&hS`_Hh&fNhSU|hVX!vNiV&Ns9V2i3WG9^?UV$GJ{j#_d61ICwD{@_DpL^z(tuO(?G
zlJO8_Jdv5sMs>SC;t5_(Zr1-R+A4jTB$D|Eh!J00=?Z(rNJ0A8fArOaV9|W`K*1FRQoI1BpPmZm}#U3AA
zvb#MDR=BGTZCFfZeleMUmVEg|e(d7if)Mzs@%0*nCu^TCrV%9POplcEa`dp7g(2|8
z-M1Aoy%^o{#JGZ~i_AIGm(L@6&Sa-rc7ZLZ&Q1;BXHS^MhwjlZzsv%6Cil*$aM8>?
zn-KMp4SagfU3WWHvY9x}s_?Ig{Ac?~t}h{HL*GB=lSwK7Z_bu0DJOmKFOtncO<}
z!lTRt^4$5+{3vq%d=3^wI6oK*IQYx&5$w#lFeh@36`RgJslp@ERI>fTB9!*{YKG*E
z`8DglQuDlmeDO6tXkB$tkIIINnbJcD$+v!Zd7QW|?nq@f*v&g}Zr5#OzwF+I4@4j0
z9AY2QD=+m6TGwpbH!Ph0h4&tfj`wSY^2W`G2KWGtXZ0`KK4Kt==R0hR-H1C9bN
z0ImXl1Kb02NJIsI3Sa{a2b2P40OkXh0M-Gv0uBSt0cP=Ke63K$KSM{9He(k
zl4RZ()|RB-ldO%X)01qBU;KM@l8spJB}(j57zZCq&b6dTYP=%*uT_j4p(Oc(IZ3(6Z=H=J#}IeE69xpX+gE;1M`U`@`q`fZ@#v*_Q!E?
zz-Pq%y>VmNCN^{2Zm7hgzxALM+`wA?|6OT{{y$I}azpT}S;WUDPvZwnnK@<7ME8__
zvt~X!YsQp*?m08^=T4dF=GJWIEB6iJd1+AlNs<9m+(qt*bKIWU17}X1RlILxFuy&B
z?EJN$O)b1%_E)>FO*nriGE4a*JiGrV*q0U0|4_tVS{vfxmEIqe*Od}gno6S@teUOz
zsMe}>sE(_CR{g08Qm3iAtNW{q)ic#@^&<5$^{eVP)jQRP)phEQnjV@GO`YZo&F`8>
ztxQ{?U8_B;{at%kD`;bNxw?M3`MNiCALwrCy6R`>z4}%9&HAHys&8jlZ#ZbUX!yl2
z*%)K`z_ia-P*UG2L^A#7>FKTXU9%$0EU9}%+k8Atr_vl9&Qce9$
zgUlz)=gmKve>aC&CR*4(YGhH2aGAGE_O@)7>{HoE*+toRvfpKQWD@y6`4IUi`6~HZ
z`J3`B@7Dc|IP*J29t(d5ADrPI@DasVf6>Ai)DK;p!DQXml6({kP1n7n`%L#Q-CdnrKSaMtzre5r3%AeE&Zsu_F&1LsYE28xQ!O3~
z`)4_v4X@Tn-j_Ek464Pd_f=<9TQrIKRDFg%OW$4JOE1@}^?7=`{uTXt{X2T2VV+^7
zK`=&}2ARCYrirFmrg^3pO?yp8Oeal^rr%7wImFQbPa`hV!IJ0Jg#--V$*`~Rv`AZX}jn$@TJ8QdZbF~_+Nt>sALTlHK(oWPqt(~KN
zPP;{*?V_0aYG!VlU!*`J0NMo$AH#TE}QDTZS
zbu-O0y=1B~Z8z1J4wxED_f6sERPzw?Q|5in%&8rOCos2Uv$$
z7g$$YcX_Q}Te%4w#{zqyvIJRw*>u?~*{hK34`lz6{U*C7Q_4;90(rT7h5R*njr@ZA
zJ9)68y&_(bsSp&MmA#a9+RSKFvwH5kP`n&o>!vo_;ONnJ1)M<)ky5(uhGnRKOHI^?e|F)!Ieq0&8
zH$yveS&Cc*ZJMZhTYXj2MLSgcjCQpCbK`O2Y2!KLS6<^c#_x%lF6z3FelrhQ?%JItS%K6H2
zWxm>>E>%w!CFipGNA&}BmgX5K+j1z|F3mp8cN$I`sZG>oKz7vHQmuC?#*?gnT_0*l
zH0TV24C4&53?CZK7`B+}&4RhBrN6~)nPypT`Oq?!4Ppbw7Kn4oo|buKZ^%B9?UUum
ztL1OV;}p3Ho#J`Ldc|JFMa5r=zDmE+r0T0GP>oVeQms)3X*y}AX*u0l-5~wb=$-coE)Y*p+~oL1aXbXUrh
zDy2?oQreVx$^vDf@)hNJrFVmJi*l>7pK89UOjWKTs&%S2RGU=0R6nYIQT?vcsEulm
zdcJzG`XzOxdX;*En%A_|L~GV*-o;`b*3@e>S|il2NIO^ig0>9m)~t=ub<$<(R64zG
zkZ!nclwf+=l52WoXcb)kAW&cAQff?BFkX{KssYIbNgXy4ZE
z)m_j%sjq=YgAx0G0ndaPi~s-t
diff --git a/pipenv/patched/notpip/_vendor/distlib/t64-arm.exe b/pipenv/patched/notpip/_vendor/distlib/t64-arm.exe
new file mode 100644
index 0000000000000000000000000000000000000000..3acd5ef9e85933e9b7373510dfb7185df4eb4a86
GIT binary patch
literal 180736
zcmeFa3wTx4nee~%IZ4(>Kk_4|Ja#JIjO^bdJo<60Wo85q*niafc#F=Xm%Y2+I^y{u|oSlSxfv%mR(QlIEZ
zQqkgXR66foR&!K}XY$2rFCD#(kXK9Q7TFP@#kEXAFK*e`SYrZMyHTn
zmQ}=OX#+6uDJPKkiZ2$ju`j_4MCxZKeJ+NwEK>deyZ_lv(akQrCh`Z!nD
zUr4FYl~y*@Dg%BS4X1Lwel!5&>$>tbJEQbb`Z}5GP3PqcU3vTZ+t=|*;{@EOG9JR?
z<@@B#w~|qCq`;Ljx&G+9e8r^xKmB)=p`M=VQPHWMP%KAH9Vk+&yvw`h*`d4sxqZm1
zqR#sTPfFEFH8-+0*jT`bFDB`x!-?m<)!>`Lk+RqiiTK)zh&ho_!TS9sX6g@
z`C89ai(%PJe?sWtIjKbtUz%C`@Z}dRdpK8lqQhkp)Wd62jfX#x>3R557kM9E$2%#%
z_D#yGtLV$Il~V6o6