Updated venv

pull/784/head^2
sTiKyt 2020-08-22 18:46:15 +00:00
parent aa3ee1a510
commit 7df5ee5baf
162 changed files with 5250 additions and 3994 deletions

View File

@ -1,13 +1,14 @@
Metadata-Version: 2.1
Name: pip
Version: 20.1.1
Version: 20.2.2
Summary: The PyPA recommended tool for installing Python packages.
Home-page: https://pip.pypa.io/
Author: The pip developers
Author-email: pypa-dev@groups.google.com
Author-email: distutils-sig@python.org
License: MIT
Project-URL: Documentation, https://pip.pypa.io
Project-URL: Source, https://github.com/pypa/pip
Project-URL: Changelog, https://pip.pypa.io/en/stable/news/
Keywords: distutils easy_install egg setuptools wheel virtualenv
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
@ -79,7 +80,7 @@ rooms, and mailing lists is expected to follow the `PyPA Code of Conduct`_.
.. _learn more and take our survey: https://pyfound.blogspot.com/2020/03/new-pip-resolver-to-roll-out-this-year.html
.. _Issue tracking: https://github.com/pypa/pip/issues
.. _Discourse channel: https://discuss.python.org/c/packaging
.. _Development mailing list: https://groups.google.com/forum/#!forum/pypa-dev
.. _Development mailing list: https://mail.python.org/mailman3/lists/distutils-sig.python.org/
.. _User IRC: https://webchat.freenode.net/?channels=%23pypa
.. _Development IRC: https://webchat.freenode.net/?channels=%23pypa-dev
.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/

View File

@ -1,14 +1,14 @@
../../../bin/pip,sha256=xEtZHt_pC8ID4KBqXAaqKhGUGUBGTtvkhuwqkLbNfHw,266
../../../bin/pip3,sha256=xEtZHt_pC8ID4KBqXAaqKhGUGUBGTtvkhuwqkLbNfHw,266
../../../bin/pip3.8,sha256=xEtZHt_pC8ID4KBqXAaqKhGUGUBGTtvkhuwqkLbNfHw,266
pip-20.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
pip-20.1.1.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090
pip-20.1.1.dist-info/METADATA,sha256=dwRFheMvgIBpyZllM4tVlf5TfjoXc1ZxlsJf0ze61_M,3634
pip-20.1.1.dist-info/RECORD,,
pip-20.1.1.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
pip-20.1.1.dist-info/entry_points.txt,sha256=HtfDOwpUlr9s73jqLQ6wF9V0_0qvUXJwCBz7Vwx0Ue0,125
pip-20.1.1.dist-info/top_level.txt,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
pip/__init__.py,sha256=9lnkMA2mCKfgnTkqep7tMbosgEJ4rENcyu2tcqDwUNw,455
pip-20.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
pip-20.2.2.dist-info/LICENSE.txt,sha256=W6Ifuwlk-TatfRU2LR7W1JMcyMj5_y1NkRkOEJvnRDE,1090
pip-20.2.2.dist-info/METADATA,sha256=ZMrQjU30hfRVRViE4qoVrwZzDSRyGCT8OdkDkkTMBv8,3708
pip-20.2.2.dist-info/RECORD,,
pip-20.2.2.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
pip-20.2.2.dist-info/entry_points.txt,sha256=HtfDOwpUlr9s73jqLQ6wF9V0_0qvUXJwCBz7Vwx0Ue0,125
pip-20.2.2.dist-info/top_level.txt,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
pip/__init__.py,sha256=fZ401OZGdv55YGsuCEhsvdNa4YCZLmK457SN3qrstJk,455
pip/__main__.py,sha256=bqCAM1cj1HwHCDx3WJa-LJxOBXimGxE8OjBqAvnhVg0,911
pip/__pycache__/__init__.cpython-38.pyc,,
pip/__pycache__/__main__.cpython-38.pyc,,
@ -23,8 +23,8 @@ pip/_internal/__pycache__/main.cpython-38.pyc,,
pip/_internal/__pycache__/pyproject.cpython-38.pyc,,
pip/_internal/__pycache__/self_outdated_check.cpython-38.pyc,,
pip/_internal/__pycache__/wheel_builder.cpython-38.pyc,,
pip/_internal/build_env.py,sha256=2P0xaKpDhEfrA5P7cXnbx9QpL52Hc1Uturp8EIcjGRg,7506
pip/_internal/cache.py,sha256=aXPdcihRKQVH26jl1cxSKTmTnV0_hNMs7cGADMUFi1Y,12334
pip/_internal/build_env.py,sha256=9_UaQ2fpsBvpKAji27f7bPAi2v3mb0cBvDYcejwFKNM,8088
pip/_internal/cache.py,sha256=pT17VVxgzZK32aqY5FRS8GyAI73LKzNMF8ZelQ7Ojm0,12249
pip/_internal/cli/__init__.py,sha256=FkHBgpxxb-_gd6r1FjnNhfMOzAUYyXoXKJ6abijfcFU,132
pip/_internal/cli/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/cli/__pycache__/autocompletion.cpython-38.pyc,,
@ -39,14 +39,14 @@ pip/_internal/cli/__pycache__/req_command.cpython-38.pyc,,
pip/_internal/cli/__pycache__/spinners.cpython-38.pyc,,
pip/_internal/cli/__pycache__/status_codes.cpython-38.pyc,,
pip/_internal/cli/autocompletion.py,sha256=ekGNtcDI0p7rFVc-7s4T9Tbss4Jgb7vsB649XJIblRg,6547
pip/_internal/cli/base_command.py,sha256=O5fT5HHfc_UYNvhqK0rjfh_13K3fIVQzcKUF4xKbFts,8024
pip/_internal/cli/cmdoptions.py,sha256=SjHNqaQ49FO0VKshjoazPVFb7iDx71FKW_N4KgcS3qQ,28403
pip/_internal/cli/base_command.py,sha256=BWTztM4b6h8hodDHDKjgJ82jaSeru2AILAJxi1d_IP8,8810
pip/_internal/cli/cmdoptions.py,sha256=M_BtuqeyRpZAUUYytts3pguBCF2RaGukVpDPE0niroI,28782
pip/_internal/cli/command_context.py,sha256=ygMVoTy2jpNilKT-6416gFSQpaBtrKRBbVbi2fy__EU,975
pip/_internal/cli/main.py,sha256=Hxc9dZyW3xiDsYZX-_J2cGXT5DWNLNn_Y7o9oUme-Ec,2616
pip/_internal/cli/main_parser.py,sha256=voAtjo4WVPIYeu7Fqabva9SXaB3BjG0gH93GBfe6jHQ,2843
pip/_internal/cli/parser.py,sha256=4FfwW8xB84CrkLs35ud90ZkhCcWyVkx17XD6j3XCW7c,9480
pip/_internal/cli/progress_bars.py,sha256=WtKOHkePvHwnlhDUotAmKpjBH6hBdVTOnxSiiuCC2l8,9031
pip/_internal/cli/req_command.py,sha256=NajtG3IfB3YkiM7LANLttyJTfPtgB-3CTErY0YR0k50,15309
pip/_internal/cli/progress_bars.py,sha256=J1zykt2LI4gbBeXorfYRmYV5FgXhcW4x3r6xE_a7Z7c,9121
pip/_internal/cli/req_command.py,sha256=Eiz8TVzeqzG-40t7qLC1vO-vzjCRvX9C-qXMyfw9D1I,15132
pip/_internal/cli/spinners.py,sha256=PS9s53LB5aDPelIn8FhKerK3bOdgeefFH5wSWJ2PCzI,5509
pip/_internal/cli/status_codes.py,sha256=F6uDG6Gj7RNKQJUDnd87QKqI16Us-t-B0wPF_4QMpWc,156
pip/_internal/commands/__init__.py,sha256=yoLAnmEXjoQgYfDuwsuWG3RzzD19oeHobGEhmpIYsB4,4100
@ -66,22 +66,22 @@ pip/_internal/commands/__pycache__/search.cpython-38.pyc,,
pip/_internal/commands/__pycache__/show.cpython-38.pyc,,
pip/_internal/commands/__pycache__/uninstall.cpython-38.pyc,,
pip/_internal/commands/__pycache__/wheel.cpython-38.pyc,,
pip/_internal/commands/cache.py,sha256=LZCLVEYCr5Ugh81Zt07Hz5v6SIt0QQzr2-npj3M44aE,5676
pip/_internal/commands/cache.py,sha256=U3rLjls0AMMO8PxnhXVwIp7Biyvns8-gBThKTH3tX7Y,5676
pip/_internal/commands/check.py,sha256=fqRrz2uKPC8Qsx2rgLygAD2Rbr-qxp1Q55zUoyZzB9Q,1677
pip/_internal/commands/completion.py,sha256=BoEW3RZQZhsZe5to1aOe245dcBLkf-PTCJL7_u9A-Es,2957
pip/_internal/commands/configuration.py,sha256=y74Vl2p41dBOE2NwUzW4YqnbGbl9r0lsCyBlHguDAWA,7206
pip/_internal/commands/debug.py,sha256=3YkY_M-h1tfpEzat4agulzAk17MU93Qt7ehy_Gi2l6Q,7275
pip/_internal/commands/download.py,sha256=thDfHi0qY6DQ_1GkYPTutwta3tA0RaHJhKycepC4FgA,4740
pip/_internal/commands/freeze.py,sha256=LveCd11SlrZ7s3RovnWpaK0tXB2Jci4DPcP6A9cj0e4,3342
pip/_internal/commands/hash.py,sha256=KckEd5FeomfsRgZmRzhJRPYSsz-HXbFZGNdrzp12ftQ,1742
pip/_internal/commands/help.py,sha256=s8bDMJbRVxs9ehLKuD4mXTsv1bTRapy1jDwaTCE90qw,1193
pip/_internal/commands/install.py,sha256=1oXXadnvHM-55XWIdeqdaR75BjLPGC2-wXEkkhMyMtQ,25430
pip/_internal/commands/list.py,sha256=GCrhhu07cw3CyZGFk8rGM2ejk3JmeF9XLbC7raiq_bw,10141
pip/_internal/commands/search.py,sha256=KjAz-s9mwkiLfDd-cpQO3pL6KFoOyl1RKlvxnJj3zz8,5191
pip/_internal/commands/show.py,sha256=RqSX_KvzcZWz1gxIOZEnnk4-VeSkNvr0yWz5jF6JrcY,6791
pip/_internal/commands/uninstall.py,sha256=D2Otze7J-RJvjfozRq2Yon9NKJrg4cbBGFuXyEwBMR0,3202
pip/_internal/commands/wheel.py,sha256=oxyo51V1m_Hu4U-HCS53vBx5-82Q6GOhn1doOgAr3KE,6431
pip/_internal/configuration.py,sha256=k3Y3HMMMm_fzNqX75QoNuHvjX8tplmNBuIJJpDHmf9M,14349
pip/_internal/commands/completion.py,sha256=ObssM77quf61qvbuSE6XLwUBdm_WcWIvXFI-Hy1RBsI,3081
pip/_internal/commands/configuration.py,sha256=IN2QBF653sRiRU7-pHTpnZ6_gyiXNKUQkLiLaNRLKNw,9344
pip/_internal/commands/debug.py,sha256=otBZnpnostX2kmYyOl6g6CeCLmk6H00Tsj2CDsCtFXw,7314
pip/_internal/commands/download.py,sha256=EKFlj_ceGUEJj6yCDw7P6w7yUoB16IcNHhT2qnCFDNQ,4918
pip/_internal/commands/freeze.py,sha256=vLBBP1d8wgEXrmlh06hbz_x_Q1mWHUdiWDa9NP2eKLE,3452
pip/_internal/commands/hash.py,sha256=v2nYCiEsEI9nEam1p6GwdG8xyj5gFv-4WrqvNexKmeY,1843
pip/_internal/commands/help.py,sha256=ryuMDt2tc7ic3NJYMjjoNRH5r6LrB2yQVZvehAm8bLs,1270
pip/_internal/commands/install.py,sha256=h2L8vS6t2DbGAdttkdZmMucK2eJG2CYvcwhDa7AdKrQ,28683
pip/_internal/commands/list.py,sha256=2o3rYw37ECrhe4-Bu5s_2C0bwhYgghh4833MxcWAEug,11312
pip/_internal/commands/search.py,sha256=1HPAFU-tmgKrHhr4xNuk3xMoPeSzD_oDvDDiUFZZ15E,5756
pip/_internal/commands/show.py,sha256=r69-G8HIepDKm4SeyeHj0Ez1P9xoihrpVUyXm6NmXYY,6996
pip/_internal/commands/uninstall.py,sha256=Ys8hwFsg0kvvGtLGYG3ibL5BKvURhlSlCX50ZQ-hsHk,3311
pip/_internal/commands/wheel.py,sha256=-HSISE5AV29I752Aqw4DdmulrGd8rB_ZTOdpbJ6T8iM,6419
pip/_internal/configuration.py,sha256=-Gxz2J-KuvxiqWIJ9F-XnYVZ5lKhNk7VO6ondEbH4EM,14115
pip/_internal/distributions/__init__.py,sha256=ECBUW5Gtu9TjJwyFLvim-i6kUMYVuikNh9I5asL6tbA,959
pip/_internal/distributions/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/distributions/__pycache__/base.cpython-38.pyc,,
@ -92,14 +92,14 @@ pip/_internal/distributions/base.py,sha256=ruprpM_L2T2HNi3KLUHlbHimZ1sWVw-3Q0Lb8
pip/_internal/distributions/installed.py,sha256=YqlkBKr6TVP1MAYS6SG8ojud21wVOYLMZ8jMLJe9MSU,760
pip/_internal/distributions/sdist.py,sha256=D4XTMlCwgPlK69l62GLYkNSVTVe99fR5iAcVt2EbGok,4086
pip/_internal/distributions/wheel.py,sha256=95uD-TfaYoq3KiKBdzk9YMN4RRqJ28LNoSTS2K46gek,1294
pip/_internal/exceptions.py,sha256=B3tSkzheqSfGoGt5OcAOhLhfnWWMzfJ60URvZWwkwHw,10308
pip/_internal/exceptions.py,sha256=ZVpArxQrSlm4qAMtHaY3nHvG_t5eSi3WCnMowdm_m8I,12637
pip/_internal/index/__init__.py,sha256=vpt-JeTZefh8a-FC22ZeBSXFVbuBcXSGiILhQZJaNpQ,30
pip/_internal/index/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/index/__pycache__/collector.cpython-38.pyc,,
pip/_internal/index/__pycache__/package_finder.cpython-38.pyc,,
pip/_internal/index/collector.py,sha256=tFpQdkBlbNzdwlep7a5_o9unymgWuEmo2WtARsagiao,21547
pip/_internal/index/package_finder.py,sha256=2Uq4RPSRboyRPj1Zp3-SB8ZFNLAEMrZv6G2yH-wVjIA,37676
pip/_internal/locations.py,sha256=VifFEqhc7FWFV8QGoEM3CpECRY8Doq7kTytytxsEgx0,6734
pip/_internal/index/collector.py,sha256=rMdGdAABOrvIl0DYlCMWXr7mIoqrU2VGeQpCuWiPu1Q,22838
pip/_internal/index/package_finder.py,sha256=ISieDd20dOSndMNybafCu3pO2JR3BKOfHv92Bes0j0Q,37364
pip/_internal/locations.py,sha256=7YjzJy2CroQD8GBMemnHWRl9448BSIt0lfH98B-Dkd8,6732
pip/_internal/main.py,sha256=IVBnUQ-FG7DK6617uEXRB5_QJqspAsBFmTmTesYkbdQ,437
pip/_internal/models/__init__.py,sha256=3DHUd_qxpPozfzouoqa9g9ts1Czr5qaHfFxbnxriepM,63
pip/_internal/models/__pycache__/__init__.cpython-38.pyc,,
@ -113,30 +113,32 @@ pip/_internal/models/__pycache__/search_scope.cpython-38.pyc,,
pip/_internal/models/__pycache__/selection_prefs.cpython-38.pyc,,
pip/_internal/models/__pycache__/target_python.cpython-38.pyc,,
pip/_internal/models/__pycache__/wheel.cpython-38.pyc,,
pip/_internal/models/candidate.py,sha256=Y58Bcm6oXUj0iS-yhmerlGo5CQJI2p0Ww9h6hR9zQDw,1150
pip/_internal/models/candidate.py,sha256=gACeCSHTIaWuB6RAeLmGJnbFFbKfp_47UERDoC_ldOU,1195
pip/_internal/models/direct_url.py,sha256=MnBLPci1hE9Ndh6d3m0LAqB7hX3ci80CCJTE5eerFaQ,6900
pip/_internal/models/format_control.py,sha256=ICzVjjGwfZYdX-eLLKHjMHLutEJlAGpfj09OG_eMqac,2673
pip/_internal/models/index.py,sha256=K59A8-hVhBM20Xkahr4dTwP7OjkJyEqXH11UwHFVgqM,1060
pip/_internal/models/link.py,sha256=KobEaGViwOzyPBD7kgzpGqyrQfh5zjlonOStCGAhl2U,7302
pip/_internal/models/scheme.py,sha256=vvhBrrno7eVDXcdKHiZWwxhPHf4VG5uSCEkC0QDR2RU,679
pip/_internal/models/search_scope.py,sha256=AYbFyfEen5cx0kRZTMgUWUxzcMr5nDk32MO4S67Ror4,4712
pip/_internal/models/selection_prefs.py,sha256=rPeif2KKjhTPXeMoQYffjqh10oWpXhdkxRDaPT1HO8k,1908
pip/_internal/models/target_python.py,sha256=bbOSwPmojPMtCW6i2XMNjVJzt_2GQYfx3FcGQY8pL44,3842
pip/_internal/models/format_control.py,sha256=RdnnmXxVJppCZWzWEmFTr-zD_m3G0izPLqJi6Iop75M,2823
pip/_internal/models/index.py,sha256=carvxxaT7mJyoEkptaECHUZiNaA6R5NrsGF55zawNn8,1161
pip/_internal/models/link.py,sha256=FMlxvqKmLoj7xTQSgKqfO2ehE1WcgD4C5DmEBuC_Qos,7470
pip/_internal/models/scheme.py,sha256=EhPkT_6G0Md84JTLSVopYsp5H_K6BREYmFvU8H6wMK8,778
pip/_internal/models/search_scope.py,sha256=Lum0mY4_pdR9DDBy6HV5xHGIMPp_kU8vMsqYKFHZip4,4751
pip/_internal/models/selection_prefs.py,sha256=pgNjTfgePPiX1R5S2S8Yc6odOfU9NzG7YP_m_gnS0kw,2044
pip/_internal/models/target_python.py,sha256=R7tAXI15B_cgw7Fgnq5cI9F-44goUZncH9JMtE8pXRw,4034
pip/_internal/models/wheel.py,sha256=FTfzVb4WIbfIehxhdlAVvCil_MQ0-W44oyN56cE6NHc,2772
pip/_internal/network/__init__.py,sha256=jf6Tt5nV_7zkARBrKojIXItgejvoegVJVKUbhAa5Ioc,50
pip/_internal/network/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/network/__pycache__/auth.cpython-38.pyc,,
pip/_internal/network/__pycache__/cache.cpython-38.pyc,,
pip/_internal/network/__pycache__/download.cpython-38.pyc,,
pip/_internal/network/__pycache__/lazy_wheel.cpython-38.pyc,,
pip/_internal/network/__pycache__/session.cpython-38.pyc,,
pip/_internal/network/__pycache__/utils.cpython-38.pyc,,
pip/_internal/network/__pycache__/xmlrpc.cpython-38.pyc,,
pip/_internal/network/auth.py,sha256=HJg5peC3gL44H7pmZhCPnu2MrwpAalOSF7d1jmNDqt8,11125
pip/_internal/network/cache.py,sha256=51CExcRkXWrgMZ7WsrZ6cmijKfViD5tVgKbBvJHO1IE,2394
pip/_internal/network/download.py,sha256=MIisedL1oFOSrYAN119HDlIuFfw6eL6CNY7oJhHIzUc,6269
pip/_internal/network/auth.py,sha256=dt3NvTRJ8182S3VpdYFEZMPT0JhOKHyFtR-O-JMlJII,11652
pip/_internal/network/cache.py,sha256=6cCD7XNrqh1d1lOSY5U-0ZXOG1YwEgMYs-VhRZVyzMA,2329
pip/_internal/network/download.py,sha256=VTGDO01_nX-5MCdatd4Icv0F88_M8N3WnW6BevA6a0o,5151
pip/_internal/network/lazy_wheel.py,sha256=RXcQILT5v5UO6kxgv76CSncLTqRL29o-OXbaW2aK7t4,8138
pip/_internal/network/session.py,sha256=Zs0uiyPxTpfpgSv-ZI9hK9TjasmTplBuBivOTcUiJME,15208
pip/_internal/network/utils.py,sha256=iiixo1OeaQ3niUWiBjg59PN6f1w7vvTww1vFriTD_IU,1959
pip/_internal/network/xmlrpc.py,sha256=AL115M3vFJ8xiHVJneb8Hi0ZFeRvdPhblC89w25OG5s,1597
pip/_internal/network/utils.py,sha256=ZPHg7u6DEcg2EvILIdPECnvPLp21OPHxNVmeXfMy-n0,4172
pip/_internal/network/xmlrpc.py,sha256=PFCiX_nnwYxC8SFIf7J3trP40ECGjA6fl2-IVNhbkPM,1882
pip/_internal/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/operations/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/operations/__pycache__/check.cpython-38.pyc,,
@ -148,23 +150,23 @@ pip/_internal/operations/build/__pycache__/metadata.cpython-38.pyc,,
pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-38.pyc,,
pip/_internal/operations/build/__pycache__/wheel.cpython-38.pyc,,
pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-38.pyc,,
pip/_internal/operations/build/metadata.py,sha256=yHMi5gHYXcXyHcvUPWHdO-UyOo3McFWljn_nHfM1O9c,1307
pip/_internal/operations/build/metadata.py,sha256=2aILgWCQTF1aIhWuCH8TTSjv_kYmA3x1262fT2FQ6pQ,1254
pip/_internal/operations/build/metadata_legacy.py,sha256=VgzBTk8naIO8-8N_ifEYF7ZAxWUDhphWVIaVlZ2FqYM,2011
pip/_internal/operations/build/wheel.py,sha256=ntltdNP6D2Tpr4V0agssu6rE0F9LaBpJkYT6zSdhEbw,1469
pip/_internal/operations/build/wheel.py,sha256=33vdkxTO-gNqrtWH1eNL_uZo4Irax85moDx2o9zae3M,1465
pip/_internal/operations/build/wheel_legacy.py,sha256=N1aqNZyGURBX0Bj6wPmB0t4866oMbxoHUpC9pz6FyT0,3356
pip/_internal/operations/check.py,sha256=a6uHG0daoWpmSPCdL7iYJaGQYZ-CRvPvTnCv2PnIIs0,5353
pip/_internal/operations/freeze.py,sha256=mGT2OFjMOb0FlVjgedAzJ9GbNOgNwYiL0130xx60pHA,10587
pip/_internal/operations/check.py,sha256=JYDsVLvpFyJuJq0ttStgg8TRKbc0myYFAMnfnnQOREM,5215
pip/_internal/operations/freeze.py,sha256=_vJSZwHBNzBV0GpRUSXhUJz3BrGFdcT2aTcWxH1L4P0,10373
pip/_internal/operations/install/__init__.py,sha256=mX7hyD2GNBO2mFGokDQ30r_GXv7Y_PLdtxcUv144e-s,51
pip/_internal/operations/install/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/operations/install/__pycache__/editable_legacy.cpython-38.pyc,,
pip/_internal/operations/install/__pycache__/legacy.cpython-38.pyc,,
pip/_internal/operations/install/__pycache__/wheel.cpython-38.pyc,,
pip/_internal/operations/install/editable_legacy.py,sha256=rJ_xs2qtDUjpY2-n6eYlVyZiNoKbOtZXZrYrcnIELt4,1488
pip/_internal/operations/install/legacy.py,sha256=YkKdL_tyNwDP2huOGxmopySh5Pz2v_wRVeSTEa6ZUco,4686
pip/_internal/operations/install/wheel.py,sha256=8IO3GYTtrJ42cqipLOh0rxex4j-PfU8m71HVB0tOQd0,23885
pip/_internal/operations/prepare.py,sha256=RDwtSetVTfv-nv1-_apYBA3Dez5ngBmOYzcuZy2Q3vk,20030
pip/_internal/operations/install/legacy.py,sha256=zu3Gw54dgHtluyW5n8j5qKcAScidQXJvqB8fb0oLB-4,4281
pip/_internal/operations/install/wheel.py,sha256=nJmOSOYY3keksXd_3GFuhAWeeoKvGOyoSGbjXABjZ40,31310
pip/_internal/operations/prepare.py,sha256=Rt7Yh7w10_Q-vI3b7R1wkt2R6XPX8YVUdODk-TaGI9c,19903
pip/_internal/pyproject.py,sha256=VJKsrXORGiGoDPVKCQhuu4tWlQSTOhoiRlVLRNu4rx4,7400
pip/_internal/req/__init__.py,sha256=UVaYPlHZVGRBQQPjvGC_6jJDQtewXm0ws-8Lxhg_TiY,2671
pip/_internal/req/__init__.py,sha256=s-E5Vxxqqpcs7xfY5gY69oHogsWJ4sLbnUiDoWmkHOU,3133
pip/_internal/req/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/req/__pycache__/constructors.cpython-38.pyc,,
pip/_internal/req/__pycache__/req_file.cpython-38.pyc,,
@ -172,12 +174,12 @@ pip/_internal/req/__pycache__/req_install.cpython-38.pyc,,
pip/_internal/req/__pycache__/req_set.cpython-38.pyc,,
pip/_internal/req/__pycache__/req_tracker.cpython-38.pyc,,
pip/_internal/req/__pycache__/req_uninstall.cpython-38.pyc,,
pip/_internal/req/constructors.py,sha256=i_dU2sYtSk5GMsad68gBx26tfneRmhPF2sYGe4uPnu8,15441
pip/_internal/req/req_file.py,sha256=5QlZr36kkw1Jsbr8vFO-fGUEAef9h-AoRRqjx8EYSuQ,19075
pip/_internal/req/req_install.py,sha256=9yn_fBFTyzHPMjYG5WXBB2WiGQvk3BT6gYl9Khw8ZoE,31713
pip/_internal/req/req_set.py,sha256=EBHZ9zWSR8arxjcadyU2OotZIECemM8oOFQ0nK-Bb7E,7792
pip/_internal/req/req_tracker.py,sha256=cAKhSw-QbhGxqPF1Wc0zD6jo932jpdYF3ROfRSH8hes,4744
pip/_internal/req/req_uninstall.py,sha256=NdErRQBpNScsdwJAo3O_zo2KPPfQyVMJ_Q2mxPWYyOA,23734
pip/_internal/req/constructors.py,sha256=LrSHbRHu52-h6HM1qJKG68o1Jw5q8MvJGfr4As6j2uU,16387
pip/_internal/req/req_file.py,sha256=p7n3Y0q275Eisqfxd0vtfnxYvlT6TCCY0tj75p-yiOY,19448
pip/_internal/req/req_install.py,sha256=hqQMm6s7f1a2RAGasyAhy47eZGom3OBKeiRM60xxdPg,33080
pip/_internal/req/req_set.py,sha256=dxcfbieWYfYkTJNE07U8xaO40zLxl8BhWOcIHVFTmoo,7886
pip/_internal/req/req_tracker.py,sha256=qWaiejNK6o6cqeyTOIGKIU1CoyrXCcqgMHYi3cqelOA,4690
pip/_internal/req/req_uninstall.py,sha256=opMGDGb7ZaFippRbaarJaljtzl2CNZmBGEUSnTubE-A,23706
pip/_internal/resolution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/resolution/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/resolution/__pycache__/base.cpython-38.pyc,,
@ -185,7 +187,7 @@ pip/_internal/resolution/base.py,sha256=xi72YmIS-lEjyK13PN_3qkGGthA4yGoK0C6qWyny
pip/_internal/resolution/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/resolution/legacy/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/resolution/legacy/__pycache__/resolver.cpython-38.pyc,,
pip/_internal/resolution/legacy/resolver.py,sha256=56GuGHWcseV24cvTCOuRHMAF_Er1UeDxn5m18XMkHBs,17587
pip/_internal/resolution/legacy/resolver.py,sha256=d-qW6UUxbZqKyXmX2bqnW5C8UtnO0ZcsQuKw_QXualc,18755
pip/_internal/resolution/resolvelib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/base.cpython-38.pyc,,
@ -194,18 +196,19 @@ pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-38.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-38.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-38.pyc,,
pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-38.pyc,,
pip/_internal/resolution/resolvelib/base.py,sha256=l2Z3-1Qg243lWzwFbaN17qixA4U8LYr-qMhZTdaHROc,1502
pip/_internal/resolution/resolvelib/candidates.py,sha256=wzi9t3aX1Twi3xTNEkpG6eWOd0dEg5uKul-QkK3arvw,15173
pip/_internal/resolution/resolvelib/factory.py,sha256=mDs3p8D9N9zfYvn_iIx0saDLHF1SF7KHBQlA1gWSWVQ,7574
pip/_internal/resolution/resolvelib/provider.py,sha256=0fKuPuEoD5T7w-YwKgQZc1AmgSnAkrxGnLBOf-_6Kiw,1703
pip/_internal/resolution/resolvelib/requirements.py,sha256=bu9Y4YINHjvBm-NBKvnxw9IYHW4t6rRlm4-QKVqLDsM,3872
pip/_internal/resolution/resolvelib/resolver.py,sha256=3LXhhCz6CtIpih8tK2nHeRvVEjVJmXrqxNCM1FQM1U0,6673
pip/_internal/self_outdated_check.py,sha256=3KO1pTJUuYaiV9X0t87I9PimkGL82HbhLWbocqKZpBU,8009
pip/_internal/resolution/resolvelib/base.py,sha256=n8Rilea9jCzhlbtFiJKwCwIQSPW0ATjEKsCc0Vpm894,2342
pip/_internal/resolution/resolvelib/candidates.py,sha256=RHo9r9g25FWzufKv93Ti9nS4hvAPUrhAjSDL7GCZFNQ,20339
pip/_internal/resolution/resolvelib/factory.py,sha256=--ahYsr-r9zIhdyJJ1ZuETgaQrWiPIqwILWiMDn1IIU,17169
pip/_internal/resolution/resolvelib/provider.py,sha256=BP8nh07Z1FlcT-Iaw4FblRM-DjUeUkiItKdKARYeM6M,6134
pip/_internal/resolution/resolvelib/requirements.py,sha256=lGvoHRhkusRfaz4cFxYBoQNqxS6TeuO3K68qlui6g-0,4511
pip/_internal/resolution/resolvelib/resolver.py,sha256=kI8g0NVlYIsDMRmDplWQdox6WO-0H7CI2wN-1ixnaew,10149
pip/_internal/self_outdated_check.py,sha256=q6_nqUHPpt-DScwD97h7FCSqd4nI1s-xkpOI4I5Za3Y,6779
pip/_internal/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_internal/utils/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/utils/__pycache__/appdirs.cpython-38.pyc,,
pip/_internal/utils/__pycache__/compat.cpython-38.pyc,,
pip/_internal/utils/__pycache__/compatibility_tags.cpython-38.pyc,,
pip/_internal/utils/__pycache__/datetime.cpython-38.pyc,,
pip/_internal/utils/__pycache__/deprecation.cpython-38.pyc,,
pip/_internal/utils/__pycache__/direct_url_helpers.cpython-38.pyc,,
pip/_internal/utils/__pycache__/distutils_args.cpython-38.pyc,,
@ -220,6 +223,7 @@ pip/_internal/utils/__pycache__/logging.cpython-38.pyc,,
pip/_internal/utils/__pycache__/misc.cpython-38.pyc,,
pip/_internal/utils/__pycache__/models.cpython-38.pyc,,
pip/_internal/utils/__pycache__/packaging.cpython-38.pyc,,
pip/_internal/utils/__pycache__/parallel.cpython-38.pyc,,
pip/_internal/utils/__pycache__/pkg_resources.cpython-38.pyc,,
pip/_internal/utils/__pycache__/setuptools_build.cpython-38.pyc,,
pip/_internal/utils/__pycache__/subprocess.cpython-38.pyc,,
@ -230,31 +234,33 @@ pip/_internal/utils/__pycache__/urls.cpython-38.pyc,,
pip/_internal/utils/__pycache__/virtualenv.cpython-38.pyc,,
pip/_internal/utils/__pycache__/wheel.cpython-38.pyc,,
pip/_internal/utils/appdirs.py,sha256=RZzUG-Bkh2b-miX0DSZ3v703_-bgK-v0PfWCCjwVE9g,1349
pip/_internal/utils/compat.py,sha256=ZRJsXMjq373p0US54CUkKRkpLH-ioOM3H3yAhmbUPcs,8898
pip/_internal/utils/compatibility_tags.py,sha256=b2NWEbxfsrB2pBLwJkNVSYUrIAsumQ2IWDoNabbwLPs,5492
pip/_internal/utils/compat.py,sha256=GoCSUMoUmTGeg5irQGLDZ7v12As87yHrMzBXEke-njg,8865
pip/_internal/utils/compatibility_tags.py,sha256=EtBJj-pstj_U0STUZ8FjlG7YDTjuRZUy6GY1cM86yv8,5439
pip/_internal/utils/datetime.py,sha256=KL-vIdGU9JIpGB5NYkmwXWkH-G_2mvvABlmRtoSZsao,295
pip/_internal/utils/deprecation.py,sha256=pBnNogoA4UGTxa_JDnPXBRRYpKMbExAhXpBwAwklOBs,3318
pip/_internal/utils/direct_url_helpers.py,sha256=bZCBNwPQVyZpYGjX_VcomvVvRHvKw-9JzEV-Ft09LQc,4359
pip/_internal/utils/distutils_args.py,sha256=a56mblNxk9BGifbpEETG61mmBrqhjtjRkJ4HYn-oOEE,1350
pip/_internal/utils/encoding.py,sha256=hxZz0t3Whw3d4MHQEiofxalTlfKwxFdLc8fpeGfhKo8,1320
pip/_internal/utils/encoding.py,sha256=wHDJ25yCT_T4ySscCL3P978OpLrfDCpitg8D64IEXMY,1284
pip/_internal/utils/entrypoints.py,sha256=vHcNpnksCv6mllihU6hfifdsKPEjwcaJ1aLIXEaynaU,1152
pip/_internal/utils/filesystem.py,sha256=fqpFwT280152rlX1RjJqjoLp_MXVA8HzKTtDmsl15Ps,6813
pip/_internal/utils/filesystem.py,sha256=-fU3XteCAIJwf_9FvCZU7vhywvt3nuf_cqkCdwgy1Y8,6943
pip/_internal/utils/filetypes.py,sha256=R2FwzoeX7b-rZALOXx5cuO8VPPMhUQ4ne7wm3n3IcWA,571
pip/_internal/utils/glibc.py,sha256=LOeNGgawCKS-4ke9fii78fwXD73dtNav3uxz1Bf-Ab8,3297
pip/_internal/utils/hashes.py,sha256=LQVOt2LTWAdBJH6WPim1YGdF0J-0AfBBLghIDlY1-80,3986
pip/_internal/utils/hashes.py,sha256=xHmrqNwC1eBN0oY0R_RXLJLXGvFdo5gwmbz_pas94k8,4358
pip/_internal/utils/inject_securetransport.py,sha256=M17ZlFVY66ApgeASVjKKLKNz0LAfk-SyU0HZ4ZB6MmI,810
pip/_internal/utils/logging.py,sha256=YIfuDUEkmdn9cIRQ_Ec8rgXs1m5nOwDECtZqM4CBH5U,13093
pip/_internal/utils/misc.py,sha256=cK17YkNfEccS9AuH6Xc9kYQxE0DPNgb-ULh8QgKPr8c,26195
pip/_internal/utils/models.py,sha256=IA0hw_T4awQzui0kqfIEASm5yLtgZAB08ag59Nip5G8,1148
pip/_internal/utils/misc.py,sha256=QQZWMJkKKADPSWQYmrwlasc8b03eCcghn0yDNprYgrI,28001
pip/_internal/utils/models.py,sha256=HqiBVtTbW_b_Umvj2fjhDWOHo2RKhPwSz4iAYkQZ688,1201
pip/_internal/utils/packaging.py,sha256=VtiwcAAL7LBi7tGL2je7LeW4bE11KMHGCsJ1NZY5XtM,3035
pip/_internal/utils/parallel.py,sha256=7az3aaTMCkqpaLFbpYYOvk0rj7Hu5YH1NPXXomqjgf4,3404
pip/_internal/utils/pkg_resources.py,sha256=ZX-k7V5q_aNWyDse92nN7orN1aCpRLsaxzpkBZ1XKzU,1254
pip/_internal/utils/setuptools_build.py,sha256=E1KswI7wfNnCDE5R6G8c9ZbByENpu7NqocjY26PCQDw,5058
pip/_internal/utils/subprocess.py,sha256=vI2QWpNDqM-dkn-z8i1Yrfxnn5sYniPeWn6FhTxX4dY,9902
pip/_internal/utils/temp_dir.py,sha256=H8yUBrRWqTM83cuUu7jVvw_xKL9eZtg_IIbXQtjMLlA,8185
pip/_internal/utils/subprocess.py,sha256=UkPe89gcjxBMx73uutoeJXgD3kwdlL6YO16BkjDdVSI,9924
pip/_internal/utils/temp_dir.py,sha256=blmG0jEvEgdxbYUt_V15bgcTIJIrxZwAw8QZlCTJYDE,8378
pip/_internal/utils/typing.py,sha256=xkYwOeHlf4zsHXBDC4310HtEqwhQcYXFPq2h35Tcrl0,1401
pip/_internal/utils/unpacking.py,sha256=M944JTSiapBOSKLWu7lbawpVHSE7flfzZTEr3TAG7v8,9438
pip/_internal/utils/unpacking.py,sha256=YFAckhqqvmehA8Kan5vd3b1kN_9TafqmOk4b-yz4fho,9488
pip/_internal/utils/urls.py,sha256=q2rw1kMiiig_XZcoyJSsWMJQqYw-2wUmrMoST4mCW_I,1527
pip/_internal/utils/virtualenv.py,sha256=iVJ8ZlbNtGon6I4uZFsY2SidrUf1vt3YHrgS5CuU98w,3553
pip/_internal/utils/wheel.py,sha256=ofsZEN35YhSxRYC4gfzpTtqa_UQ8GF1tl4jtyUdd0gU,7306
pip/_internal/utils/virtualenv.py,sha256=fNGrRp-8QmNL5OzXajBd-z7PbwOsx1XY6G-AVMAhfso,3706
pip/_internal/utils/wheel.py,sha256=wFzn3h8GqYvgsyWPZtUyn0Rb3MJzmtyP3owMOhKnmL0,7303
pip/_internal/vcs/__init__.py,sha256=viJxJRqRE_mVScum85bgQIXAd6o0ozFt18VpC-qIJrM,617
pip/_internal/vcs/__pycache__/__init__.cpython-38.pyc,,
pip/_internal/vcs/__pycache__/bazaar.cpython-38.pyc,,
@ -262,13 +268,13 @@ pip/_internal/vcs/__pycache__/git.cpython-38.pyc,,
pip/_internal/vcs/__pycache__/mercurial.cpython-38.pyc,,
pip/_internal/vcs/__pycache__/subversion.cpython-38.pyc,,
pip/_internal/vcs/__pycache__/versioncontrol.cpython-38.pyc,,
pip/_internal/vcs/bazaar.py,sha256=84q1-kj1_nJ9AMzMu8RmMp-riRZu81M7K9kowcYgi3U,3957
pip/_internal/vcs/git.py,sha256=wlvvVT-hPRwCvkihEoOCZmkCzMzosmV43_DTPvEVA_M,14165
pip/_internal/vcs/mercurial.py,sha256=wVdmoFH-RYoaxjtuAqw40b0daMPX-Fr_26W1ME_9HZU,5347
pip/_internal/vcs/subversion.py,sha256=6shByxeASetbM7WCj6WNoPcuLfBK65DoOEqbkSiWPAI,12331
pip/_internal/vcs/versioncontrol.py,sha256=RtSrHr96CynqXYBQIC61cVY_9C0e7hk8dXUV-BpHmpI,23591
pip/_internal/wheel_builder.py,sha256=p9ZFawfCR1GXchTRY6oq7Qx5enLLjs2SouafNFNsAAE,9590
pip/_vendor/__init__.py,sha256=Tfcbsek_rpFZWMnYp6vzGpWHsmiwBGYOmInUX1NGJp4,4788
pip/_internal/vcs/bazaar.py,sha256=5rRR02uDZTLaxQT-R5Obd8FZDOMlShqYds-pwVSJJs8,3887
pip/_internal/vcs/git.py,sha256=kvB729wrKY0OWMSgOS1pUly4LosZp8utrd3kOQsWalA,13985
pip/_internal/vcs/mercurial.py,sha256=FzCGmYzVZvB-vyM73fKcQk2B4jMNXGnXlQ2bJ7nmglM,5162
pip/_internal/vcs/subversion.py,sha256=rldcn9ZDt5twjNPzFn_FKRn4qdfkjlxHMJEsR2MFfoA,12399
pip/_internal/vcs/versioncontrol.py,sha256=WpxeTRC0NoGB2uXJdmfq4pPxY-p7sk1rV_WkxMxgzQA,25966
pip/_internal/wheel_builder.py,sha256=6w1VPXrpUvCCPlV0cI1wNaCqNz4laF6B6whvaxl9cns,9522
pip/_vendor/__init__.py,sha256=CsxnpYPbi_2agrDI79iQrCmQeZRcwwIF0C6cm_1RynU,4588
pip/_vendor/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/__pycache__/appdirs.cpython-38.pyc,,
pip/_vendor/__pycache__/contextlib2.cpython-38.pyc,,
@ -277,8 +283,7 @@ pip/_vendor/__pycache__/ipaddress.cpython-38.pyc,,
pip/_vendor/__pycache__/pyparsing.cpython-38.pyc,,
pip/_vendor/__pycache__/retrying.cpython-38.pyc,,
pip/_vendor/__pycache__/six.cpython-38.pyc,,
pip/_vendor/__pycache__/toml.cpython-38.pyc,,
pip/_vendor/appdirs.py,sha256=pYg72GhKgkVzkPxZNFUSIzMF3tAPWBgIPoQE8jgVftg,25888
pip/_vendor/appdirs.py,sha256=M6IYRJtdZgmSPCXCSMBRB0VT3P8MdFbWCDbSLrB2Ebg,25907
pip/_vendor/cachecontrol/__init__.py,sha256=pJtAaUxOsMPnytI1A3juAJkXYDr8krdSnsg4Yg3OBEg,302
pip/_vendor/cachecontrol/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-38.pyc,,
@ -305,13 +310,13 @@ pip/_vendor/cachecontrol/filewrapper.py,sha256=vACKO8Llzu_ZWyjV1Fxn1MA4TGU60N5N3
pip/_vendor/cachecontrol/heuristics.py,sha256=BFGHJ3yQcxvZizfo90LLZ04T_Z5XSCXvFotrp7Us0sc,4070
pip/_vendor/cachecontrol/serialize.py,sha256=vIa4jvq4x_KSOLdEIedoknX2aXYHQujLDFV4-F21Dno,7091
pip/_vendor/cachecontrol/wrapper.py,sha256=5LX0uJwkNQUtYSEw3aGmGu9WY8wGipd81mJ8lG0d0M4,690
pip/_vendor/certifi/__init__.py,sha256=AOqspvggP_F62Q_4UmJAhx5rZkoRHoRYBE3SCnSJzAk,64
pip/_vendor/certifi/__init__.py,sha256=u1E_DrSGj_nnEkK5VglvEqP8D80KpghLVWL0A_pq41A,62
pip/_vendor/certifi/__main__.py,sha256=1k3Cr95vCxxGRGDljrW3wMdpZdL3Nhf0u1n-k2qdsCY,255
pip/_vendor/certifi/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/certifi/__pycache__/__main__.cpython-38.pyc,,
pip/_vendor/certifi/__pycache__/core.cpython-38.pyc,,
pip/_vendor/certifi/cacert.pem,sha256=hwBo73gFnF0BKiP3FQKfG32xkGDhxl4SwCQiH2rDKr0,284099
pip/_vendor/certifi/core.py,sha256=c8hoNcYWz6rQo2VAM5d0mcEWiewCvQuOUbCwJz49vhQ,792
pip/_vendor/certifi/cacert.pem,sha256=GhT24f0R7_9y4YY_hkXwkO7BthZhRGDCEMO348E9S14,282394
pip/_vendor/certifi/core.py,sha256=jBrwKEWpG0IKcuozK0BQ2HHGp8adXAOyBPC7ddgR6vM,2315
pip/_vendor/chardet/__init__.py,sha256=YsP5wQlsHJ2auF1RZJfypiSrCA7_bQiRm3ES_NI76-Y,1559
pip/_vendor/chardet/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/chardet/__pycache__/big5freq.cpython-38.pyc,,
@ -407,7 +412,7 @@ pip/_vendor/colorama/initialise.py,sha256=PprovDNxMTrvoNHFcL2NZjpH2XzDc8BLxLxiEr
pip/_vendor/colorama/win32.py,sha256=bJ8Il9jwaBN5BJ8bmN6FoYZ1QYuMKv2j8fGrXh7TJjw,5404
pip/_vendor/colorama/winterm.py,sha256=2y_2b7Zsv34feAsP67mLOVc-Bgq51mdYGo571VprlrM,6438
pip/_vendor/contextlib2.py,sha256=5HjGflUzwWAUfcILhSmC2GqvoYdZZzFzVfIDztHigUs,16915
pip/_vendor/distlib/__init__.py,sha256=gzl1hjUXmDGrqRyU7ZLjBwJGAcMimQbrZ22XPVaKaRE,581
pip/_vendor/distlib/__init__.py,sha256=3veAk2rPznOB2gsK6tjbbh0TQMmGE5P82eE9wXq6NIk,581
pip/_vendor/distlib/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/distlib/__pycache__/compat.cpython-38.pyc,,
pip/_vendor/distlib/__pycache__/database.cpython-38.pyc,,
@ -428,28 +433,28 @@ pip/_vendor/distlib/_backport/__pycache__/shutil.cpython-38.pyc,,
pip/_vendor/distlib/_backport/__pycache__/sysconfig.cpython-38.pyc,,
pip/_vendor/distlib/_backport/__pycache__/tarfile.cpython-38.pyc,,
pip/_vendor/distlib/_backport/misc.py,sha256=KWecINdbFNOxSOP1fGF680CJnaC6S4fBRgEtaYTw0ig,971
pip/_vendor/distlib/_backport/shutil.py,sha256=VW1t3uYqUjWZH7jV-6QiimLhnldoV5uIpH4EuiT1jfw,25647
pip/_vendor/distlib/_backport/shutil.py,sha256=IX_G2NPqwecJibkIDje04bqu0xpHkfSQ2GaGdEVqM5Y,25707
pip/_vendor/distlib/_backport/sysconfig.cfg,sha256=swZKxq9RY5e9r3PXCrlvQPMsvOdiWZBTHLEbqS8LJLU,2617
pip/_vendor/distlib/_backport/sysconfig.py,sha256=BQHFlb6pubCl_dvT1NjtzIthylofjKisox239stDg0U,26854
pip/_vendor/distlib/_backport/tarfile.py,sha256=Ihp7rXRcjbIKw8COm9wSePV9ARGXbSF9gGXAMn2Q-KU,92628
pip/_vendor/distlib/compat.py,sha256=xdNZmqFN5HwF30HjRn5M415pcC2kgXRBXn767xS8v-M,41404
pip/_vendor/distlib/database.py,sha256=fhNzEDtb4HXrpxKyQvhVzDXcOiJlzrOM--UYnvCeZrI,51045
pip/_vendor/distlib/compat.py,sha256=ADA56xiAxar3mU6qemlBhNbsrFPosXRhO44RzsbJPqk,41408
pip/_vendor/distlib/database.py,sha256=Kl0YvPQKc4OcpVi7k5cFziydM1xOK8iqdxLGXgbZHV4,51059
pip/_vendor/distlib/index.py,sha256=SXKzpQCERctxYDMp_OLee2f0J0e19ZhGdCIoMlUfUQM,21066
pip/_vendor/distlib/locators.py,sha256=c9E4cDEacJ_uKbuE5BqAVocoWp6rsuBGTkiNDQq3zV4,52100
pip/_vendor/distlib/manifest.py,sha256=nQEhYmgoreaBZzyFzwYsXxJARu3fo4EkunU163U16iE,14811
pip/_vendor/distlib/markers.py,sha256=6Ac3cCfFBERexiESWIOXmg-apIP8l2esafNSX3KMy-8,4387
pip/_vendor/distlib/metadata.py,sha256=OhbCKmf5lswE8unWBopI1hj7tRpHp4ZbFvU4d6aAEMM,40234
pip/_vendor/distlib/metadata.py,sha256=z2KPy3h3tcDnb9Xs7nAqQ5Oz0bqjWAUFmKWcFKRoodg,38962
pip/_vendor/distlib/resources.py,sha256=2FGv0ZHF14KXjLIlL0R991lyQQGcewOS4mJ-5n-JVnc,10766
pip/_vendor/distlib/scripts.py,sha256=OAkEwxRvIzX-VSfhEttQEKJFVLA47gbW0OgQXJRs7OQ,16998
pip/_vendor/distlib/scripts.py,sha256=_MAj3sMuv56kuM8FsiIWXqbT0gmumPGaOR_atOzn4a4,17180
pip/_vendor/distlib/t32.exe,sha256=NS3xBCVAld35JVFNmb-1QRyVtThukMrwZVeXn4LhaEQ,96768
pip/_vendor/distlib/t64.exe,sha256=oAqHes78rUWVM0OtVqIhUvequl_PKhAhXYQWnUf7zR0,105984
pip/_vendor/distlib/util.py,sha256=f2jZCPrcLCt6LcnC0gUy-Fur60tXD8reA7k4rDpHMDw,59845
pip/_vendor/distlib/version.py,sha256=_n7F6juvQGAcn769E_SHa7fOcf5ERlEVymJ_EjPRwGw,23391
pip/_vendor/distlib/w32.exe,sha256=lJtnZdeUxTZWya_EW5DZos_K5rswRECGspIl8ZJCIXs,90112
pip/_vendor/distlib/w64.exe,sha256=0aRzoN2BO9NWW4ENy4_4vHkHR4qZTFZNVSAJJYlODTI,99840
pip/_vendor/distlib/wheel.py,sha256=bRtR5bNR_u_DwkwktN1bgZuwLVOJT1p_vNIUPyN8kJc,40452
pip/_vendor/distlib/wheel.py,sha256=v6DnwTqhNHwrEVFr8_YeiTW6G4ftP_evsywNgrmdb2o,41144
pip/_vendor/distro.py,sha256=xxMIh2a3KmippeWEHzynTdHT3_jZM0o-pos0dAWJROM,43628
pip/_vendor/html5lib/__init__.py,sha256=Ztrn7UvF-wIFAgRBBa0ML-Gu5AffH3BPX_INJx4SaBI,1162
pip/_vendor/html5lib/__init__.py,sha256=BYzcKCqeEii52xDrqBFruhnmtmkiuHXFyFh-cglQ8mk,1160
pip/_vendor/html5lib/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/html5lib/__pycache__/_ihatexml.cpython-38.pyc,,
pip/_vendor/html5lib/__pycache__/_inputstream.cpython-38.pyc,,
@ -458,19 +463,17 @@ pip/_vendor/html5lib/__pycache__/_utils.cpython-38.pyc,,
pip/_vendor/html5lib/__pycache__/constants.cpython-38.pyc,,
pip/_vendor/html5lib/__pycache__/html5parser.cpython-38.pyc,,
pip/_vendor/html5lib/__pycache__/serializer.cpython-38.pyc,,
pip/_vendor/html5lib/_ihatexml.py,sha256=3LBtJMlzgwM8vpQiU1TvGmEEmNH72sV0yD8yS53y07A,16705
pip/_vendor/html5lib/_inputstream.py,sha256=bPUWcAfJScK4xkjQQaG_HsI2BvEVbFvI0AsodDYPQj0,32552
pip/_vendor/html5lib/_tokenizer.py,sha256=YAaOEBD6qc5ISq9Xt9Nif1OFgcybTTfMdwqBkZhpAq4,76580
pip/_vendor/html5lib/_trie/__init__.py,sha256=8VR1bcgD2OpeS2XExpu5yBhP_Q1K-lwKbBKICBPf1kU,289
pip/_vendor/html5lib/_ihatexml.py,sha256=ifOwF7pXqmyThIXc3boWc96s4MDezqRrRVp7FwDYUFs,16728
pip/_vendor/html5lib/_inputstream.py,sha256=jErNASMlkgs7MpOM9Ve_VdLDJyFFweAjLuhVutZz33U,32353
pip/_vendor/html5lib/_tokenizer.py,sha256=04mgA2sNTniutl2fxFv-ei5bns4iRaPxVXXHh_HrV_4,77040
pip/_vendor/html5lib/_trie/__init__.py,sha256=nqfgO910329BEVJ5T4psVwQtjd2iJyEXQ2-X8c1YxwU,109
pip/_vendor/html5lib/_trie/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/html5lib/_trie/__pycache__/_base.cpython-38.pyc,,
pip/_vendor/html5lib/_trie/__pycache__/datrie.cpython-38.pyc,,
pip/_vendor/html5lib/_trie/__pycache__/py.cpython-38.pyc,,
pip/_vendor/html5lib/_trie/_base.py,sha256=CaybYyMro8uERQYjby2tTeSUatnWDfWroUN9N7ety5w,1013
pip/_vendor/html5lib/_trie/datrie.py,sha256=EQpqSfkZRuTbE-DuhW7xMdVDxdZNZ0CfmnYfHA_3zxM,1178
pip/_vendor/html5lib/_trie/py.py,sha256=wXmQLrZRf4MyWNyg0m3h81m9InhLR7GJ002mIIZh-8o,1775
pip/_vendor/html5lib/_utils.py,sha256=ismpASeqa2jqEPQjHUj8vReAf7yIoKnvLN5fuOw6nv0,4015
pip/_vendor/html5lib/constants.py,sha256=4lmZWLtEPRLnl8NzftOoYTJdo6jpeMtP6dqQC0g_bWQ,83518
pip/_vendor/html5lib/_utils.py,sha256=Dx9AKntksRjFT1veBj7I362pf5OgIaT0zglwq43RnfU,4931
pip/_vendor/html5lib/constants.py,sha256=Ll-yzLU_jcjyAI_h57zkqZ7aQWE5t5xA4y_jQgoUUhw,83464
pip/_vendor/html5lib/filters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/html5lib/filters/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/html5lib/filters/__pycache__/alphabeticalattributes.cpython-38.pyc,,
@ -485,10 +488,10 @@ pip/_vendor/html5lib/filters/base.py,sha256=z-IU9ZAYjpsVsqmVt7kuWC63jR11hDMr6CVr
pip/_vendor/html5lib/filters/inject_meta_charset.py,sha256=egDXUEHXmAG9504xz0K6ALDgYkvUrC2q15YUVeNlVQg,2945
pip/_vendor/html5lib/filters/lint.py,sha256=jk6q56xY0ojiYfvpdP-OZSm9eTqcAdRqhCoPItemPYA,3643
pip/_vendor/html5lib/filters/optionaltags.py,sha256=8lWT75J0aBOHmPgfmqTHSfPpPMp01T84NKu0CRedxcE,10588
pip/_vendor/html5lib/filters/sanitizer.py,sha256=4ON02KNjuqda1lCw5_JCUZxb0BzWR5M7ON84dtJ7dm0,26248
pip/_vendor/html5lib/filters/sanitizer.py,sha256=m6oGmkBhkGAnn2nV6D4hE78SCZ6WEnK9rKdZB3uXBIc,26897
pip/_vendor/html5lib/filters/whitespace.py,sha256=8eWqZxd4UC4zlFGW6iyY6f-2uuT8pOCSALc3IZt7_t4,1214
pip/_vendor/html5lib/html5parser.py,sha256=g5g2ezkusHxhi7b23vK_-d6K6BfIJRbqIQmvQ9z4EgI,118963
pip/_vendor/html5lib/serializer.py,sha256=yfcfBHse2wDs6ojxn-kieJjLT5s1ipilQJ0gL3-rJis,15758
pip/_vendor/html5lib/html5parser.py,sha256=anr-aXre_ImfrkQ35c_rftKXxC80vJCREKe06Tq15HA,117186
pip/_vendor/html5lib/serializer.py,sha256=_PpvcZF07cwE7xr9uKkZqh5f4UEaI8ltCU2xPJzaTpk,15759
pip/_vendor/html5lib/treeadapters/__init__.py,sha256=A0rY5gXIe4bJOiSGRO_j_tFhngRBO8QZPzPtPw5dFzo,679
pip/_vendor/html5lib/treeadapters/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/html5lib/treeadapters/__pycache__/genshi.cpython-38.pyc,,
@ -501,11 +504,11 @@ pip/_vendor/html5lib/treebuilders/__pycache__/base.cpython-38.pyc,,
pip/_vendor/html5lib/treebuilders/__pycache__/dom.cpython-38.pyc,,
pip/_vendor/html5lib/treebuilders/__pycache__/etree.cpython-38.pyc,,
pip/_vendor/html5lib/treebuilders/__pycache__/etree_lxml.cpython-38.pyc,,
pip/_vendor/html5lib/treebuilders/base.py,sha256=wQGp5yy22TNG8tJ6aREe4UUeTR7A99dEz0BXVaedWb4,14579
pip/_vendor/html5lib/treebuilders/base.py,sha256=z-o51vt9r_l2IDG5IioTOKGzZne4Fy3_Fc-7ztrOh4I,14565
pip/_vendor/html5lib/treebuilders/dom.py,sha256=22whb0C71zXIsai5mamg6qzBEiigcBIvaDy4Asw3at0,8925
pip/_vendor/html5lib/treebuilders/etree.py,sha256=aqIBOGj_dFYqBURIcTegGNBhAIJOw5iFDHb4jrkYH-8,12764
pip/_vendor/html5lib/treebuilders/etree_lxml.py,sha256=9V0dXxbJYYq-Skgb5-_OL2NkVYpjioEb4CHajo0e9yI,14122
pip/_vendor/html5lib/treewalkers/__init__.py,sha256=yhXxHpjlSqfQyUag3v8-vWjMPriFBU8YRAPNpDgBTn8,5714
pip/_vendor/html5lib/treebuilders/etree.py,sha256=w5ZFpKk6bAxnrwD2_BrF5EVC7vzz0L3LMi9Sxrbc_8w,12836
pip/_vendor/html5lib/treebuilders/etree_lxml.py,sha256=9gqDjs-IxsPhBYa5cpvv2FZ1KZlG83Giusy2lFmvIkE,14766
pip/_vendor/html5lib/treewalkers/__init__.py,sha256=OBPtc1TU5mGyy18QDMxKEyYEz0wxFUUNj5v0-XgmYhY,5719
pip/_vendor/html5lib/treewalkers/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/html5lib/treewalkers/__pycache__/base.cpython-38.pyc,,
pip/_vendor/html5lib/treewalkers/__pycache__/dom.cpython-38.pyc,,
@ -514,8 +517,8 @@ pip/_vendor/html5lib/treewalkers/__pycache__/etree_lxml.cpython-38.pyc,,
pip/_vendor/html5lib/treewalkers/__pycache__/genshi.cpython-38.pyc,,
pip/_vendor/html5lib/treewalkers/base.py,sha256=ouiOsuSzvI0KgzdWP8PlxIaSNs9falhbiinAEc_UIJY,7476
pip/_vendor/html5lib/treewalkers/dom.py,sha256=EHyFR8D8lYNnyDU9lx_IKigVJRyecUGua0mOi7HBukc,1413
pip/_vendor/html5lib/treewalkers/etree.py,sha256=sz1o6mmE93NQ53qJFDO7HKyDtuwgK-Ay3qSFZPC6u00,4550
pip/_vendor/html5lib/treewalkers/etree_lxml.py,sha256=sY6wfRshWTllu6n48TPWpKsQRPp-0CQrT0hj_AdzHSU,6309
pip/_vendor/html5lib/treewalkers/etree.py,sha256=xo1L5m9VtkfpFJK0pFmkLVajhqYYVisVZn3k9kYpPkI,4551
pip/_vendor/html5lib/treewalkers/etree_lxml.py,sha256=_b0LAVWLcVu9WaU_-w3D8f0IRSpCbjf667V-3NRdhTw,6357
pip/_vendor/html5lib/treewalkers/genshi.py,sha256=4D2PECZ5n3ZN3qu3jMl9yY7B81jnQApBQSVlfaIuYbA,2309
pip/_vendor/idna/__init__.py,sha256=9Nt7xpyet3DmOrPUGooDdAwmHZZu1qUAy2EaJ93kGiQ,58
pip/_vendor/idna/__pycache__/__init__.cpython-38.pyc,,
@ -528,11 +531,11 @@ pip/_vendor/idna/__pycache__/package_data.cpython-38.pyc,,
pip/_vendor/idna/__pycache__/uts46data.cpython-38.pyc,,
pip/_vendor/idna/codec.py,sha256=lvYb7yu7PhAqFaAIAdWcwgaWI2UmgseUua-1c0AsG0A,3299
pip/_vendor/idna/compat.py,sha256=R-h29D-6mrnJzbXxymrWUW7iZUvy-26TQwZ0ij57i4U,232
pip/_vendor/idna/core.py,sha256=Hy1RkJrrIQWW8kicqZ8vQT_GreYlAhkfopjESSzL3wk,11844
pip/_vendor/idna/idnadata.py,sha256=p1_KeD9BqT-sDGqMcGxhBWAOrYNrPxj5YvHya0ImFbU,41201
pip/_vendor/idna/core.py,sha256=jCoaLb3bA2tS_DDx9PpGuNTEZZN2jAzB369aP-IHYRE,11951
pip/_vendor/idna/idnadata.py,sha256=gmzFwZWjdms3kKZ_M_vwz7-LP_SCgYfSeE03B21Qpsk,42350
pip/_vendor/idna/intranges.py,sha256=TY1lpxZIQWEP6tNqjZkFA5hgoMWOj1OBmnUG8ihT87E,1749
pip/_vendor/idna/package_data.py,sha256=IjspS_rQQ_0HCGc0CaNhn3NXl3ohvRg7-_P0gAaSc-o,21
pip/_vendor/idna/uts46data.py,sha256=w9d1B5OESLSgr2tMx0svwoPBi0Qj0_7HRyL1Vq5axwg,201192
pip/_vendor/idna/package_data.py,sha256=bxBjpLnE06_1jSYKEy5svOMu1zM3OMztXVUb1tPlcp0,22
pip/_vendor/idna/uts46data.py,sha256=lMdw2zdjkH1JUWXPPEfFUSYT3Fyj60bBmfLvvy5m7ko,202084
pip/_vendor/ipaddress.py,sha256=-0RmurI31XgAaN20WCi0zrcuoat90nNA70_6yGlx2PU,79875
pip/_vendor/msgpack/__init__.py,sha256=2gJwcsTIaAtCM0GMi2rU-_Y6kILeeQuqRkrQ22jSANc,1118
pip/_vendor/msgpack/__pycache__/__init__.cpython-38.pyc,,
@ -544,7 +547,7 @@ pip/_vendor/msgpack/_version.py,sha256=hu7lzmZ_ClOaOOmRsWb4xomhzQ4UIsLsvv8KY6Uys
pip/_vendor/msgpack/exceptions.py,sha256=dCTWei8dpkrMsQDcjQk74ATl9HsIBH0ybt8zOPNqMYc,1081
pip/_vendor/msgpack/ext.py,sha256=nV19BzE9Be8SJHrxxYJHFbvEHJaXcP3avRkHVp5wovM,6034
pip/_vendor/msgpack/fallback.py,sha256=Z8V3iYUUPqKVy4WWTk64Vq3G0PylQIOmlWvgnMhmkdU,37133
pip/_vendor/packaging/__about__.py,sha256=y-K51xPSysxvOfTjAb074yqOZfDeX5qSID0ZEbEb9cE,744
pip/_vendor/packaging/__about__.py,sha256=PNMsaZn4UcCHyubgROH1bl6CluduPjI5kFrSp_Zgklo,736
pip/_vendor/packaging/__init__.py,sha256=6enbp5XgRfjBjsI9-bn00HjHf5TH21PDMOKkJW8xw-w,562
pip/_vendor/packaging/__pycache__/__about__.cpython-38.pyc,,
pip/_vendor/packaging/__pycache__/__init__.cpython-38.pyc,,
@ -557,15 +560,15 @@ pip/_vendor/packaging/__pycache__/specifiers.cpython-38.pyc,,
pip/_vendor/packaging/__pycache__/tags.cpython-38.pyc,,
pip/_vendor/packaging/__pycache__/utils.cpython-38.pyc,,
pip/_vendor/packaging/__pycache__/version.cpython-38.pyc,,
pip/_vendor/packaging/_compat.py,sha256=Z-PwchK0cREbaRGF5MZP8LEv8JkC-qydn2FRrtjeixk,1138
pip/_vendor/packaging/_compat.py,sha256=MXdsGpSE_W-ZrHoC87andI4LV2FAwU7HLL-eHe_CjhU,1128
pip/_vendor/packaging/_structures.py,sha256=ozkCX8Q8f2qE1Eic3YiQ4buDVfgz2iYevY9e7R2y3iY,2022
pip/_vendor/packaging/_typing.py,sha256=-cq_iNeveAWCVoseVvqmknWLbvZ_i9g7BeZBo0ShtHg,1449
pip/_vendor/packaging/markers.py,sha256=yap5bk3c8QyPuGtiVbQSYhN70bxWj1nLDv2ZuaCLq7g,9501
pip/_vendor/packaging/requirements.py,sha256=G43p2ylM_REg87RLG9JybjbdwfaPyzaKYRtllRfNdrM,4913
pip/_vendor/packaging/specifiers.py,sha256=Nz8bnFp53cQInmRGZy50QXlIi2tkDXMfRuGyGps2IRE,31314
pip/_vendor/packaging/tags.py,sha256=SCrw-jC3h0ymam6QXDX5ZqgvRcMNq_cQD55gFnT56Xg,23704
pip/_vendor/packaging/utils.py,sha256=v5Wk8B7gUL13Rzed6NNhCZlutPQT7jNV-7hr-WOtacU,1700
pip/_vendor/packaging/version.py,sha256=qRdNN0_XuPFOJ3fut8ehzxJrNYtBzqF8ZtagEvgNUUM,15480
pip/_vendor/packaging/_typing.py,sha256=VgA0AAvsc97KB5nF89zoudOyCMEsV7FlaXzZbYqEkzA,1824
pip/_vendor/packaging/markers.py,sha256=V_RdoQqOUbSfy7y9o2vRk7BkzAh3yneC82cuWpKrqOg,9491
pip/_vendor/packaging/requirements.py,sha256=F93hkn7i8NKRZP-FtdTIlhz1PUsRjhe6eRbsBXX0Uh4,4903
pip/_vendor/packaging/specifiers.py,sha256=uYp9l13F0LcknS6d4N60ytiBgFmIhKideOq9AnsxTco,31944
pip/_vendor/packaging/tags.py,sha256=NKMS37Zo_nWrZxgsD6zbXsXgc9edn9m160cBiLmHJdE,24067
pip/_vendor/packaging/utils.py,sha256=RShlvnjO2CtYSD8uri32frMMFMTmB-3ihsq1-ghzLEw,1811
pip/_vendor/packaging/version.py,sha256=Cnbm-OO9D_qd8ZTFxzFcjSavexSYFZmyeaoPvMsjgPc,15470
pip/_vendor/pep517/__init__.py,sha256=r5uA106NGJa3slspaD2m32aFpFUiZX-mZ9vIlzAEOp4,84
pip/_vendor/pep517/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/pep517/__pycache__/_in_process.cpython-38.pyc,,
@ -599,7 +602,7 @@ pip/_vendor/progress/bar.py,sha256=QuDuVNcmXgpxtNtxO0Fq72xKigxABaVmxYGBw4J3Z_E,2
pip/_vendor/progress/counter.py,sha256=MznyBrvPWrOlGe4MZAlGUb9q3aODe6_aNYeAE_VNoYA,1372
pip/_vendor/progress/spinner.py,sha256=k8JbDW94T0-WXuXfxZIFhdoNPYp3jfnpXqBnfRv5fGs,1380
pip/_vendor/pyparsing.py,sha256=J1b4z3S_KwyJW7hKGnoN-hXW9pgMIzIP6QThyY5yJq4,273394
pip/_vendor/requests/__init__.py,sha256=DoS7sn6SOs-Vmi3IHdJFfM1RmlONZHmEKO3DGvyWsnY,4080
pip/_vendor/requests/__init__.py,sha256=orzv4-1uejMDc2v3LnTVneINGXiwqXSfrASoFBsYblE,4465
pip/_vendor/requests/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/requests/__pycache__/__version__.cpython-38.pyc,,
pip/_vendor/requests/__pycache__/_internal_utils.cpython-38.pyc,,
@ -618,7 +621,7 @@ pip/_vendor/requests/__pycache__/sessions.cpython-38.pyc,,
pip/_vendor/requests/__pycache__/status_codes.cpython-38.pyc,,
pip/_vendor/requests/__pycache__/structures.cpython-38.pyc,,
pip/_vendor/requests/__pycache__/utils.cpython-38.pyc,,
pip/_vendor/requests/__version__.py,sha256=dpcXABdGo9y3UFFKFU_Wu_YHSa7TBXxCghOju7S8IYs,441
pip/_vendor/requests/__version__.py,sha256=Xwky1FMlMkJJGidBM50JC7FKcosWzkjIW-WhQGrBdFM,441
pip/_vendor/requests/_internal_utils.py,sha256=Zx3PnEUccyfsB-ie11nZVAW8qClJy0gx1qNME7rgT18,1096
pip/_vendor/requests/adapters.py,sha256=e-bmKEApNVqFdylxuMJJfiaHdlmS_zhWhIMEzlHvGuc,21548
pip/_vendor/requests/api.py,sha256=PlHM-HT3PQ5lyufoeGmV-nJxRi7UnUyGVh7OV7B9XV4,6496
@ -626,39 +629,44 @@ pip/_vendor/requests/auth.py,sha256=OMoJIVKyRLy9THr91y8rxysZuclwPB-K1Xg1zBomUhQ,
pip/_vendor/requests/certs.py,sha256=nXRVq9DtGmv_1AYbwjTu9UrgAcdJv05ZvkNeaoLOZxY,465
pip/_vendor/requests/compat.py,sha256=LQWuCR4qXk6w7-qQopXyz0WNHUdAD40k0mKnaAEf1-g,2045
pip/_vendor/requests/cookies.py,sha256=Y-bKX6TvW3FnYlE6Au0SXtVVWcaNdFvuAwQxw-G0iTI,18430
pip/_vendor/requests/exceptions.py,sha256=-mLam3TAx80V09EaH3H-ZxR61eAVuLRZ8zgBBSLjK44,3197
pip/_vendor/requests/exceptions.py,sha256=d9fJJw8YFBB9VzG9qhvxLuOx6be3c_Dwbck-dVUEAcs,3173
pip/_vendor/requests/help.py,sha256=SJPVcoXeo7KfK4AxJN5eFVQCjr0im87tU2n7ubLsksU,3578
pip/_vendor/requests/hooks.py,sha256=QReGyy0bRcr5rkwCuObNakbYsc7EkiKeBwG4qHekr2Q,757
pip/_vendor/requests/models.py,sha256=P7sUBpjV7iIzjpf9rYVzHaOXlxlzJk5ikFP_Nsg6zAU,34278
pip/_vendor/requests/models.py,sha256=_tKIbrscbGvaTdX1UHCwRaiYmPF9VBIuBeydr4Qx1Tg,34287
pip/_vendor/requests/packages.py,sha256=njJmVifY4aSctuW3PP5EFRCxjEwMRDO6J_feG2dKWsI,695
pip/_vendor/requests/sessions.py,sha256=Ju8VnlWZPU_Xr-cMjKXNbs_l2cyanr3Dm9c7fLxprQI,29265
pip/_vendor/requests/sessions.py,sha256=OBtwQs1vjkB1xamFdi_p5y8BVeX16BJoQcwSwx_Y3fI,29316
pip/_vendor/requests/status_codes.py,sha256=gT79Pbs_cQjBgp-fvrUgg1dn2DQO32bDj4TInjnMPSc,4188
pip/_vendor/requests/structures.py,sha256=msAtr9mq1JxHd-JRyiILfdFlpbJwvvFuP3rfUQT_QxE,3005
pip/_vendor/requests/utils.py,sha256=VBs99cvV8Z29WGXeWZqHzZ80_nu1AwwjYzJfe0wQIvs,30176
pip/_vendor/resolvelib/__init__.py,sha256=aBndiGQ3I68Ezdv0fMPQ9ek6ScvwpuQRimxn6wp7pJ4,537
pip/_vendor/resolvelib/__init__.py,sha256=sqMOy4CbVJQiaG9bCPj0oAntGAVy-RWdPfVaC9XDIEQ,537
pip/_vendor/resolvelib/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/resolvelib/__pycache__/providers.cpython-38.pyc,,
pip/_vendor/resolvelib/__pycache__/reporters.cpython-38.pyc,,
pip/_vendor/resolvelib/__pycache__/resolvers.cpython-38.pyc,,
pip/_vendor/resolvelib/__pycache__/structs.cpython-38.pyc,,
pip/_vendor/resolvelib/providers.py,sha256=1JCBbBV0E_Q1o0XOAeAdr2YaKU_zHxO7i_BBLqCLhUc,4913
pip/_vendor/resolvelib/reporters.py,sha256=5gXUn3hRjA4UomD3HHZGreBp23aSjMHgBrC3OgYSpTY,1094
pip/_vendor/resolvelib/resolvers.py,sha256=nTTfBBIMsoxGhqUAyh8GbmR-si_TijpY67wD7aUS264,14481
pip/_vendor/resolvelib/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-38.pyc,,
pip/_vendor/resolvelib/compat/collections_abc.py,sha256=mtTkpr3Gf3OGvU1PD8YuvrJRhVbioxV82T-niFPoX3o,127
pip/_vendor/resolvelib/providers.py,sha256=TZDCmL-Ic-R5JRIZY8G4FLG5xB2343B0DfuK7aw2Yqw,4547
pip/_vendor/resolvelib/reporters.py,sha256=ZPSJnVfK8WvXTbX8jE0Nren0-_Hg9ym4epCUPtU8Y0U,1405
pip/_vendor/resolvelib/resolvers.py,sha256=lQTGcc-2fgHbmdiLzeNDUxVmGc5ZFjkAL6JrVqnqJIw,15018
pip/_vendor/resolvelib/structs.py,sha256=yrdhd-n7DercimPGclXe20rgqhlxw8PnxC0wmcXO19Y,2016
pip/_vendor/retrying.py,sha256=k3fflf5_Mm0XcIJYhB7Tj34bqCCPhUDkYbx1NvW2FPE,9972
pip/_vendor/six.py,sha256=Q6WvEXZ1DGEASAo3CGNCJkKv2tPy8xkSmK-VHE9PYIA,34074
pip/_vendor/toml.py,sha256=D8pTQbgSGge2nDdpsAk8_C7UGJeva0qFq2tGGNaE70Q,35675
pip/_vendor/toml/__init__.py,sha256=CwtvzaEThoIbqNwJJB8BhHoA5clb2_x8V42UF6JAysA,527
pip/_vendor/six.py,sha256=U4Z_yv534W5CNyjY9i8V1OXY2SjAny8y2L5vDLhhThM,34159
pip/_vendor/toml/__init__.py,sha256=rJ1pu933HgUtyeeNiusoPd5jJOPNhaKHhSSld3o8AQo,747
pip/_vendor/toml/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/toml/__pycache__/common.cpython-38.pyc,,
pip/_vendor/toml/__pycache__/decoder.cpython-38.pyc,,
pip/_vendor/toml/__pycache__/encoder.cpython-38.pyc,,
pip/_vendor/toml/__pycache__/ordered.cpython-38.pyc,,
pip/_vendor/toml/__pycache__/tz.cpython-38.pyc,,
pip/_vendor/toml/decoder.py,sha256=8uIcWLLyMFwBGT3fiph98KDx8uF_V2Pf84Qi43pqNf0,35067
pip/_vendor/toml/encoder.py,sha256=Nw1wrNgcUgod1XbOXwxFSDGqO5blgOi07Lp_6kStTgc,8128
pip/_vendor/toml/common.py,sha256=ViBccAduP6eZNJAb1POhRhjOAi56TDsNgWJ1TjgXAug,242
pip/_vendor/toml/decoder.py,sha256=atpXmyFCzNGiqhkcYLySBuJQkPeSHDzBz47sEaX1amw,38696
pip/_vendor/toml/encoder.py,sha256=fPqLyFdPAam17X9SELz2TMp9affkfHCmgWZxRKcmzhY,9955
pip/_vendor/toml/ordered.py,sha256=UWt5Eka90IWVBYdvLgY5PXnkBcVYpHjnw9T67rM85T8,378
pip/_vendor/toml/tz.py,sha256=DrAgI3wZxZiGcLuV_l8ueA_nPrYoxQ3hZA9tJSjWRsQ,618
pip/_vendor/urllib3/__init__.py,sha256=oVOeFuNyEg33ShIefD4qSeGvbb-aJ3otjcdX2iswkLM,2683
pip/_vendor/urllib3/__init__.py,sha256=rdFZCO1L7e8861ZTvo8AiSKwxCe9SnWQUQwJ599YV9c,2683
pip/_vendor/urllib3/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/urllib3/__pycache__/_collections.cpython-38.pyc,,
pip/_vendor/urllib3/__pycache__/connection.cpython-38.pyc,,
@ -670,8 +678,8 @@ pip/_vendor/urllib3/__pycache__/poolmanager.cpython-38.pyc,,
pip/_vendor/urllib3/__pycache__/request.cpython-38.pyc,,
pip/_vendor/urllib3/__pycache__/response.cpython-38.pyc,,
pip/_vendor/urllib3/_collections.py,sha256=GouVsNzwg6jADZTmimMI6oqmwKSswnMo9dh5tGNVWO4,10792
pip/_vendor/urllib3/connection.py,sha256=wHpV1S60bvXWnqEDtdzmHiY9NwIiKeFwQI5VvX7AtqM,14026
pip/_vendor/urllib3/connectionpool.py,sha256=jg_yTHo2B3Zyo4urYVecyg-34v3hxUrH9B401-c4A9w,36513
pip/_vendor/urllib3/connection.py,sha256=Fln8a_bkegdNMkFoSOwyI0PJvL1OqzVUO6ifihKOTpc,14461
pip/_vendor/urllib3/connectionpool.py,sha256=egdaX-Db_LVXifDxv3JY0dHIpQqDv0wC0_9Eeh8FkPM,35725
pip/_vendor/urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-38.pyc,,
@ -689,10 +697,10 @@ pip/_vendor/urllib3/contrib/_securetransport/bindings.py,sha256=mullWYFaghBdRWla
pip/_vendor/urllib3/contrib/_securetransport/low_level.py,sha256=V7GnujxnWZh2N2sMsV5N4d9Imymokkm3zBwgt77_bSE,11956
pip/_vendor/urllib3/contrib/appengine.py,sha256=gfdK4T7CRin7v9HRhHDbDh-Hbk66hHDWeoz7nV3PJo8,11034
pip/_vendor/urllib3/contrib/ntlmpool.py,sha256=a402AwGN_Ll3N-4ur_AS6UrU-ycUtlnYqoBF76lORg8,4160
pip/_vendor/urllib3/contrib/pyopenssl.py,sha256=w35mWy_1POZUsbOhurVb_zhf0C1Jkd79AFlucLs6KuQ,16440
pip/_vendor/urllib3/contrib/securetransport.py,sha256=iKzVUAxKnChsADR5YMwc05oEixXDzAk0xPU0g-rc2z8,32275
pip/_vendor/urllib3/contrib/pyopenssl.py,sha256=9gm5kpC0ScbDCWobeCrh5LDqS8HgU8FNhmk5v8qQ5Bs,16582
pip/_vendor/urllib3/contrib/securetransport.py,sha256=vBDFjSnH2gWa-ztMKVaiwW46K1mlDZKqvo_VAonfdcY,32401
pip/_vendor/urllib3/contrib/socks.py,sha256=nzDMgDIFJWVubKHqvIn2-SKCO91hhJInP92WgHChGzA,7036
pip/_vendor/urllib3/exceptions.py,sha256=P3e-p9_LScyIxX7FoR3wU0A6hZmDqFAVCz2wgI3D0lM,6607
pip/_vendor/urllib3/exceptions.py,sha256=D2Jvab7M7m_n0rnmBmq481paoVT32VvVeB6VeQM0y-w,7172
pip/_vendor/urllib3/fields.py,sha256=kroD76QK-GdHHW7f_AUN4XxDC3OQPI2FFrS9eSL4BCs,8553
pip/_vendor/urllib3/filepost.py,sha256=vj0qbrpT1AFzvvW4SuC8M5kJiw7wftHcSr-7b8UpPpw,2440
pip/_vendor/urllib3/packages/__init__.py,sha256=h4BLhD4tLaBx1adaDtKXfupsgqY0wWLXb_f1_yVlV6A,108
@ -707,9 +715,9 @@ pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py,sha256=ywgKMtfHi1-Dr
pip/_vendor/urllib3/packages/ssl_match_hostname/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/urllib3/packages/ssl_match_hostname/__pycache__/_implementation.cpython-38.pyc,,
pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py,sha256=rvQDQviqQLtPJB6MfEgABnBFj3nXft7ZJ3Dx-BC0AQY,5696
pip/_vendor/urllib3/poolmanager.py,sha256=JYUyBUN3IiEknUdjZ7VJrpCQr6SP7vi0WwSndrn8XpE,17053
pip/_vendor/urllib3/poolmanager.py,sha256=iWEAIGrVNGoOmQyfiFwCqG-IyYy6GIQ-jJ9QCsX9li4,17861
pip/_vendor/urllib3/request.py,sha256=hhoHvEEatyd9Tn5EbGjQ0emn-ENMCyY591yNWTneINA,6018
pip/_vendor/urllib3/response.py,sha256=5OVDLR6ss88mak30ZhDaSqHxXbbAQ7CIUrktI7B5aPA,27833
pip/_vendor/urllib3/response.py,sha256=eo1Sfkn2x44FtjgP3qwwDsG9ak84spQAxEGy7Ovd4Pc,28221
pip/_vendor/urllib3/util/__init__.py,sha256=bWNaav_OT-1L7-sxm59cGb59rDORlbhb_4noduM5m0U,1038
pip/_vendor/urllib3/util/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/urllib3/util/__pycache__/connection.cpython-38.pyc,,
@ -725,12 +733,12 @@ pip/_vendor/urllib3/util/connection.py,sha256=NsxUAKQ98GKywta--zg57CdVpeTCI6N-GE
pip/_vendor/urllib3/util/queue.py,sha256=myTX3JDHntglKQNBf3b6dasHH-uF-W59vzGSQiFdAfI,497
pip/_vendor/urllib3/util/request.py,sha256=C-6-AWffxZG03AdRGoY59uqsn4CVItKU6gjxz7Hc3Mc,3815
pip/_vendor/urllib3/util/response.py,sha256=_WbTQr8xRQuJuY2rTIZxVdJD6mnEOtQupjaK_bF_Vj8,2573
pip/_vendor/urllib3/util/retry.py,sha256=Ui74h44gLIIWkAxT9SK3A2mEvu55-odWgJMw3LiUNGk,15450
pip/_vendor/urllib3/util/ssl_.py,sha256=W52OyrqLO1IQ5kUgzM8fLAO-EgJxuftd5nhcTOD54ew,14167
pip/_vendor/urllib3/util/timeout.py,sha256=bCtaS_xVKaTDJ5VMlroXBfCnPUDNVGZqik7-z83issg,9871
pip/_vendor/urllib3/util/url.py,sha256=6cDyd46labP8STbF0vieWygu2xVhKkgcF1h3tc6HYno,13979
pip/_vendor/urllib3/util/retry.py,sha256=3wbv7SdzYNOxPcBiFkPCubTbK1_6vWSepznOXirhUfA,15543
pip/_vendor/urllib3/util/ssl_.py,sha256=N7gqt2iqzKBsWGmc61YeKNSPri6Ns2iZ_MD5hV2y8tU,14523
pip/_vendor/urllib3/util/timeout.py,sha256=3qawUo-TZq4q7tyeRToMIOdNGEOBjOOQVq7nHnLryP4,9947
pip/_vendor/urllib3/util/url.py,sha256=S4YyAwWKJPjFFECC7l9Vp9EKqRH1XAb-uQFANn1Tak0,13981
pip/_vendor/urllib3/util/wait.py,sha256=k46KzqIYu3Vnzla5YW3EvtInNlU_QycFqQAghIOxoAg,5406
pip/_vendor/vendor.txt,sha256=ZewT4rljE_Ct9eJsAk0HhDlKSHaguTqeBSGlF1zIgss,440
pip/_vendor/vendor.txt,sha256=bWUiaRjMJhuUsqFZHEJkBH_6lJ_Avl9cOyszcI74IHs,437
pip/_vendor/webencodings/__init__.py,sha256=qOBJIuPy_4ByYH6W_bNgJF-qYQ2DoU-dKsDu5yRWCXg,10579
pip/_vendor/webencodings/__pycache__/__init__.cpython-38.pyc,,
pip/_vendor/webencodings/__pycache__/labels.cpython-38.pyc,,

View File

@ -4,7 +4,7 @@ if MYPY_CHECK_RUNNING:
from typing import List, Optional
__version__ = "20.1.1"
__version__ = "20.2.2"
def main(args=None):

View File

@ -1,10 +1,6 @@
"""Build Environment used for isolation during sdist building
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
# mypy: disallow-untyped-defs=False
import logging
import os
import sys
@ -22,7 +18,8 @@ from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Tuple, Set, Iterable, Optional, List
from types import TracebackType
from typing import Tuple, Set, Iterable, Optional, List, Type
from pip._internal.index.package_finder import PackageFinder
logger = logging.getLogger(__name__)
@ -110,6 +107,7 @@ class BuildEnvironment(object):
).format(system_sites=system_sites, lib_dirs=self._lib_dirs))
def __enter__(self):
# type: () -> None
self._save_env = {
name: os.environ.get(name, None)
for name in ('PATH', 'PYTHONNOUSERSITE', 'PYTHONPATH')
@ -128,7 +126,13 @@ class BuildEnvironment(object):
'PYTHONPATH': os.pathsep.join(pythonpath),
})
def __exit__(self, exc_type, exc_val, exc_tb):
def __exit__(
self,
exc_type, # type: Optional[Type[BaseException]]
exc_val, # type: Optional[BaseException]
exc_tb # type: Optional[TracebackType]
):
# type: (...) -> None
for varname, old_value in self._save_env.items():
if old_value is None:
os.environ.pop(varname, None)
@ -159,7 +163,7 @@ class BuildEnvironment(object):
finder, # type: PackageFinder
requirements, # type: Iterable[str]
prefix_as_string, # type: str
message # type: Optional[str]
message # type: str
):
# type: (...) -> None
prefix = self._prefixes[prefix_as_string]
@ -193,6 +197,8 @@ class BuildEnvironment(object):
args.extend(['--trusted-host', host])
if finder.allow_all_prereleases:
args.append('--pre')
if finder.prefer_binary:
args.append('--prefer-binary')
args.append('--')
args.extend(requirements)
with open_spinner(message) as spinner:
@ -204,16 +210,32 @@ class NoOpBuildEnvironment(BuildEnvironment):
"""
def __init__(self):
# type: () -> None
pass
def __enter__(self):
# type: () -> None
pass
def __exit__(self, exc_type, exc_val, exc_tb):
def __exit__(
self,
exc_type, # type: Optional[Type[BaseException]]
exc_val, # type: Optional[BaseException]
exc_tb # type: Optional[TracebackType]
):
# type: (...) -> None
pass
def cleanup(self):
# type: () -> None
pass
def install_requirements(self, finder, requirements, prefix, message):
def install_requirements(
self,
finder, # type: PackageFinder
requirements, # type: Iterable[str]
prefix_as_string, # type: str
message # type: str
):
# type: (...) -> None
raise NotImplementedError()

View File

@ -1,9 +1,6 @@
"""Cache Management
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import hashlib
import json
import logging
@ -122,7 +119,7 @@ class Cache(object):
return parts
def _get_candidates(self, link, canonical_package_name):
# type: (Link, Optional[str]) -> List[Any]
# type: (Link, str) -> List[Any]
can_not_cache = (
not self.cache_dir or
not canonical_package_name or
@ -185,6 +182,7 @@ class SimpleWheelCache(Cache):
def get_path_for_link_legacy(self, link):
# type: (Link) -> str
parts = self._get_cache_path_parts_legacy(link)
assert self.cache_dir
return os.path.join(self.cache_dir, "wheels", *parts)
def get_path_for_link(self, link):
@ -204,7 +202,7 @@ class SimpleWheelCache(Cache):
:param link: The link of the sdist for which this will cache wheels.
"""
parts = self._get_cache_path_parts(link)
assert self.cache_dir
# Store wheels within the root cache_dir
return os.path.join(self.cache_dir, "wheels", *parts)
@ -230,10 +228,9 @@ class SimpleWheelCache(Cache):
continue
if canonicalize_name(wheel.name) != canonical_package_name:
logger.debug(
"Ignoring cached wheel {} for {} as it "
"does not match the expected distribution name {}.".format(
wheel_name, link, package_name
)
"Ignoring cached wheel %s for %s as it "
"does not match the expected distribution name %s.",
wheel_name, link, package_name,
)
continue
if not wheel.supported(supported_tags):

View File

@ -19,7 +19,6 @@ from pip._internal.cli.parser import (
from pip._internal.cli.status_codes import (
ERROR,
PREVIOUS_BUILD_DIR_ERROR,
SUCCESS,
UNKNOWN_ERROR,
VIRTUALENV_NOT_FOUND,
)
@ -27,7 +26,9 @@ from pip._internal.exceptions import (
BadCommand,
CommandError,
InstallationError,
NetworkConnectionError,
PreviousBuildDirError,
SubProcessError,
UninstallationError,
)
from pip._internal.utils.deprecation import deprecated
@ -88,6 +89,12 @@ class Command(CommandContextMixIn):
)
self.parser.add_option_group(gen_opts)
self.add_options()
def add_options(self):
# type: () -> None
pass
def handle_pip_version_check(self, options):
# type: (Values) -> None
"""
@ -99,7 +106,7 @@ class Command(CommandContextMixIn):
assert not hasattr(options, 'no_index')
def run(self, options, args):
# type: (Values, List[Any]) -> Any
# type: (Values, List[Any]) -> int
raise NotImplementedError
def parse_args(self, args):
@ -142,7 +149,7 @@ class Command(CommandContextMixIn):
):
message = (
"pip 21.0 will drop support for Python 2.7 in January 2021. "
"More details about Python 2 support in pip, can be found at "
"More details about Python 2 support in pip can be found at "
"https://pip.pypa.io/en/latest/development/release-process/#python-2-support" # noqa
)
if platform.python_implementation() == "CPython":
@ -184,18 +191,38 @@ class Command(CommandContextMixIn):
)
options.cache_dir = None
if getattr(options, "build_dir", None):
deprecated(
reason=(
"The -b/--build/--build-dir/--build-directory "
"option is deprecated."
),
replacement=(
"use the TMPDIR/TEMP/TMP environment variable, "
"possibly combined with --no-clean"
),
gone_in="20.3",
issue=8333,
)
if 'resolver' in options.unstable_features:
logger.critical(
"--unstable-feature=resolver is no longer supported, and "
"has been replaced with --use-feature=2020-resolver instead."
)
sys.exit(ERROR)
try:
status = self.run(options, args)
# FIXME: all commands should return an exit status
# and when it is done, isinstance is not needed anymore
if isinstance(status, int):
return status
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) as exc:
except (InstallationError, UninstallationError, BadCommand,
SubProcessError, NetworkConnectionError) as exc:
logger.critical(str(exc))
logger.debug('Exception information:', exc_info=True)
@ -224,5 +251,3 @@ class Command(CommandContextMixIn):
return UNKNOWN_ERROR
finally:
self.handle_pip_version_check(options)
return SUCCESS

View File

@ -12,7 +12,6 @@ pass on state. To be consistent, all options will follow this design.
from __future__ import absolute_import
import logging
import os
import textwrap
import warnings
@ -35,8 +34,6 @@ if MYPY_CHECK_RUNNING:
from optparse import OptionParser, Values
from pip._internal.cli.parser import ConfigOptionParser
logger = logging.getLogger(__name__)
def raise_option_error(parser, option, msg):
# type: (OptionParser, Option, str) -> None
@ -243,7 +240,7 @@ no_input = partial(
dest='no_input',
action='store_true',
default=False,
help=SUPPRESS_HELP
help="Disable prompting for input."
) # type: Callable[..., Option]
proxy = partial(
@ -702,7 +699,8 @@ build_dir = partial(
metavar='dir',
action='callback',
callback=_handle_build_dir,
help='Directory to unpack packages into and build in. Note that '
help='(DEPRECATED) '
'Directory to unpack packages into and build in. Note that '
'an initial build still takes place in a temporary directory. '
'The location of temporary directories can be controlled by setting '
'the TMPDIR environment variable (TEMP on Windows) appropriately. '
@ -824,16 +822,6 @@ disable_pip_version_check = partial(
) # type: Callable[..., Option]
# Deprecated, Remove later
always_unzip = partial(
Option,
'-Z', '--always-unzip',
dest='always_unzip',
action='store_true',
help=SUPPRESS_HELP,
) # type: Callable[..., Option]
def _handle_merge_hash(option, opt_str, value, parser):
# type: (Option, str, str, OptionParser) -> None
"""Given a value spelled "algo:digest", append the digest to a list
@ -843,11 +831,11 @@ def _handle_merge_hash(option, opt_str, value, parser):
try:
algo, digest = value.split(':', 1)
except ValueError:
parser.error('Arguments to {} must be a hash name '
parser.error('Arguments to {} must be a hash name ' # noqa
'followed by a value, like --hash=sha256:'
'abcde...'.format(opt_str))
if algo not in STRONG_HASHES:
parser.error('Allowed hash algorithms for {} are {}.'.format(
parser.error('Allowed hash algorithms for {} are {}.'.format( # noqa
opt_str, ', '.join(STRONG_HASHES)))
parser.values.hashes.setdefault(algo, []).append(digest)
@ -915,8 +903,31 @@ unstable_feature = partial(
action='append',
default=[],
choices=['resolver'],
help=SUPPRESS_HELP, # TODO: Enable this when the resolver actually works.
# help='Enable unstable feature(s) that may be backward incompatible.',
help=SUPPRESS_HELP, # TODO: drop this in pip 20.3
) # type: Callable[..., Option]
use_new_feature = partial(
Option,
'--use-feature',
dest='features_enabled',
metavar='feature',
action='append',
default=[],
choices=['2020-resolver', 'fast-deps'],
help='Enable new functionality, that may be backward incompatible.',
) # type: Callable[..., Option]
use_deprecated_feature = partial(
Option,
'--use-deprecated',
dest='deprecated_features_enabled',
metavar='feature',
action='append',
default=[],
choices=[],
help=(
'Enable deprecated functionality, that will be removed in the future.'
),
) # type: Callable[..., Option]
@ -948,6 +959,8 @@ general_group = {
no_color,
no_python_version_warning,
unstable_feature,
use_new_feature,
use_deprecated_feature,
]
} # type: Dict[str, Any]

View File

@ -78,6 +78,7 @@ class InterruptibleMixin(object):
"""
Save the original SIGINT handler for later.
"""
# https://github.com/python/mypy/issues/5887
super(InterruptibleMixin, self).__init__( # type: ignore
*args,
**kwargs
@ -134,6 +135,7 @@ class DownloadProgressMixin(object):
def __init__(self, *args, **kwargs):
# type: (List[Any], Dict[Any, Any]) -> None
# https://github.com/python/mypy/issues/5887
super(DownloadProgressMixin, self).__init__( # type: ignore
*args,
**kwargs
@ -165,7 +167,9 @@ class DownloadProgressMixin(object):
def iter(self, it): # type: ignore
for x in it:
yield x
self.next(len(x))
# B305 is incorrectly raised here
# https://github.com/PyCQA/flake8-bugbear/issues/59
self.next(len(x)) # noqa: B305
self.finish()
@ -183,6 +187,7 @@ class WindowsMixin(object):
if WINDOWS and self.hide_cursor: # type: ignore
self.hide_cursor = False
# https://github.com/python/mypy/issues/5887
super(WindowsMixin, self).__init__(*args, **kwargs) # type: ignore
# Check if we are running on Windows and we have the colorama module,
@ -206,30 +211,27 @@ class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin,
message = "%(percent)d%%"
suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s"
# NOTE: The "type: ignore" comments on the following classes are there to
# work around https://github.com/python/typing/issues/241
class DefaultDownloadProgressBar(BaseDownloadProgressBar,
_BaseBar):
pass
class DownloadSilentBar(BaseDownloadProgressBar, SilentBar): # type: ignore
class DownloadSilentBar(BaseDownloadProgressBar, SilentBar):
pass
class DownloadBar(BaseDownloadProgressBar, # type: ignore
class DownloadBar(BaseDownloadProgressBar,
Bar):
pass
class DownloadFillingCirclesBar(BaseDownloadProgressBar, # type: ignore
class DownloadFillingCirclesBar(BaseDownloadProgressBar,
FillingCirclesBar):
pass
class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, # type: ignore
class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar,
BlueEmojiBar):
pass
@ -240,7 +242,8 @@ class DownloadProgressSpinner(WindowsMixin, InterruptibleMixin,
file = sys.stdout
suffix = "%(downloaded)s %(download_speed)s"
def next_phase(self): # type: ignore
def next_phase(self):
# type: () -> str
if not hasattr(self, "_phaser"):
self._phaser = itertools.cycle(self.phases)
return next(self._phaser)

View File

@ -13,6 +13,7 @@ from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.cli.command_context import CommandContextMixIn
from pip._internal.exceptions import CommandError, PreviousBuildDirError
from pip._internal.index.collector import LinkCollector
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.network.download import Downloader
@ -25,11 +26,7 @@ from pip._internal.req.constructors import (
install_req_from_req_string,
)
from pip._internal.req.req_file import parse_requirements
from pip._internal.req.req_set import RequirementSet
from pip._internal.self_outdated_check import (
make_link_collector,
pip_self_version_check,
)
from pip._internal.self_outdated_check import pip_self_version_check
from pip._internal.utils.temp_dir import tempdir_kinds
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@ -260,7 +257,7 @@ class RequirementCommand(IndexGroupCommand):
# The long import name and duplicated invocation is needed to convince
# Mypy into correctly typechecking. Otherwise it would complain the
# "Resolver" class being redefined.
if 'resolver' in options.unstable_features:
if '2020-resolver' in options.features_enabled:
import pip._internal.resolution.resolvelib.resolver
return pip._internal.resolution.resolvelib.resolver.Resolver(
preparer=preparer,
@ -274,6 +271,7 @@ class RequirementCommand(IndexGroupCommand):
force_reinstall=force_reinstall,
upgrade_strategy=upgrade_strategy,
py_version_info=py_version_info,
lazy_wheel='fast-deps' in options.features_enabled,
)
import pip._internal.resolution.legacy.resolver
return pip._internal.resolution.legacy.resolver.Resolver(
@ -296,15 +294,12 @@ class RequirementCommand(IndexGroupCommand):
options, # type: Values
finder, # type: PackageFinder
session, # type: PipSession
check_supported_wheels=True, # type: bool
):
# type: (...) -> List[InstallRequirement]
"""
Parse command-line arguments into the corresponding requirements.
"""
requirement_set = RequirementSet(
check_supported_wheels=check_supported_wheels
)
requirements = [] # type: List[InstallRequirement]
for filename in options.constraints:
for parsed_req in parse_requirements(
filename,
@ -313,26 +308,26 @@ class RequirementCommand(IndexGroupCommand):
req_to_add = install_req_from_parsed_requirement(
parsed_req,
isolated=options.isolated_mode,
user_supplied=False,
)
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
requirements.append(req_to_add)
for req in args:
req_to_add = install_req_from_line(
req, None, isolated=options.isolated_mode,
use_pep517=options.use_pep517,
user_supplied=True,
)
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
requirements.append(req_to_add)
for req in options.editables:
req_to_add = install_req_from_editable(
req,
user_supplied=True,
isolated=options.isolated_mode,
use_pep517=options.use_pep517,
)
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
requirements.append(req_to_add)
# NOTE: options.require_hashes may be set if --require-hashes is True
for filename in options.requirements:
@ -342,13 +337,12 @@ class RequirementCommand(IndexGroupCommand):
req_to_add = install_req_from_parsed_requirement(
parsed_req,
isolated=options.isolated_mode,
use_pep517=options.use_pep517
use_pep517=options.use_pep517,
user_supplied=True,
)
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
requirements.append(req_to_add)
# If any requirement has hash options, enable hash checking.
requirements = requirement_set.all_requirements
if any(req.has_hash_options for req in requirements):
options.require_hashes = True
@ -392,7 +386,7 @@ class RequirementCommand(IndexGroupCommand):
:param ignore_requires_python: Whether to ignore incompatible
"Requires-Python" values in links. Defaults to False.
"""
link_collector = make_link_collector(session, options=options)
link_collector = LinkCollector.create(session, options=options)
selection_prefs = SelectionPreferences(
allow_yanked=True,
format_control=options.format_control,

View File

@ -24,13 +24,13 @@ class CacheCommand(Command):
Subcommands:
dir: Show the cache directory.
info: Show information about the cache.
list: List filenames of packages stored in the cache.
remove: Remove one or more package from the cache.
purge: Remove all items from the cache.
- dir: Show the cache directory.
- info: Show information about the cache.
- list: List filenames of packages stored in the cache.
- remove: Remove one or more package from the cache.
- purge: Remove all items from the cache.
<pattern> can be a glob expression or a package name.
``<pattern>`` can be a glob expression or a package name.
"""
ignore_require_venv = True
@ -59,8 +59,9 @@ class CacheCommand(Command):
# Determine action
if not args or args[0] not in handlers:
logger.error("Need an action ({}) to perform.".format(
", ".join(sorted(handlers)))
logger.error(
"Need an action (%s) to perform.",
", ".join(sorted(handlers)),
)
return ERROR

View File

@ -1,13 +1,16 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import sys
import textwrap
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.utils.misc import get_prog
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List
from optparse import Values
BASE_COMPLETION = """
# pip {shell} completion start{script}# pip {shell} completion end
@ -53,33 +56,31 @@ class CompletionCommand(Command):
ignore_require_venv = True
def __init__(self, *args, **kw):
super(CompletionCommand, self).__init__(*args, **kw)
cmd_opts = self.cmd_opts
cmd_opts.add_option(
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'--bash', '-b',
action='store_const',
const='bash',
dest='shell',
help='Emit completion code for bash')
cmd_opts.add_option(
self.cmd_opts.add_option(
'--zsh', '-z',
action='store_const',
const='zsh',
dest='shell',
help='Emit completion code for zsh')
cmd_opts.add_option(
self.cmd_opts.add_option(
'--fish', '-f',
action='store_const',
const='fish',
dest='shell',
help='Emit completion code for fish')
self.parser.insert_option_group(0, cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
"""Prints the completion code of the given shell"""
shells = COMPLETION_SCRIPTS.keys()
shell_options = ['--' + shell for shell in sorted(shells)]
@ -89,7 +90,9 @@ class CompletionCommand(Command):
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))
)
return SUCCESS

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
import logging
import os
import subprocess
@ -13,21 +10,31 @@ from pip._internal.configuration import (
kinds,
)
from pip._internal.exceptions import PipError
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import get_prog, write_output
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List, Any, Optional
from optparse import Values
from pip._internal.configuration import Kind
logger = logging.getLogger(__name__)
class ConfigurationCommand(Command):
"""Manage local and global configuration.
"""
Manage local and global configuration.
Subcommands:
list: List the active configuration (or from the file specified)
edit: Edit the configuration file in an editor
get: Get the value associated with name
set: Set the name=value
unset: Unset the value associated with name
- list: List the active configuration (or from the file specified)
- edit: Edit the configuration file in an editor
- get: Get the value associated with name
- set: Set the name=value
- unset: Unset the value associated with name
- debug: List the configuration files and values defined under them
If none of --user, --global and --site are passed, a virtual
environment configuration file is used if one is active and the file
@ -43,13 +50,11 @@ class ConfigurationCommand(Command):
%prog [<file-option>] get name
%prog [<file-option>] set name value
%prog [<file-option>] unset name
%prog [<file-option>] debug
"""
def __init__(self, *args, **kwargs):
super(ConfigurationCommand, self).__init__(*args, **kwargs)
self.configuration = None
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'--editor',
dest='editor',
@ -88,18 +93,21 @@ class ConfigurationCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
handlers = {
"list": self.list_values,
"edit": self.open_in_editor,
"get": self.get_name,
"set": self.set_name_value,
"unset": self.unset_name
"unset": self.unset_name,
"debug": self.list_config_values,
}
# Determine action
if not args or args[0] not in handlers:
logger.error("Need an action ({}) to perform.".format(
", ".join(sorted(handlers)))
logger.error(
"Need an action (%s) to perform.",
", ".join(sorted(handlers)),
)
return ERROR
@ -131,6 +139,7 @@ class ConfigurationCommand(Command):
return SUCCESS
def _determine_file(self, options, need_value):
# type: (Values, bool) -> Optional[Kind]
file_options = [key for key, value in (
(kinds.USER, options.user_file),
(kinds.GLOBAL, options.global_file),
@ -157,30 +166,70 @@ class ConfigurationCommand(Command):
)
def list_values(self, options, args):
# type: (Values, List[str]) -> None
self._get_n_args(args, "list", n=0)
for key, value in sorted(self.configuration.items()):
write_output("%s=%r", key, value)
def get_name(self, options, args):
# type: (Values, List[str]) -> None
key = self._get_n_args(args, "get [name]", n=1)
value = self.configuration.get_value(key)
write_output("%s", value)
def set_name_value(self, options, args):
# type: (Values, List[str]) -> None
key, value = self._get_n_args(args, "set [name] [value]", n=2)
self.configuration.set_value(key, value)
self._save_configuration()
def unset_name(self, options, args):
# type: (Values, List[str]) -> None
key = self._get_n_args(args, "unset [name]", n=1)
self.configuration.unset_value(key)
self._save_configuration()
def list_config_values(self, options, args):
# type: (Values, List[str]) -> None
"""List config key-value pairs across different config files"""
self._get_n_args(args, "debug", n=0)
self.print_env_var_values()
# Iterate over config files and print if they exist, and the
# key-value pairs present in them if they do
for variant, files in sorted(self.configuration.iter_config_files()):
write_output("%s:", variant)
for fname in files:
with indent_log():
file_exists = os.path.exists(fname)
write_output("%s, exists: %r",
fname, file_exists)
if file_exists:
self.print_config_file_values(variant)
def print_config_file_values(self, variant):
# type: (Kind) -> None
"""Get key-value pairs from the file of a variant"""
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):
# type: () -> None
"""Get key-values pairs present as environment variables"""
write_output("%s:", 'env_var')
with indent_log():
for key, value in sorted(self.configuration.get_environ_vars()):
env_var = 'PIP_{}'.format(key.upper())
write_output("%s=%r", env_var, value)
def open_in_editor(self, options, args):
# type: (Values, List[str]) -> None
editor = self._determine_editor(options)
fname = self.configuration.get_file_to_edit()
@ -196,6 +245,7 @@ class ConfigurationCommand(Command):
)
def _get_n_args(self, args, example, n):
# type: (List[str], str, int) -> Any
"""Helper to make sure the command got the right number of arguments
"""
if len(args) != n:
@ -211,18 +261,19 @@ class ConfigurationCommand(Command):
return args
def _save_configuration(self):
# type: () -> None
# We successfully ran a modifying command. Need to save the
# configuration.
try:
self.configuration.save()
except Exception:
logger.error(
"Unable to save configuration. Please report this as a bug.",
exc_info=1
logger.exception(
"Unable to save configuration. Please report this as a bug."
)
raise PipError("Internal Error.")
def _determine_editor(self, options):
# type: (Values) -> str
if options.editor is not None:
return options.editor
elif "VISUAL" in os.environ:

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import locale
@ -23,15 +20,16 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from types import ModuleType
from typing import Any, List, Optional, Dict
from typing import List, Optional, Dict
from optparse import Values
from pip._internal.configuration import Configuration
logger = logging.getLogger(__name__)
def show_value(name, value):
# type: (str, Optional[str]) -> None
logger.info('{}: {}'.format(name, value))
logger.info('%s: %s', name, value)
def show_sys_implementation():
@ -67,7 +65,6 @@ def create_vendor_txt_map():
def get_module_from_module_name(module_name):
# type: (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.
@ -84,15 +81,18 @@ def get_module_from_module_name(module_name):
def get_vendor_version_from_module(module_name):
# type: (str) -> str
# type: (str) -> Optional[str]
module = get_module_from_module_name(module_name)
version = getattr(module, '__version__', None)
if not version:
# Try to find version in debundled module info
# The type for module.__file__ is Optional[str] in
# Python 2, and str in Python 3. The type: ignore is
# added to account for Python 2, instead of a cast
# and should be removed once we drop Python 2 support
pkg_set = pkg_resources.WorkingSet(
[os.path.dirname(getattr(module, '__file__'))]
[os.path.dirname(module.__file__)] # type: ignore
)
package = pkg_set.find(pkg_resources.Requirement.parse(module_name))
version = getattr(package, 'version', None)
@ -102,9 +102,9 @@ def get_vendor_version_from_module(module_name):
def show_actual_vendor_versions(vendor_txt_versions):
# type: (Dict[str, str]) -> None
# Logs the actual version and print extra info
# if there is a conflict or if the actual version could not be imported.
"""Log the actual version and print extra info if there is
a conflict or if the actual version could not be imported.
"""
for module_name, expected_version in vendor_txt_versions.items():
extra_message = ''
actual_version = get_vendor_version_from_module(module_name)
@ -115,14 +115,7 @@ def show_actual_vendor_versions(vendor_txt_versions):
elif actual_version != expected_version:
extra_message = ' (CONFLICT: vendor.txt suggests version should'\
' be {})'.format(expected_version)
logger.info(
'{name}=={actual}{extra}'.format(
name=module_name,
actual=actual_version,
extra=extra_message
)
)
logger.info('%s==%s%s', module_name, actual_version, extra_message)
def show_vendor_versions():
@ -169,8 +162,9 @@ def show_tags(options):
def ca_bundle_info(config):
# type: (Configuration) -> str
levels = set()
for key, value in config.items():
for key, _ in config.items():
levels.add(key.split('.')[0])
if not levels:
@ -197,16 +191,14 @@ class DebugCommand(Command):
%prog <options>"""
ignore_require_venv = True
def __init__(self, *args, **kw):
super(DebugCommand, self).__init__(*args, **kw)
cmd_opts = self.cmd_opts
cmdoptions.add_target_python_options(cmd_opts)
self.parser.insert_option_group(0, cmd_opts)
def add_options(self):
# type: () -> None
cmdoptions.add_target_python_options(self.cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
self.parser.config.load()
def run(self, options, args):
# type: (Values, List[Any]) -> int
# type: (Values, List[str]) -> int
logger.warning(
"This command is only meant for debugging. "
"Do not use this with automation for parsing and getting these "

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import logging
@ -9,9 +6,15 @@ import os
from pip._internal.cli import cmdoptions
from pip._internal.cli.cmdoptions import make_target_python
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.req.req_tracker import get_requirement_tracker
from pip._internal.utils.misc import ensure_dir, normalize_path, write_output
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List
logger = logging.getLogger(__name__)
@ -36,28 +39,25 @@ class DownloadCommand(RequirementCommand):
%prog [options] <local project path> ...
%prog [options] <archive url/path> ..."""
def __init__(self, *args, **kw):
super(DownloadCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> 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())
self.cmd_opts.add_option(cmdoptions.only_binary())
self.cmd_opts.add_option(cmdoptions.prefer_binary())
self.cmd_opts.add_option(cmdoptions.src())
self.cmd_opts.add_option(cmdoptions.pre())
self.cmd_opts.add_option(cmdoptions.require_hashes())
self.cmd_opts.add_option(cmdoptions.progress_bar())
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
self.cmd_opts.add_option(cmdoptions.use_pep517())
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
cmd_opts = self.cmd_opts
cmd_opts.add_option(cmdoptions.constraints())
cmd_opts.add_option(cmdoptions.requirements())
cmd_opts.add_option(cmdoptions.build_dir())
cmd_opts.add_option(cmdoptions.no_deps())
cmd_opts.add_option(cmdoptions.global_options())
cmd_opts.add_option(cmdoptions.no_binary())
cmd_opts.add_option(cmdoptions.only_binary())
cmd_opts.add_option(cmdoptions.prefer_binary())
cmd_opts.add_option(cmdoptions.src())
cmd_opts.add_option(cmdoptions.pre())
cmd_opts.add_option(cmdoptions.require_hashes())
cmd_opts.add_option(cmdoptions.progress_bar())
cmd_opts.add_option(cmdoptions.no_build_isolation())
cmd_opts.add_option(cmdoptions.use_pep517())
cmd_opts.add_option(cmdoptions.no_use_pep517())
cmd_opts.add_option(
self.cmd_opts.add_option(
'-d', '--dest', '--destination-dir', '--destination-directory',
dest='download_dir',
metavar='dir',
@ -65,7 +65,7 @@ class DownloadCommand(RequirementCommand):
help=("Download packages into <dir>."),
)
cmdoptions.add_target_python_options(cmd_opts)
cmdoptions.add_target_python_options(self.cmd_opts)
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group,
@ -73,10 +73,12 @@ class DownloadCommand(RequirementCommand):
)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
@with_cleanup
def run(self, options, args):
# type: (Values, List[str]) -> int
options.ignore_installed = True
# editable doesn't really make sense for `pip download`, but the bowels
# of the RequirementSet code require that property.
@ -132,11 +134,10 @@ class DownloadCommand(RequirementCommand):
reqs, check_supported_wheels=True
)
downloaded = ' '.join([
req.name for req in requirement_set.requirements.values()
if req.successfully_downloaded
])
downloaded = ' '.join([req.name # type: ignore
for req in requirement_set.requirements.values()
if req.successfully_downloaded])
if downloaded:
write_output('Successfully downloaded %s', downloaded)
return requirement_set
return SUCCESS

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import sys
@ -8,12 +5,18 @@ import sys
from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.models.format_control import FormatControl
from pip._internal.operations.freeze import freeze
from pip._internal.utils.compat import stdlib_pkgs
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'}
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List
class FreezeCommand(Command):
"""
@ -26,9 +29,8 @@ class FreezeCommand(Command):
%prog [options]"""
log_streams = ("ext://sys.stderr", "ext://sys.stderr")
def __init__(self, *args, **kw):
super(FreezeCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-r', '--requirement',
dest='requirements',
@ -75,6 +77,7 @@ class FreezeCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
format_control = FormatControl(set(), set())
wheel_cache = WheelCache(options.cache_dir, format_control)
skip = set(stdlib_pkgs)
@ -97,3 +100,4 @@ class FreezeCommand(Command):
for line in freeze(**freeze_kwargs):
sys.stdout.write(line + '\n')
return SUCCESS

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import hashlib
@ -8,9 +5,14 @@ import logging
import sys
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import ERROR
from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES
from pip._internal.utils.misc import read_chunks, write_output
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List
logger = logging.getLogger(__name__)
@ -26,8 +28,8 @@ class HashCommand(Command):
usage = '%prog [options] <file> ...'
ignore_require_venv = True
def __init__(self, *args, **kw):
super(HashCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-a', '--algorithm',
dest='algorithm',
@ -39,6 +41,7 @@ class HashCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
if not args:
self.parser.print_usage(sys.stderr)
return ERROR
@ -47,9 +50,11 @@ class HashCommand(Command):
for path in args:
write_output('%s:\n--hash=%s:%s',
path, algorithm, _hash_of_file(path, algorithm))
return SUCCESS
def _hash_of_file(path, algorithm):
# type: (str, str) -> str
"""Return the hash digest of a file."""
with open(path, 'rb') as archive:
hash = hashlib.new(algorithm)

View File

@ -1,11 +1,13 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import CommandError
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List
from optparse import Values
class HelpCommand(Command):
@ -16,6 +18,7 @@ class HelpCommand(Command):
ignore_require_venv = True
def run(self, options, args):
# type: (Values, List[str]) -> int
from pip._internal.commands import (
commands_dict, create_command, get_similar_commands,
)

View File

@ -1,10 +1,3 @@
# The following comment should be removed at some point in the future.
# It's included for now because without it InstallCommand.run() has a
# couple errors where we have to know req.name is str rather than
# Optional[str] for the InstallRequirement req.
# mypy: strict-optional=False
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import errno
@ -28,12 +21,14 @@ from pip._internal.locations import distutils_scheme
from pip._internal.operations.check import check_install_conflicts
from pip._internal.req import install_given_reqs
from pip._internal.req.req_tracker import get_requirement_tracker
from pip._internal.utils.datetime import today_is_later_than
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.distutils_args import parse_distutils_args
from pip._internal.utils.filesystem import test_writable_dir
from pip._internal.utils.misc import (
ensure_dir,
get_installed_version,
get_pip_version,
protect_pip_from_modification_on_windows,
write_output,
)
@ -44,9 +39,10 @@ from pip._internal.wheel_builder import build, should_build_for_install_command
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import Any, Iterable, List, Optional
from typing import Iterable, List, Optional
from pip._internal.models.format_control import FormatControl
from pip._internal.operations.check import ConflictDetails
from pip._internal.req.req_install import InstallRequirement
from pip._internal.wheel_builder import BinaryAllowedPredicate
@ -87,18 +83,15 @@ class InstallCommand(RequirementCommand):
%prog [options] [-e] <local project path> ...
%prog [options] <archive url/path> ..."""
def __init__(self, *args, **kw):
super(InstallCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(cmdoptions.requirements())
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.no_deps())
self.cmd_opts.add_option(cmdoptions.pre())
cmd_opts = self.cmd_opts
cmd_opts.add_option(cmdoptions.requirements())
cmd_opts.add_option(cmdoptions.constraints())
cmd_opts.add_option(cmdoptions.no_deps())
cmd_opts.add_option(cmdoptions.pre())
cmd_opts.add_option(cmdoptions.editable())
cmd_opts.add_option(
self.cmd_opts.add_option(cmdoptions.editable())
self.cmd_opts.add_option(
'-t', '--target',
dest='target_dir',
metavar='dir',
@ -108,9 +101,9 @@ class InstallCommand(RequirementCommand):
'<dir>. Use --upgrade to replace existing packages in <dir> '
'with new versions.'
)
cmdoptions.add_target_python_options(cmd_opts)
cmdoptions.add_target_python_options(self.cmd_opts)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--user',
dest='use_user_site',
action='store_true',
@ -118,19 +111,19 @@ class InstallCommand(RequirementCommand):
"platform. Typically ~/.local/, or %APPDATA%\\Python on "
"Windows. (See the Python documentation for site.USER_BASE "
"for full details.)")
cmd_opts.add_option(
self.cmd_opts.add_option(
'--no-user',
dest='use_user_site',
action='store_false',
help=SUPPRESS_HELP)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--root',
dest='root_path',
metavar='dir',
default=None,
help="Install everything relative to this alternate root "
"directory.")
cmd_opts.add_option(
self.cmd_opts.add_option(
'--prefix',
dest='prefix_path',
metavar='dir',
@ -138,11 +131,11 @@ class InstallCommand(RequirementCommand):
help="Installation prefix where lib, bin and other top-level "
"folders are placed")
cmd_opts.add_option(cmdoptions.build_dir())
self.cmd_opts.add_option(cmdoptions.build_dir())
cmd_opts.add_option(cmdoptions.src())
self.cmd_opts.add_option(cmdoptions.src())
cmd_opts.add_option(
self.cmd_opts.add_option(
'-U', '--upgrade',
dest='upgrade',
action='store_true',
@ -151,7 +144,7 @@ class InstallCommand(RequirementCommand):
'upgrade-strategy used.'
)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--upgrade-strategy',
dest='upgrade_strategy',
default='only-if-needed',
@ -165,14 +158,14 @@ class InstallCommand(RequirementCommand):
'satisfy the requirements of the upgraded package(s).'
)
cmd_opts.add_option(
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.')
cmd_opts.add_option(
self.cmd_opts.add_option(
'-I', '--ignore-installed',
dest='ignore_installed',
action='store_true',
@ -182,15 +175,15 @@ class InstallCommand(RequirementCommand):
'with a different package manager!'
)
cmd_opts.add_option(cmdoptions.ignore_requires_python())
cmd_opts.add_option(cmdoptions.no_build_isolation())
cmd_opts.add_option(cmdoptions.use_pep517())
cmd_opts.add_option(cmdoptions.no_use_pep517())
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
self.cmd_opts.add_option(cmdoptions.use_pep517())
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
cmd_opts.add_option(cmdoptions.install_options())
cmd_opts.add_option(cmdoptions.global_options())
self.cmd_opts.add_option(cmdoptions.install_options())
self.cmd_opts.add_option(cmdoptions.global_options())
cmd_opts.add_option(
self.cmd_opts.add_option(
"--compile",
action="store_true",
dest="compile",
@ -198,21 +191,21 @@ class InstallCommand(RequirementCommand):
help="Compile Python source files to bytecode",
)
cmd_opts.add_option(
self.cmd_opts.add_option(
"--no-compile",
action="store_false",
dest="compile",
help="Do not compile Python source files to bytecode",
)
cmd_opts.add_option(
self.cmd_opts.add_option(
"--no-warn-script-location",
action="store_false",
dest="warn_script_location",
default=True,
help="Do not warn when installing scripts outside PATH",
)
cmd_opts.add_option(
self.cmd_opts.add_option(
"--no-warn-conflicts",
action="store_false",
dest="warn_about_conflicts",
@ -220,11 +213,11 @@ class InstallCommand(RequirementCommand):
help="Do not warn about broken dependencies",
)
cmd_opts.add_option(cmdoptions.no_binary())
cmd_opts.add_option(cmdoptions.only_binary())
cmd_opts.add_option(cmdoptions.prefer_binary())
cmd_opts.add_option(cmdoptions.require_hashes())
cmd_opts.add_option(cmdoptions.progress_bar())
self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())
self.cmd_opts.add_option(cmdoptions.prefer_binary())
self.cmd_opts.add_option(cmdoptions.require_hashes())
self.cmd_opts.add_option(cmdoptions.progress_bar())
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group,
@ -232,11 +225,11 @@ class InstallCommand(RequirementCommand):
)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
@with_cleanup
def run(self, options, args):
# type: (Values, List[Any]) -> int
# type: (Values, List[str]) -> int
if options.use_user_site and options.target_dir is not None:
raise CommandError("Can not combine '--user' and '--target'")
@ -249,6 +242,7 @@ class InstallCommand(RequirementCommand):
install_options = options.install_options or []
logger.debug("Using %s", get_pip_version())
options.use_user_site = decide_user_install(
options.use_user_site,
prefix_path=options.prefix_path,
@ -272,6 +266,7 @@ class InstallCommand(RequirementCommand):
# Create a target directory for using with the target option
target_temp_dir = TempDirectory(kind="target")
target_temp_dir_path = target_temp_dir.path
self.enter_context(target_temp_dir)
global_options = options.global_options or []
@ -297,12 +292,9 @@ class InstallCommand(RequirementCommand):
)
try:
reqs = self.get_requirements(
args, options, finder, session,
check_supported_wheels=not options.target_dir,
)
reqs = self.get_requirements(args, options, finder, session)
warn_deprecated_install_options(
reject_location_related_install_options(
reqs, options.install_options
)
@ -336,7 +328,7 @@ class InstallCommand(RequirementCommand):
try:
pip_req = requirement_set.get_requirement("pip")
except KeyError:
modifying_pip = None
modifying_pip = False
else:
# If we're not replacing an already installed pip,
# we're not modifying it.
@ -365,29 +357,51 @@ class InstallCommand(RequirementCommand):
# If we're using PEP 517, we cannot do a direct install
# so we fail here.
# We don't care about failures building legacy
# requirements, as we'll fall through to a direct
# install for those.
pep517_build_failures = [
r for r in build_failures if r.use_pep517
]
if pep517_build_failures:
pep517_build_failure_names = [
r.name # type: ignore
for r in build_failures if r.use_pep517
] # type: List[str]
if pep517_build_failure_names:
raise InstallationError(
"Could not build wheels for {} which use"
" PEP 517 and cannot be installed directly".format(
", ".join(r.name for r in pep517_build_failures)))
", ".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.
legacy_build_failure_names = [
r.name # type: ignore
for r in build_failures if not r.use_pep517
] # type: List[str]
if legacy_build_failure_names:
deprecated(
reason=(
"Could not build wheels for {} which do not use "
"PEP 517. pip will fall back to legacy 'setup.py "
"install' for these.".format(
", ".join(legacy_build_failure_names)
)
),
replacement="to fix the wheel build issue reported above",
gone_in="21.0",
issue=8368,
)
to_install = resolver.get_installation_order(
requirement_set
)
# Consistency Checking of the package set we're installing.
# Check for conflicts in the package set we're installing.
conflicts = None # type: Optional[ConflictDetails]
should_warn_about_conflicts = (
not options.ignore_dependencies and
options.warn_about_conflicts
)
if should_warn_about_conflicts:
self._warn_about_conflicts(to_install)
conflicts = self._determine_conflicts(to_install)
# Don't warn about script install locations if
# --target has been specified
@ -402,9 +416,9 @@ class InstallCommand(RequirementCommand):
root=options.root_path,
home=target_temp_dir_path,
prefix=options.prefix_path,
pycompile=options.compile,
warn_script_location=warn_script_location,
use_user_site=options.use_user_site,
pycompile=options.compile,
)
lib_locations = get_lib_location_guesses(
@ -429,6 +443,13 @@ class InstallCommand(RequirementCommand):
except Exception:
pass
items.append(item)
if conflicts is not None:
self._warn_about_conflicts(
conflicts,
new_resolver='2020-resolver' in options.features_enabled,
)
installed_desc = ' '.join(items)
if installed_desc:
write_output(
@ -440,11 +461,12 @@ class InstallCommand(RequirementCommand):
message = create_env_error_message(
error, show_traceback, options.use_user_site,
)
logger.error(message, exc_info=show_traceback)
logger.error(message, exc_info=show_traceback) # noqa
return ERROR
if options.target_dir:
assert target_temp_dir
self._handle_target_dir(
options.target_dir, target_temp_dir, options.upgrade
)
@ -452,96 +474,154 @@ class InstallCommand(RequirementCommand):
return SUCCESS
def _handle_target_dir(self, target_dir, target_temp_dir, upgrade):
# type: (str, TempDirectory, bool) -> None
ensure_dir(target_dir)
# Checking both purelib and platlib directories for installed
# packages to be moved to target directory
lib_dir_list = []
with target_temp_dir:
# Checking both purelib and platlib directories for installed
# packages to be moved to target directory
scheme = distutils_scheme('', home=target_temp_dir.path)
purelib_dir = scheme['purelib']
platlib_dir = scheme['platlib']
data_dir = scheme['data']
# Checking both purelib and platlib directories for installed
# packages to be moved to target directory
scheme = distutils_scheme('', home=target_temp_dir.path)
purelib_dir = scheme['purelib']
platlib_dir = scheme['platlib']
data_dir = scheme['data']
if os.path.exists(purelib_dir):
lib_dir_list.append(purelib_dir)
if os.path.exists(platlib_dir) and platlib_dir != purelib_dir:
lib_dir_list.append(platlib_dir)
if os.path.exists(data_dir):
lib_dir_list.append(data_dir)
if os.path.exists(purelib_dir):
lib_dir_list.append(purelib_dir)
if os.path.exists(platlib_dir) and platlib_dir != purelib_dir:
lib_dir_list.append(platlib_dir)
if os.path.exists(data_dir):
lib_dir_list.append(data_dir)
for lib_dir in lib_dir_list:
for item in os.listdir(lib_dir):
if lib_dir == data_dir:
ddir = os.path.join(data_dir, item)
if any(s.startswith(ddir) for s in lib_dir_list[:-1]):
continue
target_item_dir = os.path.join(target_dir, item)
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
)
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
)
continue
if os.path.isdir(target_item_dir):
shutil.rmtree(target_item_dir)
else:
os.remove(target_item_dir)
for lib_dir in lib_dir_list:
for item in os.listdir(lib_dir):
if lib_dir == data_dir:
ddir = os.path.join(data_dir, item)
if any(s.startswith(ddir) for s in lib_dir_list[:-1]):
continue
target_item_dir = os.path.join(target_dir, item)
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
)
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
)
continue
if os.path.isdir(target_item_dir):
shutil.rmtree(target_item_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 _warn_about_conflicts(self, to_install):
def _determine_conflicts(self, to_install):
# type: (List[InstallRequirement]) -> Optional[ConflictDetails]
try:
package_set, _dep_info = check_install_conflicts(to_install)
return check_install_conflicts(to_install)
except Exception:
logger.error("Error checking for conflicts.", exc_info=True)
return
missing, conflicting = _dep_info
logger.exception(
"Error while checking for conflicts. Please file an issue on "
"pip's issue tracker: https://github.com/pypa/pip/issues/new"
)
return None
# NOTE: There is some duplication here from pip check
def _warn_about_conflicts(self, conflict_details, new_resolver):
# type: (ConflictDetails, bool) -> None
package_set, (missing, conflicting) = conflict_details
if not missing and not conflicting:
return
parts = [] # type: List[str]
if not new_resolver:
parts.append(
"After October 2020 you may experience errors when installing "
"or updating packages. This is because pip will change the "
"way that it resolves dependency conflicts.\n"
)
parts.append(
"We recommend you use --use-feature=2020-resolver to test "
"your packages with the new resolver before it becomes the "
"default.\n"
)
elif not today_is_later_than(year=2020, month=7, day=31):
# NOTE: trailing newlines here are intentional
parts.append(
"Pip will install or upgrade your package(s) and its "
"dependencies without taking into account other packages you "
"already have installed. This may cause an uncaught "
"dependency conflict.\n"
)
form_link = "https://forms.gle/cWKMoDs8sUVE29hz9"
parts.append(
"If you would like pip to take your other packages into "
"account, please tell us here: {}\n".format(form_link)
)
# NOTE: There is some duplication here, with commands/check.py
for project_name in missing:
version = package_set[project_name][0]
for dependency in missing[project_name]:
logger.critical(
"%s %s requires %s, which is not installed.",
project_name, version, dependency[1],
message = (
"{name} {version} requires {requirement}, "
"which is not installed."
).format(
name=project_name,
version=version,
requirement=dependency[1],
)
parts.append(message)
for project_name in conflicting:
version = package_set[project_name][0]
for dep_name, dep_version, req in conflicting[project_name]:
logger.critical(
"%s %s has requirement %s, but you'll have %s %s which is "
"incompatible.",
project_name, version, req, dep_name, dep_version,
message = (
"{name} {version} requires {requirement}, but you'll have "
"{dep_name} {dep_version} which is incompatible."
).format(
name=project_name,
version=version,
requirement=req,
dep_name=dep_name,
dep_version=dep_version,
)
parts.append(message)
logger.critical("\n".join(parts))
def get_lib_location_guesses(*args, **kwargs):
scheme = distutils_scheme('', *args, **kwargs)
def get_lib_location_guesses(
user=False, # type: bool
home=None, # type: Optional[str]
root=None, # type: Optional[str]
isolated=False, # type: bool
prefix=None # type: Optional[str]
):
# type:(...) -> List[str]
scheme = distutils_scheme('', user=user, home=home, root=root,
isolated=isolated, prefix=prefix)
return [scheme['purelib'], scheme['platlib']]
def site_packages_writable(**kwargs):
def site_packages_writable(root, isolated):
# type: (Optional[str], bool) -> bool
return all(
test_writable_dir(d) for d in set(get_lib_location_guesses(**kwargs))
test_writable_dir(d) for d in set(
get_lib_location_guesses(root=root, isolated=isolated))
)
@ -605,7 +685,7 @@ def decide_user_install(
return True
def warn_deprecated_install_options(requirements, options):
def reject_location_related_install_options(requirements, options):
# type: (List[InstallRequirement], Optional[List[str]]) -> None
"""If any location-changing --install-option arguments were passed for
requirements or on the command-line, then show a deprecation warning.
@ -638,24 +718,17 @@ def warn_deprecated_install_options(requirements, options):
if not offenders:
return
deprecated(
reason=(
"Location-changing options found in --install-option: {}. "
"This configuration may cause unexpected behavior and is "
"unsupported.".format(
"; ".join(offenders)
)
),
replacement=(
"using pip-level options like --user, --prefix, --root, and "
"--target"
),
gone_in="20.2",
issue=7309,
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)
)
)
def create_env_error_message(error, show_traceback, using_user_site):
# type: (EnvironmentError, bool, bool) -> str
"""Format an error message for an EnvironmentError
It may occur anytime during the execution of the install command.

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import json
@ -10,10 +7,11 @@ from pip._vendor import six
from pip._internal.cli import cmdoptions
from pip._internal.cli.req_command import IndexGroupCommand
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import CommandError
from pip._internal.index.collector import LinkCollector
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.self_outdated_check import make_link_collector
from pip._internal.utils.misc import (
dist_is_editable,
get_installed_distributions,
@ -21,6 +19,15 @@ from pip._internal.utils.misc import (
write_output,
)
from pip._internal.utils.packaging import get_installer
from pip._internal.utils.parallel import map_multithread
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List, Set, Tuple, Iterator
from pip._internal.network.session import PipSession
from pip._vendor.pkg_resources import Distribution
logger = logging.getLogger(__name__)
@ -32,30 +39,28 @@ class ListCommand(IndexGroupCommand):
Packages are listed in a case-insensitive sorted order.
"""
ignore_require_venv = True
usage = """
%prog [options]"""
def __init__(self, *args, **kw):
super(ListCommand, self).__init__(*args, **kw)
cmd_opts = self.cmd_opts
cmd_opts.add_option(
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-o', '--outdated',
action='store_true',
default=False,
help='List outdated packages')
cmd_opts.add_option(
self.cmd_opts.add_option(
'-u', '--uptodate',
action='store_true',
default=False,
help='List uptodate packages')
cmd_opts.add_option(
self.cmd_opts.add_option(
'-e', '--editable',
action='store_true',
default=False,
help='List editable projects.')
cmd_opts.add_option(
self.cmd_opts.add_option(
'-l', '--local',
action='store_true',
default=False,
@ -68,8 +73,8 @@ class ListCommand(IndexGroupCommand):
action='store_true',
default=False,
help='Only output packages installed in user-site.')
cmd_opts.add_option(cmdoptions.list_path())
cmd_opts.add_option(
self.cmd_opts.add_option(cmdoptions.list_path())
self.cmd_opts.add_option(
'--pre',
action='store_true',
default=False,
@ -77,7 +82,7 @@ class ListCommand(IndexGroupCommand):
"pip only finds stable versions."),
)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--format',
action='store',
dest='list_format',
@ -87,7 +92,7 @@ class ListCommand(IndexGroupCommand):
"or json",
)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--not-required',
action='store_true',
dest='not_required',
@ -95,13 +100,13 @@ class ListCommand(IndexGroupCommand):
"installed packages.",
)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--exclude-editable',
action='store_false',
dest='include_editable',
help='Exclude editable package from output.',
)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--include-editable',
action='store_true',
dest='include_editable',
@ -113,13 +118,14 @@ class ListCommand(IndexGroupCommand):
)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
def _build_package_finder(self, options, session):
# type: (Values, PipSession) -> PackageFinder
"""
Create a package finder appropriate to this list command.
"""
link_collector = make_link_collector(session, options=options)
link_collector = LinkCollector.create(session, options=options)
# Pass allow_yanked=False to ignore yanked versions.
selection_prefs = SelectionPreferences(
@ -133,6 +139,7 @@ class ListCommand(IndexGroupCommand):
)
def run(self, options, args):
# type: (Values, List[str]) -> int
if options.outdated and options.uptodate:
raise CommandError(
"Options --outdated and --uptodate cannot be combined.")
@ -160,30 +167,40 @@ class ListCommand(IndexGroupCommand):
packages = self.get_uptodate(packages, options)
self.output_package_listing(packages, options)
return SUCCESS
def get_outdated(self, packages, options):
# type: (List[Distribution], Values) -> List[Distribution]
return [
dist for dist in self.iter_packages_latest_infos(packages, options)
if dist.latest_version > dist.parsed_version
]
def get_uptodate(self, packages, options):
# type: (List[Distribution], Values) -> List[Distribution]
return [
dist for dist in self.iter_packages_latest_infos(packages, options)
if dist.latest_version == dist.parsed_version
]
def get_not_required(self, packages, options):
dep_keys = set()
# type: (List[Distribution], Values) -> List[Distribution]
dep_keys = set() # type: Set[Distribution]
for dist in packages:
dep_keys.update(requirement.key for requirement in dist.requires())
return {pkg for pkg in packages if pkg.key not in dep_keys}
# Create a set to remove duplicate packages, and cast it to a list
# to keep the return type consistent with get_outdated and
# get_uptodate
return list({pkg for pkg in packages if pkg.key not in dep_keys})
def iter_packages_latest_infos(self, packages, options):
# type: (List[Distribution], Values) -> Iterator[Distribution]
with self._build_session(options) as session:
finder = self._build_package_finder(options, session)
def latest_info(dist):
# type: (Distribution) -> Distribution
typ = 'unknown'
all_candidates = finder.find_all_candidates(dist.key)
if not options.pre:
@ -208,11 +225,12 @@ class ListCommand(IndexGroupCommand):
dist.latest_filetype = typ
return dist
for dist in map(latest_info, packages):
for dist in map_multithread(latest_info, packages):
if dist is not None:
yield dist
def output_package_listing(self, packages, options):
# type: (List[Distribution], Values) -> None
packages = sorted(
packages,
key=lambda dist: dist.project_name.lower(),
@ -231,6 +249,7 @@ class ListCommand(IndexGroupCommand):
write_output(format_for_json(packages, options))
def output_package_listing_columns(self, data, header):
# type: (List[List[str]], List[str]) -> None
# insert the header first: we need to know the size of column names
if len(data) > 0:
data.insert(0, header)
@ -246,6 +265,7 @@ class ListCommand(IndexGroupCommand):
def format_for_columns(pkgs, options):
# type: (List[Distribution], Values) -> Tuple[List[List[str]], List[str]]
"""
Convert the package data into something usable
by output_package_listing_columns.
@ -283,6 +303,7 @@ def format_for_columns(pkgs, options):
def format_for_json(packages, options):
# type: (List[Distribution], Values) -> str
data = []
for dist in packages:
info = {

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import logging
@ -22,7 +19,17 @@ from pip._internal.models.index import PyPI
from pip._internal.network.xmlrpc import PipXmlrpcTransport
from pip._internal.utils.compat import get_terminal_size
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import write_output
from pip._internal.utils.misc import get_distribution, write_output
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List, Dict, Optional
from typing_extensions import TypedDict
TransformedHit = TypedDict(
'TransformedHit',
{'name': str, 'summary': str, 'versions': List[str]},
)
logger = logging.getLogger(__name__)
@ -34,8 +41,8 @@ class SearchCommand(Command, SessionCommandMixin):
%prog [options] <query>"""
ignore_require_venv = True
def __init__(self, *args, **kw):
super(SearchCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-i', '--index',
dest='index',
@ -46,6 +53,7 @@ class SearchCommand(Command, SessionCommandMixin):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
if not args:
raise CommandError('Missing required argument (search query).')
query = args
@ -62,6 +70,7 @@ class SearchCommand(Command, SessionCommandMixin):
return NO_MATCHES_FOUND
def search(self, query, options):
# type: (List[str], Values) -> List[Dict[str, str]]
index_url = options.index
session = self.get_default_session(options)
@ -73,12 +82,13 @@ class SearchCommand(Command, SessionCommandMixin):
def transform_hits(hits):
# type: (List[Dict[str, str]]) -> List[TransformedHit]
"""
The list from pypi is really a list of versions. We want a list of
packages with the list of versions stored inline. This converts the
list from pypi into one we can use.
"""
packages = OrderedDict()
packages = OrderedDict() # type: OrderedDict[str, TransformedHit]
for hit in hits:
name = hit['name']
summary = hit['summary']
@ -101,6 +111,7 @@ def transform_hits(hits):
def print_results(hits, name_column_width=None, terminal_width=None):
# type: (List[TransformedHit], Optional[int], Optional[int]) -> None
if not hits:
return
if name_column_width is None:
@ -118,8 +129,9 @@ def print_results(hits, name_column_width=None, terminal_width=None):
target_width = terminal_width - name_column_width - 5
if target_width > 10:
# wrap and indent summary to fit terminal
summary = textwrap.wrap(summary, target_width)
summary = ('\n' + ' ' * (name_column_width + 3)).join(summary)
summary_lines = textwrap.wrap(summary, target_width)
summary = ('\n' + ' ' * (name_column_width + 3)).join(
summary_lines)
line = '{name_latest:{name_column_width}} - {summary}'.format(
name_latest='{name} ({latest})'.format(**locals()),
@ -127,7 +139,8 @@ def print_results(hits, name_column_width=None, terminal_width=None):
try:
write_output(line)
if name in installed_packages:
dist = pkg_resources.get_distribution(name)
dist = get_distribution(name)
assert dist is not None
with indent_log():
if dist.version == latest:
write_output('INSTALLED: %s (latest)', dist.version)
@ -143,4 +156,5 @@ def print_results(hits, name_column_width=None, terminal_width=None):
def highest_version(versions):
# type: (List[str]) -> str
return max(versions, key=parse_version)

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import logging
@ -13,6 +10,11 @@ from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.utils.misc import write_output
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List, Dict, Iterator
logger = logging.getLogger(__name__)
@ -28,8 +30,8 @@ class ShowCommand(Command):
%prog [options] <package> ..."""
ignore_require_venv = True
def __init__(self, *args, **kw):
super(ShowCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-f', '--files',
dest='files',
@ -40,6 +42,7 @@ class ShowCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
if not args:
logger.warning('ERROR: Please provide a package name or names.')
return ERROR
@ -53,6 +56,7 @@ class ShowCommand(Command):
def search_packages_info(query):
# type: (List[str]) -> Iterator[Dict[str, str]]
"""
Gather details from installed distributions. Print distribution name,
version, location, and installed files. Installed files requires a
@ -71,6 +75,7 @@ def search_packages_info(query):
logger.warning('Package(s) not found: %s', ', '.join(missing))
def get_requiring_packages(package_name):
# type: (str) -> List[str]
canonical_name = canonicalize_name(package_name)
return [
pkg.project_name for pkg in pkg_resources.working_set
@ -88,12 +93,12 @@ def search_packages_info(query):
'required_by': get_requiring_packages(dist.project_name)
}
file_list = None
metadata = None
metadata = ''
if isinstance(dist, pkg_resources.DistInfoDistribution):
# RECORDs should be part of .dist-info metadatas
if dist.has_metadata('RECORD'):
lines = dist.get_metadata_lines('RECORD')
paths = [l.split(',')[0] for l in lines]
paths = [line.split(',')[0] for line in lines]
paths = [os.path.join(dist.location, p) for p in paths]
file_list = [os.path.relpath(p, dist.location) for p in paths]
@ -141,6 +146,7 @@ def search_packages_info(query):
def print_results(distributions, list_files=False, verbose=False):
# type: (Iterator[Dict[str, str]], bool, bool) -> bool
"""
Print the information from installed distributions found.
"""

View File

@ -1,12 +1,10 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.cli.base_command import Command
from pip._internal.cli.req_command import SessionCommandMixin
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import InstallationError
from pip._internal.req import parse_requirements
from pip._internal.req.constructors import (
@ -14,6 +12,11 @@ from pip._internal.req.constructors import (
install_req_from_parsed_requirement,
)
from pip._internal.utils.misc import protect_pip_from_modification_on_windows
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List
class UninstallCommand(Command, SessionCommandMixin):
@ -31,8 +34,8 @@ class UninstallCommand(Command, SessionCommandMixin):
%prog [options] <package> ...
%prog [options] -r <requirements file> ..."""
def __init__(self, *args, **kw):
super(UninstallCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-r', '--requirement',
dest='requirements',
@ -51,6 +54,7 @@ class UninstallCommand(Command, SessionCommandMixin):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
session = self.get_default_session(options)
reqs_to_uninstall = {}
@ -87,3 +91,5 @@ class UninstallCommand(Command, SessionCommandMixin):
)
if uninstall_pathset:
uninstall_pathset.commit()
return SUCCESS

View File

@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import logging
@ -12,6 +9,7 @@ import shutil
from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import CommandError
from pip._internal.req.req_tracker import get_requirement_tracker
from pip._internal.utils.misc import ensure_dir, normalize_path
@ -21,7 +19,7 @@ from pip._internal.wheel_builder import build, should_build_for_wheel_command
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import Any, List
from typing import List
logger = logging.getLogger(__name__)
@ -49,12 +47,10 @@ class WheelCommand(RequirementCommand):
%prog [options] [-e] <local project path> ...
%prog [options] <archive url/path> ..."""
def __init__(self, *args, **kw):
super(WheelCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
cmd_opts = self.cmd_opts
cmd_opts.add_option(
self.cmd_opts.add_option(
'-w', '--wheel-dir',
dest='wheel_dir',
metavar='dir',
@ -62,29 +58,29 @@ class WheelCommand(RequirementCommand):
help=("Build wheels into <dir>, where the default is the "
"current working directory."),
)
cmd_opts.add_option(cmdoptions.no_binary())
cmd_opts.add_option(cmdoptions.only_binary())
cmd_opts.add_option(cmdoptions.prefer_binary())
cmd_opts.add_option(
self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())
self.cmd_opts.add_option(cmdoptions.prefer_binary())
self.cmd_opts.add_option(
'--build-option',
dest='build_options',
metavar='options',
action='append',
help="Extra arguments to be supplied to 'setup.py bdist_wheel'.",
)
cmd_opts.add_option(cmdoptions.no_build_isolation())
cmd_opts.add_option(cmdoptions.use_pep517())
cmd_opts.add_option(cmdoptions.no_use_pep517())
cmd_opts.add_option(cmdoptions.constraints())
cmd_opts.add_option(cmdoptions.editable())
cmd_opts.add_option(cmdoptions.requirements())
cmd_opts.add_option(cmdoptions.src())
cmd_opts.add_option(cmdoptions.ignore_requires_python())
cmd_opts.add_option(cmdoptions.no_deps())
cmd_opts.add_option(cmdoptions.build_dir())
cmd_opts.add_option(cmdoptions.progress_bar())
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
self.cmd_opts.add_option(cmdoptions.use_pep517())
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.editable())
self.cmd_opts.add_option(cmdoptions.requirements())
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())
cmd_opts.add_option(
self.cmd_opts.add_option(
'--global-option',
dest='global_options',
action='append',
@ -92,7 +88,7 @@ class WheelCommand(RequirementCommand):
help="Extra global options to be supplied to the setup.py "
"call before the 'bdist_wheel' command.")
cmd_opts.add_option(
self.cmd_opts.add_option(
'--pre',
action='store_true',
default=False,
@ -100,7 +96,7 @@ class WheelCommand(RequirementCommand):
"pip only finds stable versions."),
)
cmd_opts.add_option(cmdoptions.require_hashes())
self.cmd_opts.add_option(cmdoptions.require_hashes())
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group,
@ -108,11 +104,11 @@ class WheelCommand(RequirementCommand):
)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
@with_cleanup
def run(self, options, args):
# type: (Values, List[Any]) -> None
# type: (Values, List[str]) -> int
cmdoptions.check_install_build_global(options)
session = self.get_default_session(options)
@ -188,3 +184,5 @@ class WheelCommand(RequirementCommand):
raise CommandError(
"Failed to build one or more wheels"
)
return SUCCESS

View File

@ -11,9 +11,6 @@ Some terminology:
A single word describing where the configuration key-value pair came from
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import locale
import logging
import os
@ -114,7 +111,7 @@ class Configuration(object):
"""
def __init__(self, isolated, load_only=None):
# type: (bool, Kind) -> None
# type: (bool, Optional[Kind]) -> None
super(Configuration, self).__init__()
_valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.SITE, None]
@ -124,8 +121,8 @@ class Configuration(object):
", ".join(map(repr, _valid_load_only[:-1]))
)
)
self.isolated = isolated # type: bool
self.load_only = load_only # type: Optional[Kind]
self.isolated = isolated
self.load_only = load_only
# The order here determines the override order.
self._override_order = [
@ -185,6 +182,7 @@ class Configuration(object):
"""
self._ensure_have_load_only()
assert self.load_only
fname, parser = self._get_parser_to_modify()
if parser is not None:
@ -200,10 +198,10 @@ class Configuration(object):
def unset_value(self, key):
# type: (str) -> None
"""Unset a value in the configuration.
"""
"""Unset a value in the configuration."""
self._ensure_have_load_only()
assert self.load_only
if key not in self._config[self.load_only]:
raise ConfigurationError("No such key - {}".format(key))
@ -211,30 +209,18 @@ class Configuration(object):
if parser is not None:
section, name = _disassemble_key(key)
# Remove the key in the parser
modified_something = False
if parser.has_section(section):
# Returns whether the option was removed or not
modified_something = parser.remove_option(section, name)
if modified_something:
# name removed from parser, section may now be empty
section_iter = iter(parser.items(section))
try:
val = next(section_iter)
except StopIteration:
val = None
if val is None:
parser.remove_section(section)
self._mark_as_modified(fname, parser)
else:
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."
)
# The section may be empty after the option was removed.
if not parser.items(section):
parser.remove_section(section)
self._mark_as_modified(fname, parser)
del self._config[self.load_only][key]
def save(self):
@ -280,7 +266,7 @@ class Configuration(object):
# type: () -> None
"""Loads configuration from configuration files
"""
config_files = dict(self._iter_config_files())
config_files = dict(self.iter_config_files())
if config_files[kinds.ENV][0:1] == [os.devnull]:
logger.debug(
"Skipping loading configuration files due to "
@ -342,7 +328,7 @@ class Configuration(object):
"""Loads configuration from environment variables
"""
self._config[kinds.ENV_VAR].update(
self._normalized_keys(":env:", self._get_environ_vars())
self._normalized_keys(":env:", self.get_environ_vars())
)
def _normalized_keys(self, section, items):
@ -358,7 +344,7 @@ class Configuration(object):
normalized[key] = val
return normalized
def _get_environ_vars(self):
def get_environ_vars(self):
# type: () -> Iterable[Tuple[str, str]]
"""Returns a generator with all environmental vars with prefix PIP_"""
for key, val in os.environ.items():
@ -370,7 +356,7 @@ class Configuration(object):
yield key[4:].lower(), val
# XXX: This is patched in the tests.
def _iter_config_files(self):
def iter_config_files(self):
# type: () -> Iterable[Tuple[Kind, List[str]]]
"""Yields variant and configuration files associated with it.
@ -401,9 +387,15 @@ class Configuration(object):
# 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]
"""Get values present in a config file"""
return self._config[variant]
def _get_parser_to_modify(self):
# type: () -> Tuple[str, RawConfigParser]
# Determine which parser to modify
assert self.load_only
parsers = self._parsers[self.load_only]
if not parsers:
# This should not happen if everything works correctly.

View File

@ -1,8 +1,5 @@
"""Exceptions used throughout package"""
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
from itertools import chain, groupby, repeat
@ -12,10 +9,20 @@ from pip._vendor.six import iteritems
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Optional
from typing import Any, Optional, List, Dict, Text
from pip._vendor.pkg_resources import Distribution
from pip._vendor.requests.models import Response, Request
from pip._vendor.six import PY3
from pip._vendor.six.moves import configparser
from pip._internal.req.req_install import InstallRequirement
if PY3:
from hashlib import _Hash
else:
from hashlib import _hash as _Hash
class PipError(Exception):
"""Base pip exception"""
@ -84,10 +91,38 @@ class CommandError(PipError):
"""Raised when there is an error in command-line arguments"""
class SubProcessError(PipError):
"""Raised when there is an error raised while executing a
command in subprocess"""
class PreviousBuildDirError(PipError):
"""Raised when there's a previous conflicting build directory"""
class NetworkConnectionError(PipError):
"""HTTP connection error"""
def __init__(self, error_msg, response=None, request=None):
# type: (Text, Response, Request) -> None
"""
Initialize NetworkConnectionError with `request` and `response`
objects.
"""
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')):
self.request = self.response.request
super(NetworkConnectionError, self).__init__(
error_msg, response, request)
def __str__(self):
# type: () -> str
return str(self.error_msg)
class InvalidWheelFilename(InstallationError):
"""Invalid wheel filename."""
@ -96,16 +131,39 @@ class UnsupportedWheel(InstallationError):
"""Unsupported wheel."""
class MetadataInconsistent(InstallationError):
"""Built metadata contains inconsistent information.
This is raised when the metadata contains values (e.g. name and version)
that do not match the information previously obtained from sdist filename
or user-supplied ``#egg=`` value.
"""
def __init__(self, ireq, field, built):
# type: (InstallRequirement, str, Any) -> None
self.ireq = ireq
self.field = field
self.built = built
def __str__(self):
# type: () -> str
return "Requested {} has different {} in metadata: {!r}".format(
self.ireq, self.field, self.built,
)
class HashErrors(InstallationError):
"""Multiple HashError instances rolled into one for reporting"""
def __init__(self):
self.errors = []
# type: () -> None
self.errors = [] # type: List[HashError]
def append(self, error):
# type: (HashError) -> None
self.errors.append(error)
def __str__(self):
# type: () -> str
lines = []
self.errors.sort(key=lambda e: e.order)
for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):
@ -113,11 +171,14 @@ class HashErrors(InstallationError):
lines.extend(e.body() for e in errors_of_cls)
if lines:
return '\n'.join(lines)
return ''
def __nonzero__(self):
# type: () -> bool
return bool(self.errors)
def __bool__(self):
# type: () -> bool
return self.__nonzero__()
@ -139,8 +200,10 @@ class HashError(InstallationError):
"""
req = None # type: Optional[InstallRequirement]
head = ''
order = None # type: Optional[int]
def body(self):
# type: () -> str
"""Return a summary of me for display under the heading.
This default implementation simply prints a description of the
@ -153,9 +216,11 @@ class HashError(InstallationError):
return ' {}'.format(self._requirement_name())
def __str__(self):
# type: () -> str
return '{}\n{}'.format(self.head, self.body())
def _requirement_name(self):
# type: () -> str
"""Return a description of the requirement that triggered me.
This default implementation returns long description of the req, with
@ -196,6 +261,7 @@ class HashMissing(HashError):
'has a hash.)')
def __init__(self, gotten_hash):
# type: (str) -> None
"""
:param gotten_hash: The hash of the (possibly malicious) archive we
just downloaded
@ -203,6 +269,7 @@ class HashMissing(HashError):
self.gotten_hash = gotten_hash
def body(self):
# type: () -> str
# Dodge circular import.
from pip._internal.utils.hashes import FAVORITE_HASH
@ -245,6 +312,7 @@ class HashMismatch(HashError):
'someone may have tampered with them.')
def __init__(self, allowed, gots):
# type: (Dict[str, List[str]], Dict[str, _Hash]) -> None
"""
:param allowed: A dict of algorithm names pointing to lists of allowed
hex digests
@ -255,10 +323,12 @@ class HashMismatch(HashError):
self.gots = gots
def body(self):
# type: () -> str
return ' {}:\n{}'.format(self._requirement_name(),
self._hash_comparison())
def _hash_comparison(self):
# type: () -> str
"""
Return a comparison of actual and expected hash values.
@ -270,11 +340,12 @@ class HashMismatch(HashError):
"""
def hash_then_or(hash_name):
# type: (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'))
lines = []
lines = [] # type: List[str]
for hash_name, expecteds in iteritems(self.allowed):
prefix = hash_then_or(hash_name)
lines.extend((' Expected {} {}'.format(next(prefix), e))
@ -294,15 +365,17 @@ class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
"""
def __init__(self, reason="could not be loaded", fname=None, error=None):
# type: (str, Optional[str], Optional[configparser.Error]) -> None
super(ConfigurationFileCouldNotBeLoaded, self).__init__(error)
self.reason = reason
self.fname = fname
self.error = error
def __str__(self):
# type: () -> str
if self.fname is not None:
message_part = " in {}.".format(self.fname)
else:
assert self.error is not None
message_part = ".\n{}\n".format(self.error.message)
message_part = ".\n{}\n".format(self.error)
return "Configuration file {}{}".format(self.reason, message_part)

View File

@ -13,11 +13,14 @@ from collections import OrderedDict
from pip._vendor import html5lib, requests
from pip._vendor.distlib.compat import unescape
from pip._vendor.requests.exceptions import HTTPError, RetryError, SSLError
from pip._vendor.requests.exceptions import RetryError, SSLError
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._vendor.six.moves.urllib import request as urllib_request
from pip._internal.exceptions import NetworkConnectionError
from pip._internal.models.link import Link
from pip._internal.models.search_scope import SearchScope
from pip._internal.network.utils import raise_for_status
from pip._internal.utils.filetypes import ARCHIVE_EXTENSIONS
from pip._internal.utils.misc import pairwise, redact_auth_from_url
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@ -25,6 +28,7 @@ from pip._internal.utils.urls import path_to_url, url_to_path
from pip._internal.vcs import is_url, vcs
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import (
Callable, Iterable, List, MutableMapping, Optional,
Protocol, Sequence, Tuple, TypeVar, Union,
@ -33,7 +37,6 @@ if MYPY_CHECK_RUNNING:
from pip._vendor.requests import Response
from pip._internal.models.search_scope import SearchScope
from pip._internal.network.session import PipSession
HTMLElement = xml.etree.ElementTree.Element
@ -122,7 +125,7 @@ def _ensure_html_response(url, session):
raise _NotHTTP()
resp = session.head(url, allow_redirects=True)
resp.raise_for_status()
raise_for_status(resp)
_ensure_html_header(resp)
@ -166,7 +169,7 @@ def _get_html_response(url, session):
"Cache-Control": "max-age=0",
},
)
resp.raise_for_status()
raise_for_status(resp)
# The check for archives above only works if the url ends with
# something that looks like an archive. However that is not a
@ -434,7 +437,8 @@ def _get_html_page(link, session=None):
# Check for VCS schemes that do not support lookup as web pages.
vcs_scheme = _match_vcs_scheme(url)
if vcs_scheme:
logger.debug('Cannot look at %s URL %s', 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
@ -450,16 +454,17 @@ def _get_html_page(link, session=None):
try:
resp = _get_html_response(url, session=session)
except _NotHTTP:
logger.debug(
logger.warning(
'Skipping page %s because it looks like an archive, and cannot '
'be checked by HEAD.', link,
'be checked by a HTTP HEAD request.', link,
)
except _NotHTML as exc:
logger.debug(
'Skipping page %s because the %s request got Content-Type: %s',
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,
)
except HTTPError as exc:
except NetworkConnectionError as exc:
_handle_get_page_fail(link, exc)
except RetryError as exc:
_handle_get_page_fail(link, exc)
@ -524,8 +529,7 @@ def group_locations(locations, expand_dir=False):
urls.append(url)
else:
logger.warning(
"Path '{0}' is ignored: "
"it is a directory.".format(path),
"Path '%s' is ignored: it is a directory.", path,
)
elif os.path.isfile(path):
sort_path(path)
@ -599,6 +603,33 @@ class LinkCollector(object):
self.search_scope = search_scope
self.session = session
@classmethod
def create(cls, session, options, suppress_no_index=False):
# type: (PipSession, Values, bool) -> LinkCollector
"""
:param session: The Session to use to make requests.
:param suppress_no_index: Whether to ignore the --no-index option
when constructing the SearchScope object.
"""
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),
)
index_urls = []
# Make sure find_links is a list before passing to create().
find_links = options.find_links or []
search_scope = SearchScope.create(
find_links=find_links, index_urls=index_urls,
)
link_collector = LinkCollector(
session=session, search_scope=search_scope,
)
return link_collector
@property
def find_links(self):
# type: () -> List[str]

View File

@ -556,23 +556,7 @@ class CandidateEvaluator(object):
"""
if not candidates:
return None
best_candidate = max(candidates, key=self._sort_key)
# Log a warning per PEP 592 if necessary before returning.
link = best_candidate.link
if link.is_yanked:
reason = link.yanked_reason or '<none given>'
msg = (
# Mark this as a unicode string to prevent
# "UnicodeEncodeError: 'ascii' codec can't encode character"
# in Python 2 when the reason contains non-ascii characters.
u'The candidate selected for download or install is a '
'yanked version: {candidate}\n'
'Reason for being yanked: {reason}'
).format(candidate=best_candidate, reason=reason)
logger.warning(msg)
return best_candidate
def compute_best_candidate(
@ -674,6 +658,11 @@ class PackageFinder(object):
ignore_requires_python=selection_prefs.ignore_requires_python,
)
@property
def target_python(self):
# type: () -> TargetPython
return self._target_python
@property
def search_scope(self):
# type: () -> SearchScope
@ -709,6 +698,15 @@ class PackageFinder(object):
# type: () -> None
self._candidate_prefs.allow_all_prereleases = True
@property
def prefer_binary(self):
# type: () -> bool
return self._candidate_prefs.prefer_binary
def set_prefer_binary(self):
# type: () -> None
self._candidate_prefs.prefer_binary = True
def make_link_evaluator(self, project_name):
# type: (str) -> LinkEvaluator
canonical_name = canonicalize_name(project_name)
@ -889,11 +887,11 @@ class PackageFinder(object):
return candidate_evaluator.compute_best_candidate(candidates)
def find_requirement(self, req, upgrade):
# type: (InstallRequirement, bool) -> Optional[Link]
# type: (InstallRequirement, bool) -> Optional[InstallationCandidate]
"""Try to find a Link matching req
Expects req, an InstallRequirement and upgrade, a boolean
Returns a Link if found,
Returns a InstallationCandidate if found,
Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise
"""
hashes = req.hashes(trust_internet=False)
@ -967,7 +965,7 @@ class PackageFinder(object):
best_candidate.version,
_format_versions(best_candidate_result.iter_applicable()),
)
return best_candidate.link
return best_candidate
def _find_name_version_sep(fragment, canonical_name):

View File

@ -138,7 +138,7 @@ def distutils_scheme(
if running_under_virtualenv():
scheme['headers'] = os.path.join(
sys.prefix,
i.prefix,
'include',
'site',
'python{}'.format(get_major_minor_version()),

View File

@ -12,6 +12,8 @@ class InstallationCandidate(KeyBasedCompareMixin):
"""Represents a potential "candidate" for installation.
"""
__slots__ = ["name", "version", "link"]
def __init__(self, name, version, link):
# type: (str, str, Link) -> None
self.name = name

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.exceptions import CommandError
@ -14,6 +11,8 @@ class FormatControl(object):
"""Helper for managing formats from which a package can be installed.
"""
__slots__ = ["no_binary", "only_binary"]
def __init__(self, no_binary=None, only_binary=None):
# type: (Optional[Set[str]], Optional[Set[str]]) -> None
if no_binary is None:
@ -26,7 +25,16 @@ class FormatControl(object):
def __eq__(self, other):
# type: (object) -> bool
return self.__dict__ == other.__dict__
if not isinstance(other, self.__class__):
return NotImplemented
if self.__slots__ != other.__slots__:
return False
return all(
getattr(self, k) == getattr(other, k)
for k in self.__slots__
)
def __ne__(self, other):
# type: (object) -> bool
@ -42,7 +50,7 @@ class FormatControl(object):
@staticmethod
def handle_mutual_excludes(value, target, other):
# type: (str, Optional[Set[str]], Optional[Set[str]]) -> None
# type: (str, Set[str], Set[str]) -> None
if value.startswith('-'):
raise CommandError(
"--no-binary / --only-binary option requires 1 argument."

View File

@ -5,6 +5,9 @@ class PackageIndex(object):
"""Represents a Package Index and provides easier access to endpoints
"""
__slots__ = ['url', 'netloc', 'simple_url', 'pypi_url',
'file_storage_domain']
def __init__(self, url, file_storage_domain):
# type: (str, str) -> None
super(PackageIndex, self).__init__()

View File

@ -24,6 +24,15 @@ class Link(KeyBasedCompareMixin):
"""Represents a parsed link from a Package Index's simple URL
"""
__slots__ = [
"_parsed_url",
"_url",
"comes_from",
"requires_python",
"yanked_reason",
"cache_link_parsing",
]
def __init__(
self,
url, # type: str

View File

@ -6,10 +6,16 @@ https://docs.python.org/3/install/index.html#alternate-installation.
"""
SCHEME_KEYS = ['platlib', 'purelib', 'headers', 'scripts', 'data']
class Scheme(object):
"""A Scheme holds paths which are used as the base directories for
artifacts associated with a Python package.
"""
__slots__ = SCHEME_KEYS
def __init__(
self,
platlib, # type: str

View File

@ -24,6 +24,8 @@ class SearchScope(object):
Encapsulates the locations that pip is configured to search.
"""
__slots__ = ["find_links", "index_urls"]
@classmethod
def create(
cls,
@ -93,8 +95,8 @@ class SearchScope(object):
# exceptions for malformed URLs
if not purl.scheme and not purl.netloc:
logger.warning(
'The index url "{}" seems invalid, '
'please provide a scheme.'.format(redacted_index_url))
'The index url "%s" seems invalid, '
'please provide a scheme.', redacted_index_url)
redacted_index_urls.append(redacted_index_url)

View File

@ -6,12 +6,14 @@ if MYPY_CHECK_RUNNING:
class SelectionPreferences(object):
"""
Encapsulates the candidate selection preferences for downloading
and installing files.
"""
__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
# that decision to be made explicit in the calling code, which helps

View File

@ -20,6 +20,16 @@ class TargetPython(object):
for a package install, download, etc.
"""
__slots__ = [
"_given_py_version_info",
"abi",
"implementation",
"platform",
"py_version",
"py_version_info",
"_valid_tags",
]
def __init__(
self,
platform=None, # type: Optional[str]

View File

@ -4,9 +4,6 @@ Contains interface (MultiDomainBasicAuth) and associated glue code for
providing credentials in the context of network requests.
"""
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
import logging
from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
@ -23,11 +20,12 @@ from pip._internal.utils.misc import (
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import Dict, Optional, Tuple
from typing import Dict, Optional, Tuple, List, Any
from pip._internal.vcs.versioncontrol import AuthInfo
from pip._vendor.requests.models import Response, Request
Credentials = Tuple[str, str, str]
logger = logging.getLogger(__name__)
@ -44,7 +42,9 @@ except Exception as exc:
def get_keyring_auth(url, username):
# type: (str, str) -> Optional[AuthInfo]
"""Return the tuple auth for a given url from keyring."""
global keyring
if not url or not keyring:
return None
@ -70,12 +70,14 @@ def get_keyring_auth(url, username):
logger.warning(
"Keyring is skipped due to an exception: %s", str(exc),
)
keyring = None
return None
class MultiDomainBasicAuth(AuthBase):
def __init__(self, prompting=True, index_urls=None):
# type: (bool, Optional[Values]) -> None
# type: (bool, Optional[List[str]]) -> None
self.prompting = prompting
self.index_urls = index_urls
self.passwords = {} # type: Dict[str, AuthInfo]
@ -87,6 +89,7 @@ class MultiDomainBasicAuth(AuthBase):
self._credentials_to_save = None # type: Optional[Credentials]
def _get_index_url(self, url):
# type: (str) -> Optional[str]
"""Return the original index URL matching the requested URL.
Cached or dynamically generated credentials may work against
@ -106,9 +109,11 @@ class MultiDomainBasicAuth(AuthBase):
prefix = remove_auth_from_url(u).rstrip("/") + "/"
if url.startswith(prefix):
return u
return None
def _get_new_credentials(self, original_url, allow_netrc=True,
allow_keyring=True):
# type: (str, bool, bool) -> AuthInfo
"""Find and return credentials for the specified URL."""
# Split the credentials and netloc from the url.
url, netloc, url_user_password = split_auth_netloc_from_url(
@ -158,6 +163,7 @@ class MultiDomainBasicAuth(AuthBase):
return username, password
def _get_url_and_credentials(self, original_url):
# type: (str) -> Tuple[str, Optional[str], Optional[str]]
"""Return the credentials to use for the provided URL.
If allowed, netrc and keyring may be used to obtain the
@ -198,6 +204,7 @@ class MultiDomainBasicAuth(AuthBase):
return url, username, password
def __call__(self, req):
# type: (Request) -> Request
# Get credentials for this request
url, username, password = self._get_url_and_credentials(req.url)
@ -215,22 +222,25 @@ class MultiDomainBasicAuth(AuthBase):
# Factored out to allow for easy patching in tests
def _prompt_for_password(self, netloc):
# type: (str) -> Tuple[Optional[str], Optional[str], bool]
username = ask_input("User for {}: ".format(netloc))
if not username:
return None, None
return None, None, False
auth = get_keyring_auth(netloc, username)
if auth:
if auth and auth[0] is not None and auth[1] is not None:
return auth[0], auth[1], False
password = ask_password("Password: ")
return username, password, True
# Factored out to allow for easy patching in tests
def _should_save_password_to_keyring(self):
# type: () -> bool
if not keyring:
return False
return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
def handle_401(self, resp, **kwargs):
# type: (Response, **Any) -> Response
# We only care about 401 responses, anything else we want to just
# pass through the actual response
if resp.status_code != 401:
@ -276,6 +286,7 @@ class MultiDomainBasicAuth(AuthBase):
return new_resp
def warn_on_401(self, resp, **kwargs):
# type: (Response, **Any) -> None
"""Response callback to warn about incorrect credentials."""
if resp.status_code == 401:
logger.warning(
@ -283,6 +294,7 @@ class MultiDomainBasicAuth(AuthBase):
)
def save_credentials(self, resp, **kwargs):
# type: (Response, **Any) -> None
"""Response callback to save credentials on success."""
assert keyring is not None, "should never reach here without keyring"
if not keyring:

View File

@ -1,9 +1,6 @@
"""HTTP cache implementation.
"""
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
import os
from contextlib import contextmanager
@ -16,7 +13,7 @@ from pip._internal.utils.misc import ensure_dir
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Optional
from typing import Optional, Iterator
def is_from_cache(response):
@ -26,6 +23,7 @@ def is_from_cache(response):
@contextmanager
def suppressed_cache_errors():
# type: () -> Iterator[None]
"""If we can't access the cache then we can just skip caching and process
requests as if caching wasn't enabled.
"""

View File

@ -5,13 +5,17 @@ import logging
import mimetypes
import os
from pip._vendor import requests
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE
from pip._internal.cli.progress_bars import DownloadProgressProvider
from pip._internal.exceptions import NetworkConnectionError
from pip._internal.models.index import PyPI
from pip._internal.network.cache import is_from_cache
from pip._internal.network.utils import response_chunks
from pip._internal.network.utils import (
HEADERS,
raise_for_status,
response_chunks,
)
from pip._internal.utils.misc import (
format_size,
redact_auth_from_url,
@ -132,31 +136,8 @@ def _get_http_response_filename(resp, link):
def _http_get_download(session, link):
# type: (PipSession, Link) -> Response
target_url = link.url.split('#', 1)[0]
resp = session.get(
target_url,
# We use Accept-Encoding: identity here because requests
# defaults to accepting compressed responses. This breaks in
# a variety of ways depending on how the server is configured.
# - Some servers will notice that the file isn't a compressible
# file and will leave the file alone and with an empty
# Content-Encoding
# - Some servers will notice that the file is already
# compressed and will leave the file alone and will add a
# Content-Encoding: gzip header
# - Some servers won't notice anything at all and will take
# a file that's already been compressed and compress it again
# and set the Content-Encoding: gzip header
# By setting this to request only the identity encoding We're
# hoping to eliminate the third case. Hopefully there does not
# exist a server which when given a file will notice it is
# already compressed and that you're not asking for a
# compressed file and will then decompress it before sending
# because if that's the case I don't think it'll ever be
# possible to make this work.
headers={"Accept-Encoding": "identity"},
stream=True,
)
resp.raise_for_status()
resp = session.get(target_url, headers=HEADERS, stream=True)
raise_for_status(resp)
return resp
@ -187,7 +168,8 @@ class Downloader(object):
# type: (Link) -> Download
try:
resp = _http_get_download(self._session, link)
except requests.HTTPError as e:
except NetworkConnectionError as e:
assert e.response is not None
logger.critical(
"HTTP error %s while getting %s", e.response.status_code, link
)

View File

@ -0,0 +1,235 @@
"""Lazy ZIP over HTTP"""
__all__ = ['HTTPRangeRequestUnsupported', 'dist_from_wheel_url']
from bisect import bisect_left, bisect_right
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
from zipfile import BadZipfile, ZipFile
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE
from pip._vendor.six.moves import range
from pip._internal.network.utils import (
HEADERS,
raise_for_status,
response_chunks,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel
if MYPY_CHECK_RUNNING:
from typing import Any, Dict, Iterator, List, Optional, Tuple
from pip._vendor.pkg_resources import Distribution
from pip._vendor.requests.models import Response
from pip._internal.network.session import PipSession
class HTTPRangeRequestUnsupported(Exception):
pass
def dist_from_wheel_url(name, url, session):
# type: (str, str, PipSession) -> Distribution
"""Return a pkg_resources.Distribution 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:
# 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
# After context manager exit, wheel.name
# is an invalid file by intention.
return pkg_resources_distribution_for_wheel(zip_file, name, wheel.name)
class LazyZipOverHTTP(object):
"""File-like object mapped to a ZIP file over HTTP.
This uses HTTP range requests to lazily fetch the file's content,
which is supposed to be fed to ZipFile. If such requests are not
supported by the server, raise HTTPRangeRequestUnsupported
during initialization.
"""
def __init__(self, url, session, chunk_size=CONTENT_CHUNK_SIZE):
# type: (str, PipSession, int) -> None
head = session.head(url, headers=HEADERS)
raise_for_status(head)
assert head.status_code == 200
self._session, self._url, self._chunk_size = session, url, chunk_size
self._length = int(head.headers['Content-Length'])
self._file = NamedTemporaryFile()
self.truncate(self._length)
self._left = [] # type: List[int]
self._right = [] # type: List[int]
if 'bytes' not in head.headers.get('Accept-Ranges', 'none'):
raise HTTPRangeRequestUnsupported('range request is not supported')
self._check_zip()
@property
def mode(self):
# type: () -> str
"""Opening mode, which is always rb."""
return 'rb'
@property
def name(self):
# type: () -> str
"""Path to the underlying file."""
return self._file.name
def seekable(self):
# type: () -> bool
"""Return whether random access is supported, which is True."""
return True
def close(self):
# type: () -> None
"""Close the file."""
self._file.close()
@property
def closed(self):
# type: () -> bool
"""Whether the file is closed."""
return self._file.closed
def read(self, size=-1):
# type: (int) -> bytes
"""Read up to size bytes from the object and return them.
As a convenience, if size is unspecified or -1,
all bytes until EOF are returned. Fewer than
size bytes may be returned if EOF is reached.
"""
download_size = max(size, self._chunk_size)
start, length = self.tell(), self._length
stop = length if size < 0 else min(start+download_size, length)
start = max(0, stop-download_size)
self._download(start, stop-1)
return self._file.read(size)
def readable(self):
# type: () -> bool
"""Return whether the file is readable, which is True."""
return True
def seek(self, offset, whence=0):
# type: (int, int) -> int
"""Change stream position and return the new absolute position.
Seek to offset relative position indicated by whence:
* 0: Start of stream (the default). pos should be >= 0;
* 1: Current position - pos may be negative;
* 2: End of stream - pos usually negative.
"""
return self._file.seek(offset, whence)
def tell(self):
# type: () -> int
"""Return the current possition."""
return self._file.tell()
def truncate(self, size=None):
# type: (Optional[int]) -> int
"""Resize the stream to the given size in bytes.
If size is unspecified resize to the current position.
The current stream position isn't changed.
Return the new file size.
"""
return self._file.truncate(size)
def writable(self):
# type: () -> bool
"""Return False."""
return False
def __enter__(self):
# type: () -> LazyZipOverHTTP
self._file.__enter__()
return self
def __exit__(self, *exc):
# type: (*Any) -> Optional[bool]
return self._file.__exit__(*exc)
@contextmanager
def _stay(self):
# type: ()-> Iterator[None]
"""Return a context manager keeping the position.
At the end of the block, seek back to original position.
"""
pos = self.tell()
try:
yield
finally:
self.seek(pos)
def _check_zip(self):
# type: () -> None
"""Check and download until the file is a valid ZIP."""
end = self._length - 1
for start in reversed(range(0, end, self._chunk_size)):
self._download(start, end)
with self._stay():
try:
# For read-only ZIP files, ZipFile only needs
# methods read, seek, seekable and tell.
ZipFile(self) # type: ignore
except BadZipfile:
pass
else:
break
def _stream_response(self, start, end, base_headers=HEADERS):
# type: (int, int, Dict[str, str]) -> Response
"""Return HTTP response to a range request from start to end."""
headers = base_headers.copy()
headers['Range'] = 'bytes={}-{}'.format(start, end)
# TODO: Get range requests to be correctly cached
headers['Cache-Control'] = 'no-cache'
return self._session.get(self._url, headers=headers, stream=True)
def _merge(self, start, end, left, right):
# type: (int, int, int, int) -> Iterator[Tuple[int, int]]
"""Return an iterator of intervals to be fetched.
Args:
start (int): Start of needed interval
end (int): End of needed interval
left (int): Index of first overlapping downloaded data
right (int): Index after last overlapping downloaded data
"""
lslice, rslice = self._left[left:right], self._right[left:right]
i = start = min([start]+lslice[:1])
end = max([end]+rslice[-1:])
for j, k in zip(lslice, rslice):
if j > i:
yield i, j-1
i = k + 1
if i <= end:
yield i, end
self._left[left:right], self._right[left:right] = [start], [end]
def _download(self, start, end):
# type: (int, int) -> None
"""Download bytes from start to end inclusively."""
with self._stay():
left = bisect_left(self._right, start)
right = bisect_right(self._left, end)
for start, end in self._merge(start, end, left, right):
response = self._stream_response(start, end)
response.raise_for_status()
self.seek(start)
for chunk in response_chunks(response, self._chunk_size):
self._file.write(chunk)

View File

@ -1,9 +1,58 @@
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
from pip._internal.exceptions import NetworkConnectionError
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Iterator
from typing import Dict, Iterator
# The following comments and HTTP headers were originally added by
# Donald Stufft in git commit 22c562429a61bb77172039e480873fb239dd8c03.
#
# We use Accept-Encoding: identity here because requests defaults to
# accepting compressed responses. This breaks in a variety of ways
# depending on how the server is configured.
# - Some servers will notice that the file isn't a compressible file
# and will leave the file alone and with an empty Content-Encoding
# - Some servers will notice that the file is already compressed and
# will leave the file alone, adding a Content-Encoding: gzip header
# - Some servers won't notice anything at all and will take a file
# that's already been compressed and compress it again, and set
# the Content-Encoding: gzip header
# By setting this to request only the identity encoding we're hoping
# to eliminate the third case. Hopefully there does not exist a server
# which when given a file will notice it is already compressed and that
# you're not asking for a compressed file and will then decompress it
# before sending because if that's the case I don't think it'll ever be
# possible to make this work.
HEADERS = {'Accept-Encoding': 'identity'} # type: Dict[str, str]
def raise_for_status(resp):
# type: (Response) -> None
http_error_msg = u''
if isinstance(resp.reason, bytes):
# We attempt to decode utf-8 first because some servers
# choose to localize their reason strings. If the string
# isn't utf-8, we fall back to iso-8859-1 for all other
# encodings.
try:
reason = resp.reason.decode('utf-8')
except UnicodeDecodeError:
reason = resp.reason.decode('iso-8859-1')
else:
reason = resp.reason
if 400 <= resp.status_code < 500:
http_error_msg = u'%s Client Error: %s for url: %s' % (
resp.status_code, reason, resp.url)
elif 500 <= resp.status_code < 600:
http_error_msg = u'%s Server Error: %s for url: %s' % (
resp.status_code, reason, resp.url)
if http_error_msg:
raise NetworkConnectionError(http_error_msg, response=resp)
def response_chunks(response, chunk_size=CONTENT_CHUNK_SIZE):

View File

@ -1,17 +1,22 @@
"""xmlrpclib.Transport implementation
"""
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
import logging
from pip._vendor import requests
# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is
# why we ignore the type on this import
from pip._vendor.six.moves import xmlrpc_client # type: ignore
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._internal.exceptions import NetworkConnectionError
from pip._internal.network.utils import raise_for_status
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Dict
from pip._internal.network.session import PipSession
logger = logging.getLogger(__name__)
@ -21,22 +26,25 @@ class PipXmlrpcTransport(xmlrpc_client.Transport):
"""
def __init__(self, index_url, session, use_datetime=False):
# type: (str, PipSession, bool) -> None
xmlrpc_client.Transport.__init__(self, use_datetime)
index_parts = urllib_parse.urlparse(index_url)
self._scheme = index_parts.scheme
self._session = session
def request(self, host, handler, request_body, verbose=False):
# type: (str, str, Dict[str, str], bool) -> None
parts = (self._scheme, host, handler, None, None, None)
url = urllib_parse.urlunparse(parts)
try:
headers = {'Content-Type': 'text/xml'}
response = self._session.post(url, data=request_body,
headers=headers, stream=True)
response.raise_for_status()
raise_for_status(response)
self.verbose = verbose
return self.parse_response(response.raw)
except requests.HTTPError as exc:
except NetworkConnectionError as exc:
assert exc.response
logger.critical(
"HTTP error %s while getting %s",
exc.response.status_code, url,

View File

@ -1,7 +1,6 @@
"""Metadata generation logic for source distributions.
"""
import logging
import os
from pip._internal.utils.subprocess import runner_with_spinner_message
@ -12,8 +11,6 @@ if MYPY_CHECK_RUNNING:
from pip._internal.build_env import BuildEnvironment
from pip._vendor.pep517.wrappers import Pep517HookCaller
logger = logging.getLogger(__name__)
def generate_metadata(build_env, backend):
# type: (BuildEnvironment, Pep517HookCaller) -> str

View File

@ -27,7 +27,7 @@ def build_wheel_pep517(
if build_options:
# PEP 517 does not support --build-options
logger.error('Cannot build wheel for %s using PEP 517 when '
'--build-option is present' % (name,))
'--build-option is present', name)
return None
try:
logger.debug('Destination directory: %s', tempd)

View File

@ -1,10 +1,6 @@
"""Validation of dependencies of packages
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
# mypy: disallow-untyped-defs=False
import logging
from collections import namedtuple
@ -33,6 +29,7 @@ if MYPY_CHECK_RUNNING:
MissingDict = Dict[str, List[Missing]]
ConflictingDict = Dict[str, List[Conflicting]]
CheckResult = Tuple[MissingDict, ConflictingDict]
ConflictDetails = Tuple[PackageSet, CheckResult]
PackageDetails = namedtuple('PackageDetails', ['version', 'requires'])
@ -65,9 +62,6 @@ def check_package_set(package_set, should_ignore=None):
If should_ignore is passed, it should be a callable that takes a
package name and returns a boolean.
"""
if should_ignore is None:
def should_ignore(name):
return False
missing = {}
conflicting = {}
@ -77,7 +71,7 @@ def check_package_set(package_set, should_ignore=None):
missing_deps = set() # type: Set[Missing]
conflicting_deps = set() # type: Set[Conflicting]
if should_ignore(package_name):
if should_ignore and should_ignore(package_name):
continue
for req in package_set[package_name].requires:
@ -106,7 +100,7 @@ def check_package_set(package_set, should_ignore=None):
def check_install_conflicts(to_install):
# type: (List[InstallRequirement]) -> Tuple[PackageSet, CheckResult]
# type: (List[InstallRequirement]) -> ConflictDetails
"""For checking if the dependency graph would be consistent after \
installing given requirements
"""
@ -139,6 +133,7 @@ def _simulate_installation_of(to_install, package_set):
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.key)
package_set[name] = PackageDetails(dist.version, dist.requires())

View File

@ -1,7 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import collections
@ -46,8 +42,8 @@ logger = logging.getLogger(__name__)
def freeze(
requirement=None, # type: Optional[List[str]]
find_links=None, # type: Optional[List[str]]
local_only=None, # type: Optional[bool]
user_only=None, # type: Optional[bool]
local_only=False, # type: bool
user_only=False, # type: bool
paths=None, # type: Optional[List[str]]
isolated=False, # type: bool
wheel_cache=None, # type: Optional[WheelCache]
@ -60,10 +56,13 @@ def freeze(
for link in find_links:
yield '-f {}'.format(link)
installations = {} # type: Dict[str, FrozenRequirement]
for dist in get_installed_distributions(local_only=local_only,
skip=(),
user_only=user_only,
paths=paths):
for dist in get_installed_distributions(
local_only=local_only,
skip=(),
user_only=user_only,
paths=paths
):
try:
req = FrozenRequirement.from_dist(dist)
except RequirementParseError as exc:
@ -96,13 +95,13 @@ def freeze(
line.strip().startswith('#') or
line.startswith((
'-r', '--requirement',
'-Z', '--always-unzip',
'-f', '--find-links',
'-i', '--index-url',
'--pre',
'--trusted-host',
'--process-dependency-links',
'--extra-index-url'))):
'--extra-index-url',
'--use-feature'))):
line = line.rstrip()
if line not in emitted_options:
emitted_options.add(line)
@ -266,6 +265,7 @@ class FrozenRequirement(object):
return cls(dist.project_name, req, editable, comments=comments)
def __str__(self):
# type: () -> str
req = self.req
if self.editable:
req = '-e {}'.format(req)

View File

@ -6,7 +6,7 @@ import os
import sys
from distutils.util import change_root
from pip._internal.utils.deprecation import deprecated
from pip._internal.exceptions import InstallationError
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import ensure_dir
from pip._internal.utils.setuptools_build import make_setuptools_install_args
@ -106,24 +106,12 @@ def install(
egg_info_dir = prepend_root(directory)
break
else:
deprecated(
reason=(
"{} did not indicate that it installed an "
".egg-info directory. Only setup.py projects "
"generating .egg-info directories are supported."
).format(req_description),
replacement=(
"for maintainers: updating the setup.py of {0}. "
"For users: contact the maintainers of {0} to let "
"them know to update their setup.py.".format(
req_name
)
),
gone_in="20.2",
issue=6998,
)
# FIXME: put the record somewhere
return True
message = (
"{} did not indicate that it installed an "
".egg-info directory. Only setup.py projects "
"generating .egg-info directories are supported."
).format(req_description)
raise InstallationError(message)
new_lines = []
for line in record_lines:

View File

@ -1,64 +1,108 @@
"""Support for installing and building the "wheel" binary package format.
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import collections
import compileall
import contextlib
import csv
import importlib
import logging
import os.path
import re
import shutil
import stat
import sys
import warnings
from base64 import urlsafe_b64encode
from itertools import starmap
from itertools import chain, starmap
from zipfile import ZipFile
from pip._vendor import pkg_resources
from pip._vendor.distlib.scripts import ScriptMaker
from pip._vendor.distlib.util import get_export_entry
from pip._vendor.six import StringIO
from pip._vendor.six import (
PY2,
ensure_str,
ensure_text,
itervalues,
reraise,
text_type,
)
from pip._vendor.six.moves import filterfalse, map
from pip._internal.exceptions import InstallationError
from pip._internal.locations import get_major_minor_version
from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, DirectUrl
from pip._internal.models.scheme import SCHEME_KEYS
from pip._internal.utils.filesystem import adjacent_tmp_file, replace
from pip._internal.utils.misc import captured_stdout, ensure_dir, hash_file
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.utils.misc import (
captured_stdout,
ensure_dir,
hash_file,
partition,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.unpacking import current_umask, unpack_file
from pip._internal.utils.wheel import parse_wheel
from pip._internal.utils.unpacking import (
current_umask,
is_within_directory,
set_extracted_file_to_default_mode_plus_executable,
zip_item_is_executable,
)
from pip._internal.utils.wheel import (
parse_wheel,
pkg_resources_distribution_for_wheel,
)
if MYPY_CHECK_RUNNING:
# Use the custom cast function at runtime to make cast work,
# and import typing.cast when performing pre-commit and type
# checks
if not MYPY_CHECK_RUNNING:
from pip._internal.utils.typing import cast
else:
from email.message import Message
from typing import (
Dict, List, Optional, Sequence, Tuple, Any,
Iterable, Iterator, Callable, Set,
Any,
Callable,
Dict,
IO,
Iterable,
Iterator,
List,
NewType,
Optional,
Protocol,
Sequence,
Set,
Tuple,
Union,
cast,
)
from zipfile import ZipInfo
from pip._vendor.pkg_resources import Distribution
from pip._internal.models.scheme import Scheme
from pip._internal.utils.filesystem import NamedTemporaryFileResult
InstalledCSVRow = Tuple[str, ...]
RecordPath = NewType('RecordPath', text_type)
InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]]
class File(Protocol):
src_record_path = None # type: RecordPath
dest_path = None # type: text_type
changed = None # type: bool
def save(self):
# type: () -> None
pass
logger = logging.getLogger(__name__)
def normpath(src, p):
# type: (str, str) -> str
return os.path.relpath(src, p).replace(os.path.sep, '/')
def rehash(path, blocksize=1 << 20):
# type: (str, int) -> Tuple[str, str]
# type: (text_type, int) -> Tuple[str, str]
"""Return (encoded_digest, length) for path using hashlib.sha256()"""
h, length = hash_file(path, blocksize)
digest = 'sha256=' + urlsafe_b64encode(
@ -73,31 +117,31 @@ def csv_io_kwargs(mode):
"""Return keyword arguments to properly open a CSV file
in the given mode.
"""
if sys.version_info.major < 3:
if PY2:
return {'mode': '{}b'.format(mode)}
else:
return {'mode': mode, 'newline': ''}
return {'mode': mode, 'newline': '', 'encoding': 'utf-8'}
def fix_script(path):
# type: (str) -> Optional[bool]
# type: (text_type) -> bool
"""Replace #!python with #!/path/to/python
Return True if file was changed.
"""
# XXX RECORD hashes will need to be updated
if os.path.isfile(path):
with open(path, 'rb') as script:
firstline = script.readline()
if not firstline.startswith(b'#!python'):
return False
exename = sys.executable.encode(sys.getfilesystemencoding())
firstline = b'#!' + exename + os.linesep.encode("ascii")
rest = script.read()
with open(path, 'wb') as script:
script.write(firstline)
script.write(rest)
return True
return None
assert os.path.isfile(path)
with open(path, 'rb') as script:
firstline = script.readline()
if not firstline.startswith(b'#!python'):
return False
exename = sys.executable.encode(sys.getfilesystemencoding())
firstline = b'#!' + exename + os.linesep.encode("ascii")
rest = script.read()
with open(path, 'wb') as script:
script.write(firstline)
script.write(rest)
return True
def wheel_root_is_purelib(metadata):
@ -105,26 +149,16 @@ def wheel_root_is_purelib(metadata):
return metadata.get("Root-Is-Purelib", "").lower() == "true"
def get_entrypoints(filename):
# type: (str) -> Tuple[Dict[str, str], Dict[str, str]]
if not os.path.exists(filename):
return {}, {}
# This is done because you can pass a string to entry_points wrappers which
# means that they may or may not be valid INI files. The attempt here is to
# strip leading and trailing whitespace in order to make them valid INI
# files.
with open(filename) as fp:
data = StringIO()
for line in fp:
data.write(line.strip())
data.write("\n")
data.seek(0)
def get_entrypoints(distribution):
# type: (Distribution) -> Tuple[Dict[str, str], Dict[str, str]]
# get the entry points and then the script names
entry_points = pkg_resources.EntryPoint.parse_map(data)
console = entry_points.get('console_scripts', {})
gui = entry_points.get('gui_scripts', {})
try:
console = distribution.get_entry_map('console_scripts')
gui = distribution.get_entry_map('gui_scripts')
except KeyError:
# Our dict-based Distribution raises KeyError if entry_points.txt
# doesn't exist.
return {}, {}
def _split_ep(s):
# type: (pkg_resources.EntryPoint) -> Tuple[str, str]
@ -211,9 +245,12 @@ def message_about_scripts_not_on_PATH(scripts):
return "\n".join(msg_lines)
def sorted_outrows(outrows):
# type: (Iterable[InstalledCSVRow]) -> List[InstalledCSVRow]
"""Return the given rows of a RECORD file in sorted order.
def _normalized_outrows(outrows):
# type: (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
the value more predictable for tests.
Each row is a 3-tuple (path, hash, size) and corresponds to a record of
a RECORD file (see PEP 376 and PEP 427 for details). For the rows
@ -228,13 +265,39 @@ def sorted_outrows(outrows):
# coerce each element to a string to avoid a TypeError in this case.
# For additional background, see--
# https://github.com/pypa/pip/issues/5868
return sorted(outrows, key=lambda row: tuple(str(x) for x in row))
return sorted(
(ensure_str(record_path, encoding='utf-8'), hash_, str(size))
for record_path, hash_, size in outrows
)
def _record_to_fs_path(record_path):
# type: (RecordPath) -> text_type
return record_path
def _fs_to_record_path(path, relative_to=None):
# type: (text_type, Optional[text_type]) -> 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():
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)
def get_csv_rows_for_installed(
old_csv_rows, # type: Iterable[List[str]]
installed, # type: Dict[str, str]
changed, # type: Set[str]
old_csv_rows, # type: List[List[str]]
installed, # type: Dict[RecordPath, RecordPath]
changed, # type: Set[RecordPath]
generated, # type: List[str]
lib_dir, # type: str
):
@ -246,227 +309,32 @@ def get_csv_rows_for_installed(
installed_rows = [] # type: List[InstalledCSVRow]
for row in old_csv_rows:
if len(row) > 3:
logger.warning(
'RECORD line has more than three elements: {}'.format(row)
)
# Make a copy because we are mutating the row.
row = list(row)
old_path = row[0]
new_path = installed.pop(old_path, old_path)
row[0] = new_path
if new_path in changed:
digest, length = rehash(new_path)
row[1] = digest
row[2] = length
installed_rows.append(tuple(row))
logger.warning('RECORD line has more than three elements: %s', row)
old_record_path = _parse_record_path(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 ''
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((normpath(f, lib_dir), digest, str(length)))
for f in installed:
installed_rows.append((installed[f], '', ''))
installed_rows.append((path, digest, length))
for installed_record_path in itervalues(installed):
installed_rows.append((installed_record_path, '', ''))
return installed_rows
class MissingCallableSuffix(Exception):
pass
def _raise_for_invalid_entrypoint(specification):
# type: (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]
_raise_for_invalid_entrypoint(specification)
return super(PipScriptMaker, self).make(specification, options)
def install_unpacked_wheel(
name, # type: str
wheeldir, # type: str
wheel_zip, # type: ZipFile
scheme, # type: Scheme
req_description, # type: str
pycompile=True, # type: bool
warn_script_location=True, # type: bool
direct_url=None, # type: Optional[DirectUrl]
):
# type: (...) -> None
"""Install a wheel.
:param name: Name of the project to install
:param wheeldir: Base directory of the unpacked wheel
:param wheel_zip: open ZipFile for wheel being installed
:param scheme: Distutils scheme dictating the install directories
:param req_description: String used in place of the requirement, for
logging
:param pycompile: Whether to byte-compile installed Python files
:param warn_script_location: Whether to check that scripts are installed
into a directory on PATH
:raises UnsupportedWheel:
* when the directory holds an unpacked wheel with incompatible
Wheel-Version
* when the .dist-info dir does not match the wheel
def get_console_script_specs(console):
# type: (Dict[str, str]) -> List[str]
"""
# TODO: Investigate and break this up.
# TODO: Look into moving this into a dedicated class for representing an
# installation.
source = wheeldir.rstrip(os.path.sep) + os.path.sep
info_dir, metadata = parse_wheel(wheel_zip, name)
if wheel_root_is_purelib(metadata):
lib_dir = scheme.purelib
else:
lib_dir = scheme.platlib
subdirs = os.listdir(source)
data_dirs = [s for s in subdirs if s.endswith('.data')]
# Record details of the files moved
# 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[str, str]
changed = set()
generated = [] # type: List[str]
# Compile all of the pyc files that we're going to be installing
if pycompile:
with captured_stdout() as stdout:
with warnings.catch_warnings():
warnings.filterwarnings('ignore')
compileall.compile_dir(source, force=True, quiet=True)
logger.debug(stdout.getvalue())
def record_installed(srcfile, destfile, modified=False):
# type: (str, str, bool) -> None
"""Map archive RECORD paths to installation RECORD paths."""
oldpath = normpath(srcfile, wheeldir)
newpath = normpath(destfile, lib_dir)
installed[oldpath] = newpath
if modified:
changed.add(destfile)
def clobber(
source, # type: str
dest, # type: str
is_base, # type: bool
fixer=None, # type: Optional[Callable[[str], Any]]
filter=None # type: Optional[Callable[[str], bool]]
):
# type: (...) -> None
ensure_dir(dest) # common for the 'include' path
for dir, subdirs, files in os.walk(source):
basedir = dir[len(source):].lstrip(os.path.sep)
destdir = os.path.join(dest, basedir)
if is_base and basedir == '':
subdirs[:] = [s for s in subdirs if not s.endswith('.data')]
for f in files:
# Skip unwanted files
if filter and filter(f):
continue
srcfile = os.path.join(dir, f)
destfile = os.path.join(dest, basedir, f)
# directory creation is lazy and after the file filtering above
# to ensure we don't install empty dirs; empty dirs can't be
# uninstalled.
ensure_dir(destdir)
# copyfile (called below) truncates the destination if it
# exists and then writes the new contents. This is fine in most
# cases, but can cause a segfault if pip has loaded a shared
# object (e.g. from pyopenssl through its vendored urllib3)
# Since the shared object is mmap'd an attempt to call a
# symbol in it will then cause a segfault. Unlinking the file
# allows writing of new contents while allowing the process to
# continue to use the old copy.
if os.path.exists(destfile):
os.unlink(destfile)
# We use copyfile (not move, copy, or copy2) to be extra sure
# that we are not moving directories over (copyfile fails for
# directories) as well as to ensure that we are not copying
# over any metadata because we want more control over what
# metadata we actually copy over.
shutil.copyfile(srcfile, destfile)
# Copy over the metadata for the file, currently this only
# includes the atime and mtime.
st = os.stat(srcfile)
if hasattr(os, "utime"):
os.utime(destfile, (st.st_atime, st.st_mtime))
# If our file is executable, then make our destination file
# executable.
if os.access(srcfile, os.X_OK):
st = os.stat(srcfile)
permissions = (
st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
)
os.chmod(destfile, permissions)
changed = False
if fixer:
changed = fixer(destfile)
record_installed(srcfile, destfile, changed)
clobber(source, lib_dir, True)
dest_info_dir = os.path.join(lib_dir, info_dir)
# Get the defined entry points
ep_file = os.path.join(dest_info_dir, 'entry_points.txt')
console, gui = get_entrypoints(ep_file)
def is_entrypoint_wrapper(name):
# type: (str) -> bool
# EP, EP.exe and EP-script.py are scripts generated for
# entry point EP by setuptools
if name.lower().endswith('.exe'):
matchname = name[:-4]
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)
for datadir in data_dirs:
fixer = None
filter = None
for subdir in os.listdir(os.path.join(wheeldir, datadir)):
fixer = None
if subdir == 'scripts':
fixer = fix_script
filter = is_entrypoint_wrapper
source = os.path.join(wheeldir, datadir, subdir)
dest = getattr(scheme, subdir)
clobber(source, dest, False, fixer=fixer, filter=filter)
maker = PipScriptMaker(None, scheme.scripts)
# Ensure old scripts are overwritten.
# See https://github.com/pypa/pip/issues/1800
maker.clobber = True
# 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 = {''}
# This is required because otherwise distlib creates scripts that are not
# executable.
# See https://bitbucket.org/pypa/distlib/issue/32/
maker.set_mode = True
Given the mapping from entrypoint name to callable, return the relevant
console script specs.
"""
# Don't mutate caller's version
console = console.copy()
scripts_to_generate = []
@ -539,28 +407,364 @@ def install_unpacked_wheel(
for k in easy_install_ep:
del console[k]
# Generate the console and GUI entry points specified in the wheel
# Generate the console entry points specified in the wheel
scripts_to_generate.extend(starmap('{} = {}'.format, console.items()))
return scripts_to_generate
class ZipBackedFile(object):
def __init__(self, src_record_path, dest_path, zip_file):
# type: (RecordPath, text_type, 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
if not PY2:
return self._zip_file.getinfo(self.src_record_path)
# Python 2 does not expose a way to detect a ZIP's encoding, but the
# wheel specification (PEP 427) explicitly mandates that paths should
# use UTF-8, so we assume it is true.
return self._zip_file.getinfo(self.src_record_path.encode("utf-8"))
def save(self):
# type: () -> None
# directory creation is lazy and after file filtering
# to ensure we don't install empty dirs; empty dirs can't be
# uninstalled.
parent_dir = os.path.dirname(self.dest_path)
ensure_dir(parent_dir)
# When we open the output file below, any existing file is truncated
# before we start writing the new contents. This is fine in most
# cases, but can cause a segfault if pip has loaded a shared
# object (e.g. from pyopenssl through its vendored urllib3)
# Since the shared object is mmap'd an attempt to call a
# symbol in it will then cause a segfault. Unlinking the file
# allows writing of new contents while allowing the process to
# continue to use the old copy.
if os.path.exists(self.dest_path):
os.unlink(self.dest_path)
zipinfo = self._getinfo()
with self._zip_file.open(zipinfo) as f:
with open(self.dest_path, "wb") as dest:
shutil.copyfileobj(f, dest)
if zip_item_is_executable(zipinfo):
set_extracted_file_to_default_mode_plus_executable(self.dest_path)
class ScriptFile(object):
def __init__(self, file):
# type: (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
self._file.save()
self.changed = fix_script(self.dest_path)
class MissingCallableSuffix(InstallationError):
def __init__(self, entry_point):
# type: (str) -> None
super(MissingCallableSuffix, self).__init__(
"Invalid script entry point: {} - A callable "
"suffix is required. Cf https://packaging.python.org/"
"specifications/entry-points/#use-for-scripts for more "
"information.".format(entry_point)
)
def _raise_for_invalid_entrypoint(specification):
# type: (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]
_raise_for_invalid_entrypoint(specification)
return super(PipScriptMaker, self).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
"""Install a wheel.
:param name: Name of the project to install
:param wheel_zip: open ZipFile for wheel being installed
:param scheme: Distutils scheme dictating the install directories
:param req_description: String used in place of the requirement, for
logging
:param pycompile: Whether to byte-compile installed Python files
:param warn_script_location: Whether to check that scripts are installed
into a directory on PATH
:raises UnsupportedWheel:
* when the directory holds an unpacked wheel with incompatible
Wheel-Version
* when the .dist-info dir does not match the wheel
"""
info_dir, metadata = parse_wheel(wheel_zip, name)
if wheel_root_is_purelib(metadata):
lib_dir = scheme.purelib
else:
lib_dir = scheme.platlib
# Record details of the files moved
# 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]
def record_installed(srcfile, destfile, modified=False):
# type: (RecordPath, text_type, bool) -> 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
return path.endswith("/")
def assert_no_path_traversal(dest_dir_path, target_path):
# type: (text_type, text_type) -> None
if not is_within_directory(dest_dir_path, target_path):
message = (
"The wheel {!r} has a file {!r} trying to install"
" outside the target directory {!r}"
)
raise InstallationError(
message.format(wheel_path, target_path, dest_dir_path)
)
def root_scheme_file_maker(zip_file, dest):
# type: (ZipFile, text_type) -> Callable[[RecordPath], File]
def make_root_scheme_file(record_path):
# type: (RecordPath) -> File
normed_path = os.path.normpath(record_path)
dest_path = os.path.join(dest, normed_path)
assert_no_path_traversal(dest, dest_path)
return ZipBackedFile(record_path, dest_path, zip_file)
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 make_data_scheme_file(record_path):
# type: (RecordPath) -> File
normed_path = os.path.normpath(record_path)
try:
_, scheme_key, dest_subpath = normed_path.split(os.path.sep, 2)
except ValueError:
message = (
"Unexpected file in {}: {!r}. .data directory contents"
" should be named like: '<scheme key>/<path>'."
).format(wheel_path, record_path)
raise InstallationError(message)
try:
scheme_path = scheme_paths[scheme_key]
except KeyError:
valid_scheme_keys = ", ".join(sorted(scheme_paths))
message = (
"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
)
raise InstallationError(message)
dest_path = os.path.join(scheme_path, dest_subpath)
assert_no_path_traversal(scheme_path, dest_path)
return ZipBackedFile(record_path, dest_path, zip_file)
return make_data_scheme_file
def is_data_scheme_path(path):
# type: (RecordPath) -> bool
return path.split("/", 1)[0].endswith(".data")
paths = all_paths()
file_paths = filterfalse(is_dir_path, 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)
def is_script_scheme_path(path):
# type: (RecordPath) -> bool
parts = path.split("/", 2)
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
)
make_data_scheme_file = data_scheme_file_maker(wheel_zip, scheme)
other_scheme_files = map(make_data_scheme_file, other_scheme_paths)
files = chain(files, other_scheme_files)
# Get the defined entry points
distribution = pkg_resources_distribution_for_wheel(
wheel_zip, name, wheel_path
)
console, gui = get_entrypoints(distribution)
def is_entrypoint_wrapper(file):
# type: (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'):
matchname = name[:-4]
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)
script_scheme_files = 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)
for file in files:
file.save()
record_installed(file.src_record_path, file.dest_path, file.changed)
def pyc_source_file_paths():
# type: () -> Iterator[text_type]
# 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
# issues related to permissions on existing files.
for installed_path in sorted(set(installed.values())):
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'):
continue
yield full_installed_path
def pyc_output_path(path):
# type: (text_type) -> text_type
"""Return the path the pyc file would have been written to.
"""
if PY2:
if sys.flags.optimize:
return path + 'o'
else:
return path + 'c'
else:
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')
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
)
if success:
pyc_path = pyc_output_path(path)
assert os.path.exists(pyc_path)
pyc_record_path = cast(
"RecordPath", pyc_path.replace(os.path.sep, "/")
)
record_installed(pyc_record_path, pyc_path)
logger.debug(stdout.getvalue())
maker = PipScriptMaker(None, scheme.scripts)
# Ensure old scripts are overwritten.
# See https://github.com/pypa/pip/issues/1800
maker.clobber = True
# 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 = {''}
# This is required because otherwise distlib creates scripts that are not
# executable.
# See https://bitbucket.org/pypa/distlib/issue/32/
maker.set_mode = True
# 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()))
generated_console_scripts = [] # type: List[str]
generated_console_scripts = maker.make_multiple(scripts_to_generate)
generated.extend(generated_console_scripts)
try:
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})
)
except MissingCallableSuffix as e:
entry = e.args[0]
raise InstallationError(
"Invalid script entry point: {} for req: {} - A callable "
"suffix is required. Cf https://packaging.python.org/"
"specifications/entry-points/#use-for-scripts for more "
"information.".format(entry, req_description)
)
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)
@ -577,6 +781,8 @@ def install_unpacked_wheel(
os.chmod(f.name, generated_file_mode)
replace(f.name, path)
dest_info_dir = os.path.join(lib_dir, info_dir)
# Record pip as the installer
installer_path = os.path.join(dest_info_dir, 'INSTALLER')
with _generate_file(installer_path) as installer_file:
@ -590,18 +796,44 @@ def install_unpacked_wheel(
direct_url_file.write(direct_url.to_json().encode("utf-8"))
generated.append(direct_url_path)
# Record the REQUESTED file
if requested:
requested_path = os.path.join(dest_info_dir, 'REQUESTED')
with open(requested_path, "w"):
pass
generated.append(requested_path)
record_text = distribution.get_metadata('RECORD')
record_rows = list(csv.reader(record_text.splitlines()))
rows = get_csv_rows_for_installed(
record_rows,
installed=installed,
changed=changed,
generated=generated,
lib_dir=lib_dir)
# Record details of all files installed
record_path = os.path.join(dest_info_dir, 'RECORD')
with open(record_path, **csv_io_kwargs('r')) as record_file:
rows = get_csv_rows_for_installed(
csv.reader(record_file),
installed=installed,
changed=changed,
generated=generated,
lib_dir=lib_dir)
with _generate_file(record_path, **csv_io_kwargs('w')) as record_file:
writer = csv.writer(record_file)
writer.writerows(sorted_outrows(rows)) # sort to simplify testing
# 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))
writer.writerows(_normalized_outrows(rows))
@contextlib.contextmanager
def req_error_context(req_description):
# type: (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]
)
def install_wheel(
@ -611,21 +843,19 @@ def install_wheel(
req_description, # type: str
pycompile=True, # type: bool
warn_script_location=True, # type: bool
_temp_dir_for_testing=None, # type: Optional[str]
direct_url=None, # type: Optional[DirectUrl]
requested=False, # type: bool
):
# type: (...) -> None
with TempDirectory(
path=_temp_dir_for_testing, kind="unpacked-wheel"
) as unpacked_dir, ZipFile(wheel_path, allowZip64=True) as z:
unpack_file(wheel_path, unpacked_dir.path)
install_unpacked_wheel(
name=name,
wheeldir=unpacked_dir.path,
wheel_zip=z,
scheme=scheme,
req_description=req_description,
pycompile=pycompile,
warn_script_location=warn_script_location,
direct_url=direct_url,
)
with ZipFile(wheel_path, allowZip64=True) as z:
with req_error_context(req_description):
_install_wheel(
name=name,
wheel_zip=z,
wheel_path=wheel_path,
scheme=scheme,
pycompile=pycompile,
warn_script_location=warn_script_location,
direct_url=direct_url,
requested=requested,
)

View File

@ -9,7 +9,6 @@ import mimetypes
import os
import shutil
from pip._vendor import requests
from pip._vendor.six import PY2
from pip._internal.distributions import (
@ -21,6 +20,7 @@ from pip._internal.exceptions import (
HashMismatch,
HashUnpinned,
InstallationError,
NetworkConnectionError,
PreviousBuildDirError,
VcsHashUnsupported,
)
@ -260,8 +260,9 @@ def unpack_url(
)
# unpack the archive to the build dir location. even when only downloading
# archives, they have to be unpacked to parse dependencies
unpack_file(file.path, location, file.content_type)
# archives, they have to be unpacked to parse dependencies, except wheels
if not link.is_wheel:
unpack_file(file.path, location, file.content_type)
return file
@ -376,104 +377,98 @@ class RequirementPreparer(object):
"Could not find or access download directory '{}'"
.format(self.download_dir))
def prepare_linked_requirement(
self,
req, # type: InstallRequirement
):
# type: (...) -> AbstractDistribution
"""Prepare a requirement that would be obtained from req.link
"""
assert req.link
link = req.link
# TODO: Breakup into smaller functions
if link.scheme == 'file':
path = link.file_path
def _log_preparing_link(self, req):
# type: (InstallRequirement) -> None
"""Log the way the link prepared."""
if req.link.is_file:
path = req.link.file_path
logger.info('Processing %s', display_path(path))
else:
logger.info('Collecting %s', req.req or req)
download_dir = self.download_dir
if link.is_wheel and self.wheel_download_dir:
# when doing 'pip wheel` we download wheels to a
# dedicated dir.
download_dir = self.wheel_download_dir
def _ensure_link_req_src_dir(self, req, download_dir, parallel_builds):
# type: (InstallRequirement, Optional[str], bool) -> None
"""Ensure source_dir of a linked InstallRequirement."""
# Since source_dir is only set for editable requirements.
if req.link.is_wheel:
# We don't need to unpack wheels, so no need for a source
# directory.
return
assert req.source_dir is None
# We always delete unpacked sdists after pip runs.
req.ensure_has_source_dir(
self.build_dir,
autodelete=True,
parallel_builds=parallel_builds,
)
if link.is_wheel:
if download_dir:
# When downloading, we only unpack wheels to get
# metadata.
autodelete_unpacked = True
else:
# When installing a wheel, we use the unpacked
# wheel.
autodelete_unpacked = False
# If a checkout exists, it's unwise to keep going. version
# inconsistencies are logged later, but do not fail the
# installation.
# FIXME: this won't upgrade when there's an existing
# package unpacked in `req.source_dir`
if os.path.exists(os.path.join(req.source_dir, 'setup.py')):
raise PreviousBuildDirError(
"pip can't proceed with requirements '{}' due to a"
"pre-existing build directory ({}). This is likely "
"due to a previous installation that failed . pip is "
"being responsible and not assuming it can delete this. "
"Please delete it and try again.".format(req, req.source_dir)
)
def _get_linked_req_hashes(self, req):
# type: (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.
# (For example, we can raise VcsHashUnsupported for a VCS URL
# rather than HashMissing.)
if not self.require_hashes:
return req.hashes(trust_internet=True)
# We could check these first 2 conditions inside unpack_url
# and save repetition of conditions, but then we would
# report less-useful error messages for unhashable
# requirements, complaining that there's no hash provided.
if req.link.is_vcs:
raise VcsHashUnsupported()
if req.link.is_existing_dir():
raise DirectoryUrlHashUnsupported()
# Unpinned packages are asking for trouble when a new version
# is uploaded. This isn't a security check, but it saves users
# a surprising hash mismatch in the future.
# file:/// URLs aren't pinnable, so don't complain about them
# not being pinned.
if req.original_link is None and not req.is_pinned:
raise HashUnpinned()
# If known-good hashes are missing for this requirement,
# shim it with a facade object that will provoke hash
# computation and then raise a HashMissing exception
# showing the user what the hash should be.
return req.hashes(trust_internet=False) or MissingHashes()
def prepare_linked_requirement(self, req, parallel_builds=False):
# type: (InstallRequirement, bool) -> AbstractDistribution
"""Prepare a requirement to be obtained from req.link."""
assert req.link
link = req.link
self._log_preparing_link(req)
if link.is_wheel and self.wheel_download_dir:
# Download wheels to a dedicated dir when doing `pip wheel`.
download_dir = self.wheel_download_dir
else:
# We always delete unpacked sdists after pip runs.
autodelete_unpacked = True
download_dir = self.download_dir
with indent_log():
# Since source_dir is only set for editable requirements.
assert req.source_dir is None
req.ensure_has_source_dir(self.build_dir, autodelete_unpacked)
# If a checkout exists, it's unwise to keep going. version
# inconsistencies are logged later, but do not fail the
# installation.
# FIXME: this won't upgrade when there's an existing
# package unpacked in `req.source_dir`
if os.path.exists(os.path.join(req.source_dir, 'setup.py')):
raise PreviousBuildDirError(
"pip can't proceed with requirements '{}' due to a"
" pre-existing build directory ({}). This is "
"likely due to a previous installation that failed"
". pip is being responsible and not assuming it "
"can delete this. Please delete it and try again."
.format(req, req.source_dir)
)
# Now that we have the real link, we can tell what kind of
# requirements we have and raise some more informative errors
# than otherwise. (For example, we can raise VcsHashUnsupported
# for a VCS URL rather than HashMissing.)
if self.require_hashes:
# We could check these first 2 conditions inside
# unpack_url and save repetition of conditions, but then
# we would report less-useful error messages for
# unhashable requirements, complaining that there's no
# hash provided.
if link.is_vcs:
raise VcsHashUnsupported()
elif link.is_existing_dir():
raise DirectoryUrlHashUnsupported()
if not req.original_link and not req.is_pinned:
# Unpinned packages are asking for trouble when a new
# version is uploaded. This isn't a security check, but
# it saves users a surprising hash mismatch in the
# future.
#
# file:/// URLs aren't pinnable, so don't complain
# about them not being pinned.
raise HashUnpinned()
hashes = req.hashes(trust_internet=not self.require_hashes)
if self.require_hashes and not hashes:
# Known-good hashes are missing for this requirement, so
# shim it with a facade object that will provoke hash
# computation and then raise a HashMissing exception
# showing the user what the hash should be.
hashes = MissingHashes()
self._ensure_link_req_src_dir(req, download_dir, parallel_builds)
try:
local_file = unpack_url(
link, req.source_dir, self.downloader, download_dir,
hashes=hashes,
)
except requests.HTTPError as exc:
logger.critical(
'Could not install requirement %s because of error %s',
req,
exc,
hashes=self._get_linked_req_hashes(req)
)
except NetworkConnectionError as exc:
raise InstallationError(
'Could not install requirement {} because of HTTP '
'error {} for URL {}'.format(req, exc, link)
@ -497,9 +492,8 @@ class RequirementPreparer(object):
)
if not os.path.exists(download_location):
shutil.copy(local_file.path, download_location)
logger.info(
'Saved %s', display_path(download_location)
)
download_path = display_path(download_location)
logger.info('Saved %s', download_path)
if self._download_should_save:
# Make a .zip of the source_dir we already created.

View File

@ -1,8 +1,6 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import collections
import logging
from pip._internal.utils.logging import indent_log
@ -13,7 +11,7 @@ from .req_install import InstallRequirement
from .req_set import RequirementSet
if MYPY_CHECK_RUNNING:
from typing import Any, List, Sequence
from typing import Iterator, List, Optional, Sequence, Tuple
__all__ = [
"RequirementSet", "InstallRequirement",
@ -33,12 +31,25 @@ class InstallationResult(object):
return "InstallationResult(name={!r})".format(self.name)
def _validate_requirements(
requirements, # type: List[InstallRequirement]
):
# type: (...) -> Iterator[Tuple[str, InstallRequirement]]
for req in requirements:
assert req.name, "invalid to-be-installed requirement: {}".format(req)
yield req.name, req
def install_given_reqs(
to_install, # type: List[InstallRequirement]
requirements, # type: List[InstallRequirement]
install_options, # type: List[str]
global_options=(), # type: Sequence[str]
*args, # type: Any
**kwargs # type: Any
global_options, # type: Sequence[str]
root, # type: Optional[str]
home, # type: Optional[str]
prefix, # type: Optional[str]
warn_script_location, # type: bool
use_user_site, # type: bool
pycompile, # type: bool
):
# type: (...) -> List[InstallationResult]
"""
@ -46,47 +57,47 @@ def install_given_reqs(
(to be called after having downloaded and unpacked the packages)
"""
to_install = collections.OrderedDict(_validate_requirements(requirements))
if to_install:
logger.info(
'Installing collected packages: %s',
', '.join([req.name for req in to_install]),
', '.join(to_install.keys()),
)
installed = []
with indent_log():
for requirement in to_install:
for req_name, requirement in to_install.items():
if requirement.should_reinstall:
logger.info('Attempting uninstall: %s', requirement.name)
logger.info('Attempting uninstall: %s', req_name)
with indent_log():
uninstalled_pathset = requirement.uninstall(
auto_confirm=True
)
else:
uninstalled_pathset = None
try:
requirement.install(
install_options,
global_options,
*args,
**kwargs
root=root,
home=home,
prefix=prefix,
warn_script_location=warn_script_location,
use_user_site=use_user_site,
pycompile=pycompile,
)
except Exception:
should_rollback = (
requirement.should_reinstall and
not requirement.install_succeeded
)
# if install did not succeed, rollback previous uninstall
if should_rollback:
if uninstalled_pathset and not requirement.install_succeeded:
uninstalled_pathset.rollback()
raise
else:
should_commit = (
requirement.should_reinstall and
requirement.install_succeeded
)
if should_commit:
if uninstalled_pathset and requirement.install_succeeded:
uninstalled_pathset.commit()
installed.append(InstallationResult(requirement.name))
installed.append(InstallationResult(req_name))
return installed

View File

@ -8,9 +8,6 @@ These are meant to be used elsewhere within pip to create instances of
InstallRequirement.
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import logging
import os
import re
@ -26,6 +23,7 @@ from pip._internal.models.link import Link
from pip._internal.models.wheel import Wheel
from pip._internal.pyproject import make_pyproject_path
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.filetypes import ARCHIVE_EXTENSIONS
from pip._internal.utils.misc import is_installable_dir, splitext
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@ -78,7 +76,7 @@ def convert_extras(extras):
def parse_editable(editable_req):
# type: (str) -> Tuple[Optional[str], str, Optional[Set[str]]]
# type: (str) -> Tuple[Optional[str], str, Set[str]]
"""Parses an editable requirement into:
- a requirement name
- an URL
@ -120,7 +118,7 @@ def parse_editable(editable_req):
Requirement("placeholder" + extras.lower()).extras,
)
else:
return package_name, url_no_extras, None
return package_name, url_no_extras, set()
for version_control in vcs:
if url.lower().startswith('{}:'.format(version_control)):
@ -149,7 +147,7 @@ def parse_editable(editable_req):
"Could not detect requirement name for '{}', please specify one "
"with #egg=your_package_name".format(editable_req)
)
return package_name, url, None
return package_name, url, set()
def deduce_helpful_msg(req):
@ -175,8 +173,9 @@ def deduce_helpful_msg(req):
" the packages specified within it."
).format(req)
except RequirementParseError:
logger.debug("Cannot parse '{}' as requirements \
file".format(req), exc_info=True)
logger.debug(
"Cannot parse '%s' as requirements file", req, exc_info=True
)
else:
msg += " File '{}' does not exist.".format(req)
return msg
@ -222,7 +221,8 @@ def install_req_from_editable(
use_pep517=None, # type: Optional[bool]
isolated=False, # type: bool
options=None, # type: Optional[Dict[str, Any]]
constraint=False # type: bool
constraint=False, # type: bool
user_supplied=False, # type: bool
):
# type: (...) -> InstallRequirement
@ -231,6 +231,7 @@ def install_req_from_editable(
return InstallRequirement(
parts.requirement,
comes_from=comes_from,
user_supplied=user_supplied,
editable=True,
link=parts.link,
constraint=constraint,
@ -264,7 +265,7 @@ def _looks_like_path(name):
def _get_url_from_path(path, name):
# type: (str, str) -> str
# type: (str, str) -> Optional[str]
"""
First, it checks whether a provided path is an installable directory
(e.g. it has a setup.py). If it is, returns the path.
@ -371,6 +372,17 @@ def parse_req_from_line(name, line_source):
if add_msg:
msg += '\nHint: {}'.format(add_msg)
raise InstallationError(msg)
else:
# Deprecate extras after specifiers: "name>=1.0[extras]"
# This currently works by accident because _strip_extras() parses
# any extras in the end of the string and those are saved in
# RequirementParts
for spec in req.specifier:
spec_str = str(spec)
if spec_str.endswith(']'):
msg = "Extras after version '{}'.".format(spec_str)
replace = "moving the extras before version specifiers"
deprecated(msg, replacement=replace, gone_in="21.0")
else:
req = None
@ -385,6 +397,7 @@ def install_req_from_line(
options=None, # type: Optional[Dict[str, Any]]
constraint=False, # type: bool
line_source=None, # type: Optional[str]
user_supplied=False, # type: bool
):
# type: (...) -> InstallRequirement
"""Creates an InstallRequirement from a name, which might be a
@ -403,6 +416,7 @@ def install_req_from_line(
hash_options=options.get("hashes", {}) if options else {},
constraint=constraint,
extras=parts.extras,
user_supplied=user_supplied,
)
@ -410,7 +424,8 @@ def install_req_from_req_string(
req_string, # type: str
comes_from=None, # type: Optional[InstallRequirement]
isolated=False, # type: bool
use_pep517=None # type: Optional[bool]
use_pep517=None, # type: Optional[bool]
user_supplied=False, # type: bool
):
# type: (...) -> InstallRequirement
try:
@ -432,14 +447,19 @@ def install_req_from_req_string(
)
return InstallRequirement(
req, comes_from, isolated=isolated, use_pep517=use_pep517
req,
comes_from,
isolated=isolated,
use_pep517=use_pep517,
user_supplied=user_supplied,
)
def install_req_from_parsed_requirement(
parsed_req, # type: ParsedRequirement
isolated=False, # type: bool
use_pep517=None # type: Optional[bool]
use_pep517=None, # type: Optional[bool]
user_supplied=False, # type: bool
):
# type: (...) -> InstallRequirement
if parsed_req.is_editable:
@ -449,6 +469,7 @@ def install_req_from_parsed_requirement(
use_pep517=use_pep517,
constraint=parsed_req.constraint,
isolated=isolated,
user_supplied=user_supplied,
)
else:
@ -460,5 +481,6 @@ def install_req_from_parsed_requirement(
options=parsed_req.options,
constraint=parsed_req.constraint,
line_source=parsed_req.line_source,
user_supplied=user_supplied,
)
return req

View File

@ -2,9 +2,6 @@
Requirements file parsing
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import optparse
@ -21,6 +18,7 @@ from pip._internal.exceptions import (
RequirementsFileParseError,
)
from pip._internal.models.search_scope import SearchScope
from pip._internal.network.utils import raise_for_status
from pip._internal.utils.encoding import auto_decode
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.urls import get_url_scheme
@ -60,10 +58,11 @@ SUPPORTED_OPTIONS = [
cmdoptions.find_links,
cmdoptions.no_binary,
cmdoptions.only_binary,
cmdoptions.prefer_binary,
cmdoptions.require_hashes,
cmdoptions.pre,
cmdoptions.trusted_host,
cmdoptions.always_unzip, # Deprecated
cmdoptions.use_new_feature,
] # type: List[Callable[..., optparse.Option]]
# options to be passed to requirements
@ -101,7 +100,7 @@ class ParsedLine(object):
self,
filename, # type: str
lineno, # type: int
comes_from, # type: str
comes_from, # type: Optional[str]
args, # type: str
opts, # type: Values
constraint, # type: bool
@ -135,7 +134,7 @@ def parse_requirements(
constraint=False, # type: bool
):
# type: (...) -> Iterator[ParsedRequirement]
"""Parse a requirements file and yield InstallRequirement instances.
"""Parse a requirements file and yield ParsedRequirement instances.
:param filename: Path or url of requirements file.
:param session: PipSession instance.
@ -226,12 +225,18 @@ def handle_option_line(
):
# type: (...) -> None
# percolate hash-checking option upward
if opts.require_hashes:
options.require_hashes = opts.require_hashes
if options:
# percolate options upward
if opts.require_hashes:
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
)
# set finder options
elif finder:
if finder:
find_links = finder.find_links
index_urls = finder.index_urls
if opts.index_url:
@ -260,6 +265,9 @@ def handle_option_line(
if opts.pre:
finder.set_allow_all_prereleases()
if opts.prefer_binary:
finder.set_prefer_binary()
if session:
for host in opts.trusted_hosts or []:
source = 'line {} of {}'.format(lineno, filename)
@ -316,7 +324,7 @@ class RequirementsFileParser(object):
self,
session, # type: PipSession
line_parser, # type: LineParser
comes_from, # type: str
comes_from, # type: Optional[str]
):
# type: (...) -> None
self._session = session
@ -478,6 +486,7 @@ def join_lines(lines_enum):
line = ' ' + line
if new_line:
new_line.append(line)
assert primary_line_number is not None
yield primary_line_number, ''.join(new_line)
new_line = []
else:
@ -489,6 +498,7 @@ def join_lines(lines_enum):
# last line contains \
if new_line:
assert primary_line_number is not None
yield primary_line_number, ''.join(new_line)
# TODO: handle space after '\'.
@ -549,7 +559,7 @@ def get_file_content(url, session, comes_from=None):
if scheme in ['http', 'https']:
# FIXME: catch some errors
resp = session.get(url)
resp.raise_for_status()
raise_for_status(resp)
return resp.url, resp.text
elif scheme == 'file':

View File

@ -7,6 +7,7 @@ import logging
import os
import shutil
import sys
import uuid
import zipfile
from pip._vendor import pkg_resources, six
@ -40,6 +41,7 @@ from pip._internal.utils.misc import (
display_path,
dist_in_site_packages,
dist_in_usersite,
get_distribution,
get_installed_version,
hide_url,
redact_auth_from_url,
@ -110,7 +112,8 @@ class InstallRequirement(object):
global_options=None, # type: Optional[List[str]]
hash_options=None, # type: Optional[Dict[str, List[str]]]
constraint=False, # type: bool
extras=() # type: Iterable[str]
extras=(), # type: Iterable[str]
user_supplied=False, # type: bool
):
# type: (...) -> None
assert req is None or isinstance(req, Requirement), req
@ -171,7 +174,10 @@ class InstallRequirement(object):
self.hash_options = hash_options if hash_options else {}
# Set to True after successful preparation of this requirement
self.prepared = False
self.is_direct = False
# User supplied requirement are explicitly requested for installation
# by the user via CLI arguments or requirements files, as opposed to,
# e.g. dependencies, extras or constraints.
self.user_supplied = user_supplied
# Set by the legacy resolver when the requirement has been downloaded
# TODO: This introduces a strong coupling between the resolver and the
@ -339,8 +345,8 @@ class InstallRequirement(object):
s += '->' + comes_from
return s
def ensure_build_location(self, build_dir, autodelete):
# type: (str, bool) -> str
def ensure_build_location(self, build_dir, autodelete, parallel_builds):
# type: (str, bool, bool) -> str
assert build_dir is not None
if self._temp_build_dir is not None:
assert self._temp_build_dir.path
@ -354,16 +360,19 @@ class InstallRequirement(object):
)
return self._temp_build_dir.path
if self.editable:
name = self.name.lower()
else:
name = self.name
# When parallel builds are enabled, add a UUID to the build directory
# name so multiple builds do not interfere with each other.
dir_name = canonicalize_name(self.name)
if parallel_builds:
dir_name = "{}_{}".format(dir_name, uuid.uuid4().hex)
# 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)
os.makedirs(build_dir)
actual_build_dir = os.path.join(build_dir, name)
actual_build_dir = os.path.join(build_dir, dir_name)
# `None` indicates that we respect the globally-configured deletion
# settings, which is what we actually want when auto-deleting.
delete_arg = None if autodelete else False
@ -420,20 +429,13 @@ class InstallRequirement(object):
"""
if self.req is None:
return
# get_distribution() will resolve the entire list of requirements
# anyway, and we've already determined that we need the requirement
# in question, so strip the marker so that we don't try to
# evaluate it.
no_marker = Requirement(str(self.req))
no_marker.marker = None
try:
self.satisfied_by = pkg_resources.get_distribution(str(no_marker))
except pkg_resources.DistributionNotFound:
existing_dist = get_distribution(self.req.name)
if not existing_dist:
return
except pkg_resources.VersionConflict:
existing_dist = pkg_resources.get_distribution(
self.req.name
)
existing_version = existing_dist.parsed_version
if not self.req.specifier.contains(existing_version, prereleases=True):
self.satisfied_by = None
if use_user_site:
if dist_in_usersite(existing_dist):
self.should_reinstall = True
@ -447,11 +449,13 @@ class InstallRequirement(object):
else:
self.should_reinstall = True
else:
if self.editable and self.satisfied_by:
if self.editable:
self.should_reinstall = True
# when installing editables, nothing pre-existing should ever
# satisfy
self.satisfied_by = None
else:
self.satisfied_by = existing_dist
# Things valid for wheels
@property
@ -588,8 +592,13 @@ class InstallRequirement(object):
)
# For both source distributions and editables
def ensure_has_source_dir(self, parent_dir, autodelete=False):
# type: (str, bool) -> None
def ensure_has_source_dir(
self,
parent_dir,
autodelete=False,
parallel_builds=False,
):
# type: (str, bool, bool) -> None
"""Ensure that a source_dir is set.
This will create a temporary build dir if the name of the requirement
@ -601,7 +610,9 @@ class InstallRequirement(object):
"""
if self.source_dir is None:
self.source_dir = self.ensure_build_location(
parent_dir, autodelete
parent_dir,
autodelete=autodelete,
parallel_builds=parallel_builds,
)
# For editable installations
@ -664,13 +675,11 @@ class InstallRequirement(object):
"""
assert self.req
try:
dist = pkg_resources.get_distribution(self.req.name)
except pkg_resources.DistributionNotFound:
dist = get_distribution(self.req.name)
if not dist:
logger.warning("Skipping %s as it is not installed.", self.name)
return None
else:
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)
@ -809,6 +818,7 @@ class InstallRequirement(object):
pycompile=pycompile,
warn_script_location=warn_script_location,
direct_url=direct_url,
requested=self.user_supplied,
)
self.install_succeeded = True
return
@ -848,3 +858,35 @@ class InstallRequirement(object):
raise
self.install_succeeded = success
def check_invalid_constraint_type(req):
# type: (InstallRequirement) -> str
# Check for unsupported forms
problem = ""
if not req.name:
problem = "Unnamed requirements are not allowed as constraints"
elif req.link:
problem = "Links are not allowed as constraints"
elif req.extras:
problem = "Constraints cannot have extras"
if problem:
deprecated(
reason=(
"Constraints are only allowed to take the form of a package "
"name and a version specifier. Other forms were originally "
"permitted as an accident of the implementation, but were "
"undocumented. The new implementation of the resolver no "
"longer supports these forms."
),
replacement=(
"replacing the constraint with a requirement."
),
# No plan yet for when the new resolver becomes default
gone_in=None,
issue=8210
)
return problem

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import logging
@ -110,9 +107,8 @@ class RequirementSet(object):
)
# This next bit is really a sanity check.
assert install_req.is_direct == (parent_req_name is None), (
"a direct req shouldn't have a parent and also, "
"a non direct req should 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
@ -122,7 +118,8 @@ class RequirementSet(object):
return [install_req], None
try:
existing_req = self.get_requirement(install_req.name)
existing_req = self.get_requirement(
install_req.name) # type: Optional[InstallRequirement]
except KeyError:
existing_req = None
@ -167,6 +164,10 @@ class RequirementSet(object):
# If we're now installing a constraint, mark the existing
# object for real installation.
existing_req.constraint = False
# If we're now installing a user supplied 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)
))

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import contextlib
@ -98,6 +95,7 @@ class RequirementTracker(object):
"""Add an InstallRequirement to build tracking.
"""
assert req.link
# Get the file to write information about this requirement.
entry_path = self._entry_path(req.link)
@ -130,6 +128,7 @@ class RequirementTracker(object):
"""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)

View File

@ -612,8 +612,7 @@ class UninstallPthEntries(object):
# 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 {}".format(
self.file)
"Cannot remove entries from nonexistent file %s", self.file
)
return
with open(self.file, 'rb') as fh:

View File

@ -28,6 +28,7 @@ from pip._internal.exceptions import (
HashErrors,
UnsupportedPythonVersion,
)
from pip._internal.req.req_install import check_invalid_constraint_type
from pip._internal.req.req_set import RequirementSet
from pip._internal.resolution.base import BaseResolver
from pip._internal.utils.compatibility_tags import get_supported
@ -46,6 +47,7 @@ if MYPY_CHECK_RUNNING:
from pip._internal.cache import WheelCache
from pip._internal.distributions import AbstractDistribution
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.link import Link
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req.req_install import InstallRequirement
from pip._internal.resolution.base import InstallRequirementProvider
@ -166,6 +168,8 @@ class Resolver(BaseResolver):
check_supported_wheels=check_supported_wheels
)
for req in root_reqs:
if req.constraint:
check_invalid_constraint_type(req)
requirement_set.add_requirement(req)
# Actually prepare the files, and collect any exceptions. Most hash
@ -174,7 +178,7 @@ class Resolver(BaseResolver):
# based on link type.
discovered_reqs = [] # type: List[InstallRequirement]
hash_errors = HashErrors()
for req in chain(root_reqs, discovered_reqs):
for req in chain(requirement_set.all_requirements, discovered_reqs):
try:
discovered_reqs.extend(self._resolve_one(requirement_set, req))
except HashError as exc:
@ -194,7 +198,7 @@ class Resolver(BaseResolver):
return True
else:
assert self.upgrade_strategy == "only-if-needed"
return req.is_direct
return req.user_supplied or req.constraint
def _set_req_to_reinstall(self, req):
# type: (InstallRequirement) -> None
@ -260,6 +264,29 @@ class Resolver(BaseResolver):
self._set_req_to_reinstall(req_to_install)
return None
def _find_requirement_link(self, req):
# type: (InstallRequirement) -> Optional[Link]
upgrade = self._is_upgrade_allowed(req)
best_candidate = self.finder.find_requirement(req, upgrade)
if not best_candidate:
return None
# Log a warning per PEP 592 if necessary before returning.
link = best_candidate.link
if link.is_yanked:
reason = link.yanked_reason or '<none given>'
msg = (
# Mark this as a unicode string to prevent
# "UnicodeEncodeError: 'ascii' codec can't encode character"
# in Python 2 when the reason contains non-ascii characters.
u'The candidate selected for download or install is a '
'yanked version: {candidate}\n'
'Reason for being yanked: {reason}'
).format(candidate=best_candidate, reason=reason)
logger.warning(msg)
return link
def _populate_link(self, req):
# type: (InstallRequirement) -> None
"""Ensure that if a link can be found for this, that it is found.
@ -274,9 +301,8 @@ class Resolver(BaseResolver):
mismatches. Furthermore, cached wheels at present have undeterministic
contents due to file modification times.
"""
upgrade = self._is_upgrade_allowed(req)
if req.link is None:
req.link = self.finder.find_requirement(req, upgrade)
req.link = self._find_requirement_link(req)
if self.wheel_cache is None or self.preparer.require_hashes:
return
@ -396,7 +422,7 @@ class Resolver(BaseResolver):
# 'unnamed' requirements will get added here
# 'unnamed' requirements can only come from being directly
# provided by the user.
assert req_to_install.is_direct
assert req_to_install.user_supplied
requirement_set.add_requirement(
req_to_install, parent_req_name=None,
)
@ -412,7 +438,7 @@ class Resolver(BaseResolver):
)
for missing in missing_requested:
logger.warning(
'%s does not provide the extra \'%s\'',
"%s does not provide the extra '%s'",
dist, missing
)

View File

@ -3,14 +3,21 @@ from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Optional, Sequence, Set
from typing import FrozenSet, Iterable, Optional, Tuple
from pip._internal.req.req_install import InstallRequirement
from pip._vendor.packaging.version import _BaseVersion
from pip._internal.models.link import Link
from pip._internal.req.req_install import InstallRequirement
CandidateLookup = Tuple[
Optional["Candidate"],
Optional[InstallRequirement],
]
def format_name(project, extras):
# type: (str, Set[str]) -> str
# type: (str, FrozenSet[str]) -> str
if not extras:
return project
canonical_extras = sorted(canonicalize_name(e) for e in extras)
@ -23,14 +30,18 @@ class Requirement(object):
# type: () -> str
raise NotImplementedError("Subclass should override")
def find_matches(self):
# type: () -> Sequence[Candidate]
raise NotImplementedError("Subclass should override")
def is_satisfied_by(self, candidate):
# type: (Candidate) -> bool
return False
def get_candidate_lookup(self):
# type: () -> CandidateLookup
raise NotImplementedError("Subclass should override")
def format_for_error(self):
# type: () -> str
raise NotImplementedError("Subclass should override")
class Candidate(object):
@property
@ -43,10 +54,29 @@ class Candidate(object):
# type: () -> _BaseVersion
raise NotImplementedError("Override in subclass")
def get_dependencies(self):
# type: () -> Sequence[Requirement]
@property
def is_installed(self):
# type: () -> bool
raise NotImplementedError("Override in subclass")
@property
def is_editable(self):
# type: () -> bool
raise NotImplementedError("Override in subclass")
@property
def source_link(self):
# type: () -> Optional[Link]
raise NotImplementedError("Override in subclass")
def iter_dependencies(self, with_requires):
# type: (bool) -> Iterable[Optional[Requirement]]
raise NotImplementedError("Override in subclass")
def get_install_requirement(self):
# type: () -> Optional[InstallRequirement]
raise NotImplementedError("Override in subclass")
def format_for_error(self):
# type: () -> str
raise NotImplementedError("Subclass should override")

View File

@ -1,23 +1,30 @@
import logging
import sys
from pip._vendor.contextlib2 import suppress
from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.packaging.version import Version
from pip._internal.exceptions import HashError, MetadataInconsistent
from pip._internal.network.lazy_wheel import (
HTTPRangeRequestUnsupported,
dist_from_wheel_url,
)
from pip._internal.req.constructors import (
install_req_from_editable,
install_req_from_line,
)
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.misc import normalize_version_info
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import dist_is_editable, normalize_version_info
from pip._internal.utils.packaging import get_requires_python
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .base import Candidate, format_name
if MYPY_CHECK_RUNNING:
from typing import Any, Optional, Sequence, Set, Tuple, Union
from typing import Any, FrozenSet, Iterable, Optional, Tuple, Union
from pip._vendor.packaging.version import _BaseVersion
from pip._vendor.pkg_resources import Distribution
@ -38,55 +45,69 @@ if MYPY_CHECK_RUNNING:
logger = logging.getLogger(__name__)
def make_install_req_from_link(link, parent):
def make_install_req_from_link(link, template):
# type: (Link, InstallRequirement) -> InstallRequirement
assert not parent.editable, "parent is editable"
return install_req_from_line(
link.url,
comes_from=parent.comes_from,
use_pep517=parent.use_pep517,
isolated=parent.isolated,
constraint=parent.constraint,
assert not template.editable, "template is editable"
if template.req:
line = str(template.req)
else:
line = link.url
ireq = install_req_from_line(
line,
user_supplied=template.user_supplied,
comes_from=template.comes_from,
use_pep517=template.use_pep517,
isolated=template.isolated,
constraint=template.constraint,
options=dict(
install_options=parent.install_options,
global_options=parent.global_options,
hashes=parent.hash_options
install_options=template.install_options,
global_options=template.global_options,
hashes=template.hash_options
),
)
ireq.original_link = template.original_link
ireq.link = link
return ireq
def make_install_req_from_editable(link, parent):
def make_install_req_from_editable(link, template):
# type: (Link, InstallRequirement) -> InstallRequirement
assert parent.editable, "parent not editable"
assert template.editable, "template not editable"
return install_req_from_editable(
link.url,
comes_from=parent.comes_from,
use_pep517=parent.use_pep517,
isolated=parent.isolated,
constraint=parent.constraint,
user_supplied=template.user_supplied,
comes_from=template.comes_from,
use_pep517=template.use_pep517,
isolated=template.isolated,
constraint=template.constraint,
options=dict(
install_options=parent.install_options,
global_options=parent.global_options,
hashes=parent.hash_options
install_options=template.install_options,
global_options=template.global_options,
hashes=template.hash_options
),
)
def make_install_req_from_dist(dist, parent):
def make_install_req_from_dist(dist, template):
# type: (Distribution, InstallRequirement) -> InstallRequirement
project_name = canonicalize_name(dist.project_name)
if template.req:
line = str(template.req)
elif template.link:
line = "{} @ {}".format(project_name, template.link.url)
else:
line = "{}=={}".format(project_name, dist.parsed_version)
ireq = install_req_from_line(
"{}=={}".format(
canonicalize_name(dist.project_name),
dist.parsed_version,
),
comes_from=parent.comes_from,
use_pep517=parent.use_pep517,
isolated=parent.isolated,
constraint=parent.constraint,
line,
user_supplied=template.user_supplied,
comes_from=template.comes_from,
use_pep517=template.use_pep517,
isolated=template.isolated,
constraint=template.constraint,
options=dict(
install_options=parent.install_options,
global_options=parent.global_options,
hashes=parent.hash_options
install_options=template.install_options,
global_options=template.global_options,
hashes=template.hash_options
),
)
ireq.satisfied_by = dist
@ -94,33 +115,56 @@ def make_install_req_from_dist(dist, parent):
class _InstallRequirementBackedCandidate(Candidate):
"""A candidate backed by an ``InstallRequirement``.
This represents a package request with the target not being already
in the environment, and needs to be fetched and installed. The backing
``InstallRequirement`` is responsible for most of the leg work; this
class exposes appropriate information to the resolver.
:param link: The link passed to the ``InstallRequirement``. The backing
``InstallRequirement`` will use this link to fetch the distribution.
:param source_link: The link this candidate "originates" from. This is
different from ``link`` when the link is found in the wheel cache.
``link`` would point to the wheel cache, while this points to the
found remote link (e.g. from pypi.org).
"""
is_installed = False
def __init__(
self,
link, # type: Link
source_link, # type: Link
ireq, # type: InstallRequirement
factory, # type: Factory
name=None, # type: Optional[str]
version=None, # type: Optional[_BaseVersion]
):
# type: (...) -> None
self.link = link
self._link = link
self._source_link = source_link
self._factory = factory
self._ireq = ireq
self._name = name
self._version = version
self._dist = None # type: Optional[Distribution]
self._prepared = False
def __repr__(self):
# type: () -> str
return "{class_name}({link!r})".format(
class_name=self.__class__.__name__,
link=str(self.link),
link=str(self._link),
)
def __hash__(self):
# type: () -> int
return hash((self.__class__, self._link))
def __eq__(self, other):
# type: (Any) -> bool
if isinstance(other, self.__class__):
return self.link == other.link
return self._link == other._link
return False
# Needed for Python 2, which does not implement this by default
@ -128,6 +172,11 @@ class _InstallRequirementBackedCandidate(Candidate):
# type: (Any) -> bool
return not self.__eq__(other)
@property
def source_link(self):
# type: () -> Optional[Link]
return self._source_link
@property
def name(self):
# type: () -> str
@ -143,44 +192,73 @@ class _InstallRequirementBackedCandidate(Candidate):
self._version = self.dist.parsed_version
return self._version
def format_for_error(self):
# type: () -> str
return "{} {} (from {})".format(
self.name,
self.version,
self._link.file_path if self._link.is_file else self._link
)
def _prepare_abstract_distribution(self):
# type: () -> AbstractDistribution
raise NotImplementedError("Override in subclass")
def _prepare(self):
def _check_metadata_consistency(self):
# type: () -> None
if self._dist is not None:
return
abstract_dist = self._prepare_abstract_distribution()
self._dist = abstract_dist.get_pkg_resources_distribution()
assert self._dist is not None, "Distribution already installed"
# TODO: Abort cleanly here, as the resolution has been
# based on the wrong name/version until now, and
# so is wrong.
"""Check for consistency of project name and version of dist."""
# TODO: (Longer term) Rather than abort, reject this candidate
# and backtrack. This would need resolvelib support.
# These should be "proper" errors, not just asserts, as they
# can result from user errors like a requirement "foo @ URL"
# when the project at URL has a name of "bar" in its metadata.
assert (
self._name is None or
self._name == canonicalize_name(self._dist.project_name)
), "Name mismatch: {!r} vs {!r}".format(
self._name, canonicalize_name(self._dist.project_name),
)
assert (
self._version is None or
self._version == self._dist.parsed_version
), "Version mismatch: {!r} vs {!r}".format(
self._version, self._dist.parsed_version,
)
dist = self._dist # type: Distribution
name = canonicalize_name(dist.project_name)
if self._name is not None and self._name != name:
raise MetadataInconsistent(self._ireq, "name", dist.project_name)
version = dist.parsed_version
if self._version is not None and self._version != version:
raise MetadataInconsistent(self._ireq, "version", dist.version)
def _prepare(self):
# type: () -> None
if self._prepared:
return
try:
abstract_dist = self._prepare_abstract_distribution()
except HashError as e:
e.req = self._ireq
raise
self._dist = abstract_dist.get_pkg_resources_distribution()
assert self._dist is not None, "Distribution already installed"
self._check_metadata_consistency()
self._prepared = True
def _fetch_metadata(self):
# type: () -> None
"""Fetch metadata, using lazy wheel if possible."""
preparer = self._factory.preparer
use_lazy_wheel = self._factory.use_lazy_wheel
remote_wheel = self._link.is_wheel and not self._link.is_file
if use_lazy_wheel and remote_wheel and not preparer.require_hashes:
assert self._name is not None
logger.info('Collecting %s', self._ireq.req or self._ireq)
# If HTTPRangeRequestUnsupported is raised, fallback silently.
with indent_log(), suppress(HTTPRangeRequestUnsupported):
logger.info(
'Obtaining dependency information from %s %s',
self._name, self._version,
)
url = self._link.url.split('#', 1)[0]
session = preparer.downloader._session
self._dist = dist_from_wheel_url(self._name, url, session)
self._check_metadata_consistency()
if self._dist is None:
self._prepare()
@property
def dist(self):
# type: () -> Distribution
self._prepare()
if self._dist is None:
self._fetch_metadata()
return self._dist
def _get_requires_python_specifier(self):
@ -197,18 +275,17 @@ class _InstallRequirementBackedCandidate(Candidate):
return None
return spec
def get_dependencies(self):
# type: () -> Sequence[Requirement]
deps = [
self._factory.make_requirement_from_spec(str(r), self._ireq)
for r in self.dist.requires()
]
def iter_dependencies(self, with_requires):
# type: (bool) -> Iterable[Optional[Requirement]]
if not with_requires:
return
for r in self.dist.requires():
yield self._factory.make_requirement_from_spec(str(r), self._ireq)
python_dep = self._factory.make_requires_python_requirement(
self._get_requires_python_specifier(),
)
if python_dep:
deps.append(python_dep)
return deps
yield python_dep
def get_install_requirement(self):
# type: () -> Optional[InstallRequirement]
@ -217,18 +294,33 @@ class _InstallRequirementBackedCandidate(Candidate):
class LinkCandidate(_InstallRequirementBackedCandidate):
is_editable = False
def __init__(
self,
link, # type: Link
parent, # type: InstallRequirement
template, # type: InstallRequirement
factory, # type: Factory
name=None, # type: Optional[str]
version=None, # type: Optional[_BaseVersion]
):
# type: (...) -> None
source_link = link
cache_entry = factory.get_wheel_cache_entry(link, name)
if cache_entry is not None:
logger.debug("Using cached wheel link: %s", cache_entry.link)
link = cache_entry.link
ireq = make_install_req_from_link(link, template)
if (cache_entry is not None and
cache_entry.persistent and
template.link is template.original_link):
ireq.original_link_is_in_wheel_cache = True
super(LinkCandidate, self).__init__(
link=link,
ireq=make_install_req_from_link(link, parent),
source_link=source_link,
ireq=ireq,
factory=factory,
name=name,
version=version,
@ -236,14 +328,18 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
def _prepare_abstract_distribution(self):
# type: () -> AbstractDistribution
return self._factory.preparer.prepare_linked_requirement(self._ireq)
return self._factory.preparer.prepare_linked_requirement(
self._ireq, parallel_builds=True,
)
class EditableCandidate(_InstallRequirementBackedCandidate):
is_editable = True
def __init__(
self,
link, # type: Link
parent, # type: InstallRequirement
template, # type: InstallRequirement
factory, # type: Factory
name=None, # type: Optional[str]
version=None, # type: Optional[_BaseVersion]
@ -251,7 +347,8 @@ class EditableCandidate(_InstallRequirementBackedCandidate):
# type: (...) -> None
super(EditableCandidate, self).__init__(
link=link,
ireq=make_install_req_from_editable(link, parent),
source_link=link,
ireq=make_install_req_from_editable(link, template),
factory=factory,
name=name,
version=version,
@ -263,15 +360,18 @@ class EditableCandidate(_InstallRequirementBackedCandidate):
class AlreadyInstalledCandidate(Candidate):
is_installed = True
source_link = None
def __init__(
self,
dist, # type: Distribution
parent, # type: InstallRequirement
template, # type: InstallRequirement
factory, # type: Factory
):
# type: (...) -> None
self.dist = dist
self._ireq = make_install_req_from_dist(dist, parent)
self._ireq = make_install_req_from_dist(dist, template)
self._factory = factory
# This is just logging some messages, so we can do it eagerly.
@ -288,6 +388,10 @@ class AlreadyInstalledCandidate(Candidate):
distribution=self.dist,
)
def __hash__(self):
# type: () -> int
return hash((self.__class__, self.name, self.version))
def __eq__(self, other):
# type: (Any) -> bool
if isinstance(other, self.__class__):
@ -309,12 +413,21 @@ class AlreadyInstalledCandidate(Candidate):
# type: () -> _BaseVersion
return self.dist.parsed_version
def get_dependencies(self):
# type: () -> Sequence[Requirement]
return [
self._factory.make_requirement_from_spec(str(r), self._ireq)
for r in self.dist.requires()
]
@property
def is_editable(self):
# type: () -> bool
return dist_is_editable(self.dist)
def format_for_error(self):
# type: () -> str
return "{} {} (Installed)".format(self.name, self.version)
def iter_dependencies(self, with_requires):
# type: (bool) -> Iterable[Optional[Requirement]]
if not with_requires:
return
for r in self.dist.requires():
yield self._factory.make_requirement_from_spec(str(r), self._ireq)
def get_install_requirement(self):
# type: () -> Optional[InstallRequirement]
@ -335,8 +448,8 @@ class ExtrasCandidate(Candidate):
to treat it as a separate node in the dependency graph.
2. When we're getting the candidate's dependencies,
a) We specify that we want the extra dependencies as well.
b) We add a dependency on the base candidate (matching the name and
version). See below for why this is needed.
b) We add a dependency on the base candidate.
See below for why this is needed.
3. We return None for the underlying InstallRequirement, as the base
candidate will provide it, and we don't want to end up with duplicates.
@ -348,7 +461,7 @@ class ExtrasCandidate(Candidate):
def __init__(
self,
base, # type: BaseCandidate
extras, # type: Set[str]
extras, # type: FrozenSet[str]
):
# type: (...) -> None
self.base = base
@ -362,6 +475,10 @@ class ExtrasCandidate(Candidate):
extras=self.extras,
)
def __hash__(self):
# type: () -> int
return hash((self.base, self.extras))
def __eq__(self, other):
# type: (Any) -> bool
if isinstance(other, self.__class__):
@ -384,30 +501,56 @@ class ExtrasCandidate(Candidate):
# type: () -> _BaseVersion
return self.base.version
def get_dependencies(self):
# type: () -> Sequence[Requirement]
def format_for_error(self):
# type: () -> str
return "{} [{}]".format(
self.base.format_for_error(),
", ".join(sorted(self.extras))
)
@property
def is_installed(self):
# type: () -> bool
return self.base.is_installed
@property
def is_editable(self):
# type: () -> bool
return self.base.is_editable
@property
def source_link(self):
# type: () -> Optional[Link]
return self.base.source_link
def iter_dependencies(self, with_requires):
# type: (bool) -> Iterable[Optional[Requirement]]
factory = self.base._factory
# Add a dependency on the exact base
# (See note 2b in the class docstring)
yield factory.make_requirement_from_candidate(self.base)
if not with_requires:
return
# 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)
if invalid_extras:
for extra in sorted(invalid_extras):
logger.warning(
"Invalid extras specified in %s: %s",
self.name,
','.join(sorted(invalid_extras))
"%s %s does not provide the extra '%s'",
self.base.name,
self.version,
extra
)
deps = [
factory.make_requirement_from_spec(str(r), self.base._ireq)
for r in self.base.dist.requires(valid_extras)
]
# Add a dependency on the exact base.
# (See note 2b in the class docstring)
spec = "{}=={}".format(self.base.name, self.base.version)
deps.append(factory.make_requirement_from_spec(spec, self.base._ireq))
return deps
for r in self.base.dist.requires(valid_extras):
requirement = factory.make_requirement_from_spec(
str(r), self.base._ireq, valid_extras,
)
if requirement:
yield requirement
def get_install_requirement(self):
# type: () -> Optional[InstallRequirement]
@ -418,6 +561,9 @@ class ExtrasCandidate(Candidate):
class RequiresPythonCandidate(Candidate):
is_installed = False
source_link = None
def __init__(self, py_version_info):
# type: (Optional[Tuple[int, ...]]) -> None
if py_version_info is not None:
@ -434,16 +580,20 @@ class RequiresPythonCandidate(Candidate):
def name(self):
# type: () -> str
# Avoid conflicting with the PyPI package "Python".
return "<Python fom Requires-Python>"
return "<Python from Requires-Python>"
@property
def version(self):
# type: () -> _BaseVersion
return self._version
def get_dependencies(self):
# type: () -> Sequence[Requirement]
return []
def format_for_error(self):
# type: () -> str
return "Python {}".format(self.version)
def iter_dependencies(self, with_requires):
# type: (bool) -> Iterable[Optional[Requirement]]
return ()
def get_install_requirement(self):
# type: () -> Optional[InstallRequirement]

View File

@ -1,11 +1,26 @@
import collections
import logging
from pip._vendor import six
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.exceptions import (
DistributionNotFound,
InstallationError,
UnsupportedPythonVersion,
UnsupportedWheel,
)
from pip._internal.models.wheel import Wheel
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.compatibility_tags import get_supported
from pip._internal.utils.hashes import Hashes
from pip._internal.utils.misc import (
dist_in_site_packages,
dist_in_usersite,
get_installed_distributions,
)
from pip._internal.utils.misc import get_installed_distributions
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.virtualenv import running_under_virtualenv
from .candidates import (
AlreadyInstalledCandidate,
@ -21,17 +36,27 @@ from .requirements import (
)
if MYPY_CHECK_RUNNING:
from typing import Dict, Iterator, Optional, Set, Tuple, TypeVar
from typing import (
FrozenSet,
Dict,
Iterable,
List,
Optional,
Sequence,
Set,
Tuple,
TypeVar,
)
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.version import _BaseVersion
from pip._vendor.pkg_resources import Distribution
from pip._vendor.resolvelib import ResolutionImpossible
from pip._internal.cache import CacheEntry, WheelCache
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.link import Link
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req.req_install import InstallRequirement
from pip._internal.resolution.base import InstallRequirementProvider
from .base import Candidate, Requirement
@ -39,6 +64,10 @@ if MYPY_CHECK_RUNNING:
C = TypeVar("C")
Cache = Dict[Link, C]
VersionCandidates = Dict[_BaseVersion, Candidate]
logger = logging.getLogger(__name__)
class Factory(object):
@ -47,18 +76,24 @@ class Factory(object):
finder, # type: PackageFinder
preparer, # type: RequirementPreparer
make_install_req, # type: InstallRequirementProvider
wheel_cache, # type: Optional[WheelCache]
use_user_site, # type: bool
force_reinstall, # type: bool
ignore_installed, # type: bool
ignore_requires_python, # type: bool
py_version_info=None, # type: Optional[Tuple[int, ...]]
lazy_wheel=False, # type: bool
):
# type: (...) -> None
self.finder = finder
self._finder = finder
self.preparer = preparer
self._wheel_cache = wheel_cache
self._python_candidate = RequiresPythonCandidate(py_version_info)
self._make_install_req_from_spec = make_install_req
self._use_user_site = use_user_site
self._force_reinstall = force_reinstall
self._ignore_requires_python = ignore_requires_python
self.use_lazy_wheel = lazy_wheel
self._link_candidate_cache = {} # type: Cache[LinkCandidate]
self._editable_candidate_cache = {} # type: Cache[EditableCandidate]
@ -71,96 +106,186 @@ class Factory(object):
else:
self._installed_dists = {}
@property
def force_reinstall(self):
# type: () -> bool
return self._force_reinstall
def _make_candidate_from_dist(
self,
dist, # type: Distribution
extras, # type: Set[str]
parent, # type: InstallRequirement
extras, # type: FrozenSet[str]
template, # type: InstallRequirement
):
# type: (...) -> Candidate
base = AlreadyInstalledCandidate(dist, parent, factory=self)
base = AlreadyInstalledCandidate(dist, template, factory=self)
if extras:
return ExtrasCandidate(base, extras)
return base
def _make_candidate_from_link(
self,
link, # type: Link
extras, # type: Set[str]
parent, # type: InstallRequirement
name=None, # type: Optional[str]
version=None, # type: Optional[_BaseVersion]
link, # type: Link
extras, # type: FrozenSet[str]
template, # type: InstallRequirement
name, # type: Optional[str]
version, # type: Optional[_BaseVersion]
):
# type: (...) -> Candidate
# TODO: Check already installed candidate, and use it if the link and
# editable flag match.
if parent.editable:
if template.editable:
if link not in self._editable_candidate_cache:
self._editable_candidate_cache[link] = EditableCandidate(
link, parent, factory=self, name=name, version=version,
link, template, factory=self, name=name, version=version,
)
base = self._editable_candidate_cache[link] # type: BaseCandidate
else:
if link not in self._link_candidate_cache:
self._link_candidate_cache[link] = LinkCandidate(
link, parent, factory=self, name=name, version=version,
link, template, factory=self, name=name, version=version,
)
base = self._link_candidate_cache[link]
if extras:
return ExtrasCandidate(base, extras)
return base
def iter_found_candidates(self, ireq, extras):
# type: (InstallRequirement, Set[str]) -> Iterator[Candidate]
name = canonicalize_name(ireq.req.name)
if not self._force_reinstall:
installed_dist = self._installed_dists.get(name)
else:
installed_dist = None
def _iter_found_candidates(
self,
ireqs, # type: Sequence[InstallRequirement]
specifier, # type: SpecifierSet
):
# type: (...) -> Iterable[Candidate]
if not ireqs:
return ()
found = self.finder.find_best_candidate(
project_name=ireq.req.name,
specifier=ireq.req.specifier,
hashes=ireq.hashes(trust_internet=False),
# The InstallRequirement implementation requires us to give it a
# "template". Here we just choose the first requirement to represent
# all of them.
# Hopefully the Project model can correct this mismatch in the future.
template = ireqs[0]
name = canonicalize_name(template.req.name)
hashes = Hashes()
extras = frozenset() # type: FrozenSet[str]
for ireq in ireqs:
specifier &= ireq.req.specifier
hashes |= ireq.hashes(trust_internet=False)
extras |= frozenset(ireq.extras)
# We use this to ensure that we only yield a single candidate for
# each version (the finder's preferred one for that version). The
# requirement needs to return only one candidate per version, so we
# implement that logic here so that requirements using this helper
# don't all have to do the same thing later.
candidates = collections.OrderedDict() # type: VersionCandidates
# Get the installed version, if it matches, unless the user
# specified `--force-reinstall`, when we want the version from
# the index instead.
installed_version = None
installed_candidate = None
if not self._force_reinstall and name in self._installed_dists:
installed_dist = self._installed_dists[name]
installed_version = installed_dist.parsed_version
if specifier.contains(installed_version, prereleases=True):
installed_candidate = self._make_candidate_from_dist(
dist=installed_dist,
extras=extras,
template=template,
)
found = self._finder.find_best_candidate(
project_name=name,
specifier=specifier,
hashes=hashes,
)
for ican in found.iter_applicable():
if (installed_dist is not None and
installed_dist.parsed_version == ican.version):
continue
yield self._make_candidate_from_link(
link=ican.link,
extras=extras,
parent=ireq,
name=name,
version=ican.version,
if ican.version == installed_version and installed_candidate:
candidate = installed_candidate
else:
candidate = self._make_candidate_from_link(
link=ican.link,
extras=extras,
template=template,
name=name,
version=ican.version,
)
candidates[ican.version] = candidate
# Yield the installed version even if it is not found on the index.
if installed_version and installed_candidate:
candidates[installed_version] = installed_candidate
return six.itervalues(candidates)
def find_candidates(self, requirements, constraint):
# type: (Sequence[Requirement], SpecifierSet) -> Iterable[Candidate]
explicit_candidates = set() # type: Set[Candidate]
ireqs = [] # type: List[InstallRequirement]
for req in requirements:
cand, ireq = req.get_candidate_lookup()
if cand is not None:
explicit_candidates.add(cand)
if ireq is not None:
ireqs.append(ireq)
# If none of the requirements want an explicit candidate, we can ask
# the finder for candidates.
if not explicit_candidates:
return self._iter_found_candidates(ireqs, constraint)
if constraint:
name = explicit_candidates.pop().name
raise InstallationError(
"Could not satisfy constraints for {!r}: installation from "
"path or url cannot be constrained to a version".format(name)
)
# Return installed distribution if it matches the specifier. This is
# done last so the resolver will prefer it over downloading links.
if (installed_dist is not None and
installed_dist.parsed_version in ireq.req.specifier):
yield self._make_candidate_from_dist(
dist=installed_dist,
extras=extras,
parent=ireq,
)
return (
c for c in explicit_candidates
if all(req.is_satisfied_by(c) for req in requirements)
)
def make_requirement_from_install_req(self, ireq):
# type: (InstallRequirement) -> Requirement
if ireq.link:
# TODO: Get name and version from ireq, if possible?
# Specifically, this might be needed in "name @ URL"
# syntax - need to check where that syntax is handled.
cand = self._make_candidate_from_link(
ireq.link, extras=set(), parent=ireq,
def make_requirement_from_install_req(self, ireq, requested_extras):
# type: (InstallRequirement, Iterable[str]) -> Optional[Requirement]
if not ireq.match_markers(requested_extras):
logger.info(
"Ignoring %s: markers '%s' don't match your environment",
ireq.name, ireq.markers,
)
return ExplicitRequirement(cand)
return SpecifierRequirement(ireq, factory=self)
return None
if not ireq.link:
return SpecifierRequirement(ireq)
if ireq.link.is_wheel:
wheel = Wheel(ireq.link.filename)
if not wheel.supported(self._finder.target_python.get_tags()):
msg = "{} is not a supported wheel on this platform.".format(
wheel.filename,
)
raise UnsupportedWheel(msg)
cand = self._make_candidate_from_link(
ireq.link,
extras=frozenset(ireq.extras),
template=ireq,
name=canonicalize_name(ireq.name) if ireq.name else None,
version=None,
)
return self.make_requirement_from_candidate(cand)
def make_requirement_from_spec(self, specifier, comes_from):
# type: (str, InstallRequirement) -> Requirement
def make_requirement_from_candidate(self, candidate):
# type: (Candidate) -> ExplicitRequirement
return ExplicitRequirement(candidate)
def make_requirement_from_spec(
self,
specifier, # type: str
comes_from, # type: InstallRequirement
requested_extras=(), # type: Iterable[str]
):
# type: (...) -> Optional[Requirement]
ireq = self._make_install_req_from_spec(specifier, comes_from)
return self.make_requirement_from_install_req(ireq)
return self.make_requirement_from_install_req(ireq, requested_extras)
def make_requires_python_requirement(self, specifier):
# type: (Optional[SpecifierSet]) -> Optional[Requirement]
@ -168,34 +293,167 @@ class Factory(object):
return None
return RequiresPythonRequirement(specifier, self._python_candidate)
def should_reinstall(self, candidate):
# type: (Candidate) -> bool
def get_wheel_cache_entry(self, link, name):
# type: (Link, Optional[str]) -> Optional[CacheEntry]
"""Look up the link in the wheel cache.
If ``preparer.require_hashes`` is True, don't use the wheel cache,
because cached wheels, always built locally, have different hashes
than the files downloaded from the index server and thus throw false
hash mismatches. Furthermore, cached wheels at present have
nondeterministic contents due to file modification times.
"""
if self._wheel_cache is None or self.preparer.require_hashes:
return None
return self._wheel_cache.get_cache_entry(
link=link,
package_name=name,
supported_tags=get_supported(),
)
def get_dist_to_uninstall(self, candidate):
# type: (Candidate) -> Optional[Distribution]
# TODO: Are there more cases this needs to return True? Editable?
return candidate.name in self._installed_dists
dist = self._installed_dists.get(candidate.name)
if dist is None: # Not installed, no uninstallation required.
return None
# We're installing into global site. The current installation must
# be uninstalled, no matter it's in global or user site, because the
# user site installation has precedence over global.
if not self._use_user_site:
return dist
# We're installing into user site. Remove the user site installation.
if dist_in_usersite(dist):
return dist
# We're installing into user site, but the installed incompatible
# package is in global site. We can't uninstall that, and would let
# the new user installation to "shadow" it. But shadowing won't work
# in virtual environments, so we error out.
if running_under_virtualenv() and dist_in_site_packages(dist):
raise InstallationError(
"Will not install to the user site because it will "
"lack sys.path precedence to {} in {}".format(
dist.project_name, dist.location,
)
)
return None
def _report_requires_python_error(
self,
requirement, # type: RequiresPythonRequirement
parent, # type: Candidate
template, # type: Candidate
):
# type: (...) -> UnsupportedPythonVersion
template = (
message_format = (
"Package {package!r} requires a different Python: "
"{version} not in {specifier!r}"
)
message = template.format(
package=parent.name,
message = message_format.format(
package=template.name,
version=self._python_candidate.version,
specifier=str(requirement.specifier),
)
return UnsupportedPythonVersion(message)
def get_installation_error(self, e):
# type: (ResolutionImpossible) -> Optional[InstallationError]
# type: (ResolutionImpossible) -> InstallationError
assert e.causes, "Installation error reported with no cause"
# If one of the things we can't solve is "we need Python X.Y",
# that is what we report.
for cause in e.causes:
if isinstance(cause.requirement, RequiresPythonRequirement):
return self._report_requires_python_error(
cause.requirement,
cause.parent,
)
return None
# Otherwise, we have a set of causes which can't all be satisfied
# at once.
# The simplest case is when we have *one* cause that can't be
# satisfied. We just report that case.
if len(e.causes) == 1:
req, parent = e.causes[0]
if parent is None:
req_disp = str(req)
else:
req_disp = '{} (from {})'.format(req, parent.name)
logger.critical(
"Could not find a version that satisfies the requirement %s",
req_disp,
)
return DistributionNotFound(
'No matching distribution found for {}'.format(req)
)
# OK, we now have a list of requirements that can't all be
# satisfied at once.
# A couple of formatting helpers
def text_join(parts):
# type: (List[str]) -> str
if len(parts) == 1:
return parts[0]
return ", ".join(parts[:-1]) + " and " + parts[-1]
def readable_form(cand):
# type: (Candidate) -> str
return "{} {}".format(cand.name, cand.version)
def describe_trigger(parent):
# type: (Candidate) -> str
ireq = parent.get_install_requirement()
if not ireq or not ireq.comes_from:
return "{} {}".format(parent.name, parent.version)
if isinstance(ireq.comes_from, InstallRequirement):
return str(ireq.comes_from.name)
return str(ireq.comes_from)
triggers = []
for req, parent in e.causes:
if parent is None:
# This is a root requirement, so we can report it directly
trigger = req.format_for_error()
else:
trigger = describe_trigger(parent)
triggers.append(trigger)
if triggers:
info = text_join(triggers)
else:
info = "the requested packages"
msg = "Cannot install {} because these package versions " \
"have conflicting dependencies.".format(info)
logger.critical(msg)
msg = "\nThe conflict is caused by:"
for req, parent in e.causes:
msg = msg + "\n "
if parent:
msg = msg + "{} {} depends on ".format(
parent.name,
parent.version
)
else:
msg = msg + "The user requested "
msg = msg + req.format_for_error()
msg = msg + "\n\n" + \
"To fix this you could try to:\n" + \
"1. loosen the range of package versions you've specified\n" + \
"2. remove package versions to allow pip attempt to solve " + \
"the dependency conflict\n"
logger.info(msg)
return DistributionNotFound(
"ResolutionImpossible: for help visit "
"https://pip.pypa.io/en/latest/user_guide/"
"#fixing-conflicting-dependencies"
)

View File

@ -1,29 +1,119 @@
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.resolvelib.providers import AbstractProvider
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Any, Optional, Sequence, Tuple, Union
from pip._internal.req.req_install import InstallRequirement
from typing import (
Any,
Dict,
Iterable,
Optional,
Sequence,
Set,
Tuple,
Union,
)
from .base import Requirement, Candidate
from .factory import Factory
# Notes on the relationship between the provider, the factory, and the
# candidate and requirement classes.
#
# The provider is a direct implementation of the resolvelib class. Its role
# is to deliver the API that resolvelib expects.
#
# Rather than work with completely abstract "requirement" and "candidate"
# concepts as resolvelib does, pip has concrete classes implementing these two
# ideas. The API of Requirement and Candidate objects are defined in the base
# classes, but essentially map fairly directly to the equivalent provider
# methods. In particular, `find_matches` and `is_satisfied_by` are
# requirement methods, and `get_dependencies` is a candidate method.
#
# The factory is the interface to pip's internal mechanisms. It is stateless,
# and is created by the resolver and held as a property of the provider. It is
# responsible for creating Requirement and Candidate objects, and provides
# services to those objects (access to pip's finder and preparer).
class PipProvider(AbstractProvider):
def __init__(
self,
factory, # type: Factory
constraints, # type: Dict[str, SpecifierSet]
ignore_dependencies, # type: bool
upgrade_strategy, # type: str
user_requested, # type: Set[str]
):
# type: (...) -> None
self._factory = factory
self._constraints = constraints
self._ignore_dependencies = ignore_dependencies
self._upgrade_strategy = upgrade_strategy
self.user_requested = user_requested
def get_install_requirement(self, c):
# type: (Candidate) -> Optional[InstallRequirement]
return c.get_install_requirement()
def _sort_matches(self, matches):
# type: (Iterable[Candidate]) -> Sequence[Candidate]
# The requirement is responsible for returning a sequence of potential
# candidates, one per version. The provider handles the logic of
# deciding the order in which these candidates should be passed to
# the resolver.
# The `matches` argument is a sequence of candidates, one per version,
# which are potential options to be installed. The requirement will
# have already sorted out whether to give us an already-installed
# candidate or a version from PyPI (i.e., it will deal with options
# like --force-reinstall and --ignore-installed).
# We now work out the correct order.
#
# 1. If no other considerations apply, later versions take priority.
# 2. An already installed distribution is preferred over any other,
# unless the user has requested an upgrade.
# Upgrades are allowed when:
# * The --upgrade flag is set, and
# - The project was specified on the command line, or
# - The project is a dependency and the "eager" upgrade strategy
# was requested.
def _eligible_for_upgrade(name):
# type: (str) -> bool
"""Are upgrades allowed for this project?
This checks the upgrade strategy, and whether the project was one
that the user specified in the command line, in order to decide
whether we should upgrade if there's a newer version available.
(Note that we don't need access to the `--upgrade` flag, because
an upgrade strategy of "to-satisfy-only" means that `--upgrade`
was not specified).
"""
if self._upgrade_strategy == "eager":
return True
elif self._upgrade_strategy == "only-if-needed":
return (name in self.user_requested)
return False
def sort_key(c):
# type: (Candidate) -> int
"""Return a sort key for the matches.
The highest priority should be given to installed candidates that
are not eligible for upgrade. We use the integer value in the first
part of the key to sort these before other candidates.
We only pull the installed candidate to the bottom (i.e. most
preferred), but otherwise keep the ordering returned by the
requirement. The requirement is responsible for returning a list
otherwise sorted for the resolver, taking account for versions
and binary preferences as specified by the user.
"""
if c.is_installed and not _eligible_for_upgrade(c.name):
return 1
return 0
return sorted(matches, key=sort_key)
def identify(self, dependency):
# type: (Union[Requirement, Candidate]) -> str
@ -39,9 +129,15 @@ class PipProvider(AbstractProvider):
# Use the "usual" value for now
return len(candidates)
def find_matches(self, requirement):
# type: (Requirement) -> Sequence[Candidate]
return requirement.find_matches()
def find_matches(self, requirements):
# type: (Sequence[Requirement]) -> Iterable[Candidate]
if not requirements:
return []
constraint = self._constraints.get(
requirements[0].name, SpecifierSet(),
)
candidates = self._factory.find_candidates(requirements, constraint)
return reversed(self._sort_matches(candidates))
def is_satisfied_by(self, requirement, candidate):
# type: (Requirement, Candidate) -> bool
@ -49,6 +145,9 @@ class PipProvider(AbstractProvider):
def get_dependencies(self, candidate):
# type: (Candidate) -> Sequence[Requirement]
if self._ignore_dependencies:
return []
return candidate.get_dependencies()
with_requires = not self._ignore_dependencies
return [
r
for r in candidate.iter_dependencies(with_requires)
if r is not None
]

View File

@ -5,14 +5,11 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .base import Requirement, format_name
if MYPY_CHECK_RUNNING:
from typing import Sequence
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._internal.req.req_install import InstallRequirement
from .base import Candidate
from .factory import Factory
from .base import Candidate, CandidateLookup
class ExplicitRequirement(Requirement):
@ -33,9 +30,13 @@ class ExplicitRequirement(Requirement):
# No need to canonicalise - the candidate did this
return self.candidate.name
def find_matches(self):
# type: () -> Sequence[Candidate]
return [self.candidate]
def format_for_error(self):
# type: () -> str
return self.candidate.format_for_error()
def get_candidate_lookup(self):
# type: () -> CandidateLookup
return self.candidate, None
def is_satisfied_by(self, candidate):
# type: (Candidate) -> bool
@ -43,12 +44,11 @@ class ExplicitRequirement(Requirement):
class SpecifierRequirement(Requirement):
def __init__(self, ireq, factory):
# type: (InstallRequirement, Factory) -> None
def __init__(self, ireq):
# type: (InstallRequirement) -> None
assert ireq.link is None, "This is a link, not a specifier"
self._ireq = ireq
self._factory = factory
self.extras = ireq.req.extras
self._extras = frozenset(ireq.extras)
def __str__(self):
# type: () -> str
@ -65,12 +65,26 @@ class SpecifierRequirement(Requirement):
def name(self):
# type: () -> str
canonical_name = canonicalize_name(self._ireq.req.name)
return format_name(canonical_name, self.extras)
return format_name(canonical_name, self._extras)
def find_matches(self):
# type: () -> Sequence[Candidate]
it = self._factory.iter_found_candidates(self._ireq, self.extras)
return list(it)
def format_for_error(self):
# type: () -> str
# Convert comma-separated specifiers into "A, B, ..., F and G"
# This makes the specifier a bit more "human readable", without
# risking a change in meaning. (Hopefully! Not all edge cases have
# been checked)
parts = [s.strip() for s in str(self).split(",")]
if len(parts) == 0:
return ""
elif len(parts) == 1:
return parts[0]
return ", ".join(parts[:-1]) + " and " + parts[-1]
def get_candidate_lookup(self):
# type: () -> CandidateLookup
return None, self._ireq
def is_satisfied_by(self, candidate):
# type: (Candidate) -> bool
@ -104,11 +118,15 @@ class RequiresPythonRequirement(Requirement):
# type: () -> str
return self._candidate.name
def find_matches(self):
# type: () -> Sequence[Candidate]
if self._candidate.version in self.specifier:
return [self._candidate]
return []
def format_for_error(self):
# type: () -> str
return "Python " + str(self.specifier)
def get_candidate_lookup(self):
# type: () -> CandidateLookup
if self.specifier.contains(self._candidate.version, prereleases=True):
return self._candidate, None
return None, None
def is_satisfied_by(self, candidate):
# type: (Candidate) -> bool

View File

@ -7,17 +7,21 @@ from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible
from pip._vendor.resolvelib import Resolver as RLResolver
from pip._internal.exceptions import InstallationError
from pip._internal.req.req_install import check_invalid_constraint_type
from pip._internal.req.req_set import RequirementSet
from pip._internal.resolution.base import BaseResolver
from pip._internal.resolution.resolvelib.provider import PipProvider
from pip._internal.utils.misc import dist_is_editable
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .factory import Factory
if MYPY_CHECK_RUNNING:
from typing import Dict, List, Optional, Tuple
from typing import Dict, List, Optional, Set, Tuple
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.resolvelib.resolvers import Result
from pip._vendor.resolvelib.structs import Graph
from pip._internal.cache import WheelCache
from pip._internal.index.package_finder import PackageFinder
@ -30,6 +34,8 @@ logger = logging.getLogger(__name__)
class Resolver(BaseResolver):
_allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"}
def __init__(
self,
preparer, # type: RequirementPreparer
@ -43,113 +49,146 @@ class Resolver(BaseResolver):
force_reinstall, # type: bool
upgrade_strategy, # type: str
py_version_info=None, # type: Optional[Tuple[int, ...]]
lazy_wheel=False, # type: bool
):
super(Resolver, self).__init__()
if lazy_wheel:
logger.warning(
'pip is using lazily downloaded wheels using HTTP '
'range requests to obtain dependency information. '
'This experimental feature is enabled through '
'--use-feature=fast-deps and it is not ready for production.'
)
assert upgrade_strategy in self._allowed_strategies
self.factory = Factory(
finder=finder,
preparer=preparer,
make_install_req=make_install_req,
wheel_cache=wheel_cache,
use_user_site=use_user_site,
force_reinstall=force_reinstall,
ignore_installed=ignore_installed,
ignore_requires_python=ignore_requires_python,
py_version_info=py_version_info,
lazy_wheel=lazy_wheel,
)
self.ignore_dependencies = ignore_dependencies
self.upgrade_strategy = upgrade_strategy
self._result = None # type: Optional[Result]
def resolve(self, root_reqs, check_supported_wheels):
# type: (List[InstallRequirement], bool) -> RequirementSet
# FIXME: Implement constraints.
if any(r.constraint for r in root_reqs):
raise InstallationError("Constraints are not yet supported.")
constraints = {} # type: Dict[str, SpecifierSet]
user_requested = set() # type: Set[str]
requirements = []
for req in root_reqs:
if req.constraint:
# Ensure we only accept valid constraints
problem = check_invalid_constraint_type(req)
if problem:
raise InstallationError(problem)
if not req.match_markers():
continue
name = canonicalize_name(req.name)
if name in constraints:
constraints[name] = constraints[name] & req.specifier
else:
constraints[name] = req.specifier
else:
if req.user_supplied and req.name:
user_requested.add(canonicalize_name(req.name))
r = self.factory.make_requirement_from_install_req(
req, requested_extras=(),
)
if r is not None:
requirements.append(r)
provider = PipProvider(
factory=self.factory,
constraints=constraints,
ignore_dependencies=self.ignore_dependencies,
upgrade_strategy=self.upgrade_strategy,
user_requested=user_requested,
)
reporter = BaseReporter()
resolver = RLResolver(provider, reporter)
requirements = [
self.factory.make_requirement_from_install_req(r)
for r in root_reqs
]
try:
self._result = resolver.resolve(requirements)
try_to_avoid_resolution_too_deep = 2000000
self._result = resolver.resolve(
requirements, max_rounds=try_to_avoid_resolution_too_deep,
)
except ResolutionImpossible as e:
error = self.factory.get_installation_error(e)
if not error:
# TODO: This needs fixing, we need to look at the
# factory.get_installation_error infrastructure, as that
# doesn't really allow for the logger.critical calls I'm
# using here.
for req, parent in e.causes:
logger.critical(
"Could not find a version that satisfies " +
"the requirement " +
str(req) +
("" if parent is None else " (from {})".format(
parent.name
))
)
raise InstallationError(
"No matching distribution found for " +
", ".join([r.name for r, _ in e.causes])
)
raise
six.raise_from(error, e)
req_set = RequirementSet(check_supported_wheels=check_supported_wheels)
for candidate in self._result.mapping.values():
ireq = provider.get_install_requirement(candidate)
ireq = candidate.get_install_requirement()
if ireq is None:
continue
ireq.should_reinstall = self.factory.should_reinstall(candidate)
# Check if there is already an installation under the same name,
# and set a flag for later stages to uninstall it, if needed.
# * There isn't, good -- no uninstalltion needed.
# * The --force-reinstall flag is set. Always reinstall.
# * The installation is different in version or editable-ness, so
# we need to uninstall it to install the new distribution.
# * The installed version is the same as the pending distribution.
# Skip this distrubiton altogether to save work.
installed_dist = self.factory.get_dist_to_uninstall(candidate)
if installed_dist is None:
ireq.should_reinstall = False
elif self.factory.force_reinstall:
ireq.should_reinstall = True
elif installed_dist.parsed_version != candidate.version:
ireq.should_reinstall = True
elif dist_is_editable(installed_dist) != candidate.is_editable:
ireq.should_reinstall = True
else:
continue
link = candidate.source_link
if link and link.is_yanked:
# The reason can contain non-ASCII characters, Unicode
# is required for Python 2.
msg = (
u'The candidate selected for download or install is a '
u'yanked version: {name!r} candidate (version {version} '
u'at {link})\nReason for being yanked: {reason}'
).format(
name=candidate.name,
version=candidate.version,
link=link,
reason=link.yanked_reason or u'<none given>',
)
logger.warning(msg)
req_set.add_named_requirement(ireq)
return req_set
def get_installation_order(self, req_set):
# type: (RequirementSet) -> List[InstallRequirement]
"""Create a list that orders given requirements for installation.
"""Get order for installation of requirements in RequirementSet.
The returned list should contain all requirements in ``req_set``,
so the caller can loop through it and have a requirement installed
before the requiring thing.
The returned list contains a requirement before another that depends on
it. This helps ensure that the environment is kept consistent as they
get installed one-by-one.
The current implementation walks the resolved dependency graph, and
make sure every node has a greater "weight" than all its parents.
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.
"""
assert self._result is not None, "must call resolve() first"
weights = {} # type: Dict[Optional[str], int]
graph = self._result.graph
key_count = len(self._result.mapping) + 1 # Packages plus sentinal.
while len(weights) < key_count:
progressed = False
for key in graph:
if key in weights:
continue
parents = list(graph.iter_parents(key))
if not all(p in weights for p in parents):
continue
if parents:
weight = max(weights[p] for p in parents) + 1
else:
weight = 0
weights[key] = weight
progressed = True
# FIXME: This check will fail if there are unbreakable cycles.
# Implement something to forcifully break them up to continue.
if not progressed:
raise InstallationError(
"Could not determine installation order due to cicular "
"dependency."
)
weights = get_topological_weights(graph)
sorted_items = sorted(
req_set.requirements.items(),
@ -159,6 +198,52 @@ class Resolver(BaseResolver):
return [ireq for _, ireq in sorted_items]
def get_topological_weights(graph):
# type: (Graph) -> 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.
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
don't get stuck in a cycle.
When assigning weight, the longer path (i.e. larger length) is preferred.
"""
path = set() # type: Set[Optional[str]]
weights = {} # type: Dict[Optional[str], int]
def visit(node):
# type: (Optional[str]) -> None
if node in path:
# We hit a cycle, so we'll break it here.
return
# Time to visit the children!
path.add(node)
for child in graph.iter_children(node):
visit(child)
path.remove(node)
last_known_parent_count = weights.get(node, 0)
weights[node] = max(last_known_parent_count, len(path))
# `None` is guaranteed to be the root node by resolvelib.
visit(None)
# Sanity checks
assert weights[None] == 0
assert len(weights) == len(graph)
return weights
def _req_set_item_sorter(
item, # type: Tuple[str, InstallRequirement]
weights, # type: Dict[Optional[str], int]

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import datetime
@ -10,13 +7,11 @@ import logging
import os.path
import sys
from pip._vendor import pkg_resources
from pip._vendor.packaging import version as packaging_version
from pip._vendor.six import ensure_binary
from pip._internal.index.collector import LinkCollector
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.search_scope import SearchScope
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.utils.filesystem import (
adjacent_tmp_file,
@ -25,15 +20,14 @@ from pip._internal.utils.filesystem import (
)
from pip._internal.utils.misc import (
ensure_dir,
get_distribution,
get_installed_version,
redact_auth_from_url,
)
from pip._internal.utils.packaging import get_installer
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
import optparse
from optparse import Values
from typing import Any, Dict, Text, Union
from pip._internal.network.session import PipSession
@ -45,37 +39,6 @@ SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ"
logger = logging.getLogger(__name__)
def make_link_collector(
session, # type: PipSession
options, # type: Values
suppress_no_index=False, # type: bool
):
# type: (...) -> LinkCollector
"""
:param session: The Session to use to make requests.
:param suppress_no_index: Whether to ignore the --no-index option
when constructing the SearchScope object.
"""
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),
)
index_urls = []
# Make sure find_links is a list before passing to create().
find_links = options.find_links or []
search_scope = SearchScope.create(
find_links=find_links, index_urls=index_urls,
)
link_collector = LinkCollector(session=session, search_scope=search_scope)
return link_collector
def _get_statefile_name(key):
# type: (Union[str, Text]) -> str
key_bytes = ensure_binary(key)
@ -104,6 +67,7 @@ class SelfCheckState(object):
@property
def key(self):
# type: () -> str
return sys.prefix
def save(self, pypi_version, current_time):
@ -149,11 +113,10 @@ def was_installed_by_pip(pkg):
This is used not to display the upgrade message when pip is in fact
installed by system package manager, such as dnf on Fedora.
"""
try:
dist = pkg_resources.get_distribution(pkg)
return "pip" == get_installer(dist)
except pkg_resources.DistributionNotFound:
dist = get_distribution(pkg)
if not dist:
return False
return "pip" == get_installer(dist)
def pip_self_version_check(session, options):
@ -187,7 +150,7 @@ def pip_self_version_check(session, options):
# Refresh the version if we need to or just see if we need to warn
if pypi_version is None:
# Lets use PackageFinder to see what the latest pip version is
link_collector = make_link_collector(
link_collector = LinkCollector.create(
session,
options=options,
suppress_no_index=True,

View File

@ -121,10 +121,11 @@ def str_to_display(data, desc=None):
try:
decoded_data = data.decode(encoding)
except UnicodeDecodeError:
if desc is None:
desc = 'Bytes object'
msg_format = '{} does not appear to be encoded as %s'.format(desc)
logger.warning(msg_format, encoding)
logger.warning(
'%s does not appear to be encoded as %s',
desc or 'Bytes object',
encoding,
)
decoded_data = data.decode(encoding, errors=backslashreplace_decode)
# Make sure we can print the output, by encoding it to the output

View File

@ -3,7 +3,6 @@
from __future__ import absolute_import
import logging
import re
from pip._vendor.packaging.tags import (
@ -23,8 +22,6 @@ if MYPY_CHECK_RUNNING:
from pip._vendor.packaging.tags import PythonVersion
logger = logging.getLogger(__name__)
_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)')

View File

@ -0,0 +1,14 @@
"""For when pip wants to check the date or time.
"""
from __future__ import absolute_import
import datetime
def today_is_later_than(year, month, day):
# type: (int, int, int) -> bool
today = datetime.date.today()
given = datetime.date(year, month, day)
return today > given

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import codecs
import locale
import re
@ -35,7 +32,9 @@ def auto_decode(data):
# Lets check the first two lines as in PEP263
for line in data.split(b'\n')[:2]:
if line[0:1] == b'#' and ENCODING_RE.search(line):
encoding = ENCODING_RE.search(line).groups()[0].decode('ascii')
result = ENCODING_RE.search(line)
assert result is not None
encoding = result.groups()[0].decode('ascii')
return data.decode(encoding)
return data.decode(
locale.getpreferredencoding(False) or sys.getdefaultencoding(),

View File

@ -155,7 +155,7 @@ def _test_writable_dir_win(path):
# and we can't use tempfile: http://bugs.python.org/issue22107
basename = 'accesstest_deleteme_fishfingers_custard_'
alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
for i in range(10):
for _ in range(10):
name = basename + ''.join(random.choice(alphabet) for _ in range(6))
file = os.path.join(path, name)
try:
@ -170,6 +170,8 @@ def _test_writable_dir_win(path):
# This could be because there's a directory with the same name.
# But it's highly unlikely there's a directory called that,
# so we'll assume it's because the parent dir is not writable.
# This could as well be because the parent dir is not readable,
# due to non-privileged user access.
return False
raise
else:
@ -188,7 +190,7 @@ def find_files(path, pattern):
"""Returns a list of absolute paths of files beneath path, recursively,
with filenames which match the UNIX-style shell glob pattern."""
result = [] # type: List[str]
for root, dirs, files in os.walk(path):
for root, _, files in os.walk(path):
matches = fnmatch.filter(files, pattern)
result.extend(os.path.join(root, f) for f in matches)
return result

View File

@ -46,6 +46,18 @@ class Hashes(object):
"""
self._allowed = {} if hashes is None else hashes
def __or__(self, other):
# type: (Hashes) -> Hashes
if not isinstance(other, Hashes):
return NotImplemented
new = self._allowed.copy()
for alg, values in iteritems(other._allowed):
try:
new[alg] += values
except KeyError:
new[alg] = values
return Hashes(new)
@property
def digest_count(self):
# type: () -> int

View File

@ -16,13 +16,15 @@ import shutil
import stat
import sys
from collections import deque
from itertools import tee
from pip._vendor import pkg_resources
from pip._vendor.packaging.utils import canonicalize_name
# NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is
# why we ignore the type on this import.
from pip._vendor.retrying import retry # type: ignore
from pip._vendor.six import PY2, text_type
from pip._vendor.six.moves import input, map, zip_longest
from pip._vendor.six.moves import filter, filterfalse, input, map, zip_longest
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
@ -52,12 +54,13 @@ else:
if MYPY_CHECK_RUNNING:
from typing import (
Any, AnyStr, Container, Iterable, Iterator, List, Optional, Text,
Tuple, Union,
Any, AnyStr, Callable, Container, Iterable, Iterator, List, Optional,
Text, Tuple, TypeVar, Union,
)
from pip._vendor.pkg_resources import Distribution
VersionInfo = Tuple[int, int, int]
T = TypeVar("T")
__all__ = ['rmtree', 'display_path', 'backup_dir',
@ -131,7 +134,7 @@ def get_prog():
# Retry every half second for up to 3 seconds
@retry(stop_max_delay=3000, wait_fixed=500)
def rmtree(dir, ignore_errors=False):
# type: (str, bool) -> None
# type: (Text, bool) -> None
shutil.rmtree(dir, ignore_errors=ignore_errors,
onerror=rmtree_errorhandler)
@ -480,6 +483,57 @@ def get_installed_distributions(
]
def _search_distribution(req_name):
# type: (str) -> Optional[Distribution]
"""Find a distribution matching the ``req_name`` in the environment.
This searches from *all* distributions available in the environment, to
match the behavior of ``pkg_resources.get_distribution()``.
"""
# Canonicalize the name before searching in the list of
# installed distributions and also while creating the package
# dictionary to get the Distribution object
req_name = canonicalize_name(req_name)
packages = get_installed_distributions(
local_only=False,
skip=(),
include_editables=True,
editables_only=False,
user_only=False,
paths=None,
)
pkg_dict = {canonicalize_name(p.key): p for p in packages}
return pkg_dict.get(req_name)
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()``.
"""
# Search the distribution by looking through the working set
dist = _search_distribution(req_name)
# If distribution could not be found, call working_set.require
# to update the working set, and try to find the distribution
# again.
# This might happen for e.g. when you install a package
# twice, once using setup.py develop and again using setup.py install.
# Now when run pip uninstall twice, the package gets removed
# from the working set in the first uninstall, so we have to populate
# the working set again so that pip knows about it and the packages
# gets picked up and is successfully uninstalled the second time too.
if not dist:
try:
pkg_resources.working_set.require(req_name)
except pkg_resources.DistributionNotFound:
return None
return _search_distribution(req_name)
def egg_link_path(dist):
# type: (Distribution) -> Optional[str]
"""
@ -533,7 +587,7 @@ def dist_location(dist):
def write_output(msg, *args):
# type: (str, str) -> None
# type: (Any, Any) -> None
logger.info(msg, *args)
@ -541,14 +595,11 @@ class FakeFile(object):
"""Wrap a list of lines in an object with readline() to make
ConfigParser happy."""
def __init__(self, lines):
self._gen = (l for l in lines)
self._gen = iter(lines)
def readline(self):
try:
try:
return next(self._gen)
except NameError:
return self._gen.next()
return next(self._gen)
except StopIteration:
return ''
@ -603,26 +654,6 @@ def captured_stderr():
return captured_output('stderr')
class cached_property(object):
"""A property that is only computed once per instance and then replaces
itself with an ordinary attribute. Deleting the attribute resets the
property.
Source: https://github.com/bottlepy/bottle/blob/0.11.5/bottle.py#L175
"""
def __init__(self, func):
self.__doc__ = getattr(func, '__doc__')
self.func = func
def __get__(self, obj, cls):
if obj is None:
# We're being accessed from the class itself, not from an object
return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value
def get_installed_version(dist_name, working_set=None):
"""Get the installed version of dist_name avoiding pkg_resources cache"""
# Create a requirement that we'll look for inside of setuptools.
@ -876,7 +907,7 @@ def is_console_interactive():
def hash_file(path, blocksize=1 << 20):
# type: (str, int) -> Tuple[Any, int]
# type: (Text, int) -> Tuple[Any, int]
"""Return (hash, length) for path using hashlib.sha256()
"""
@ -911,3 +942,18 @@ def pairwise(iterable):
"""
iterable = iter(iterable)
return zip_longest(iterable, iterable)
def partition(
pred, # type: Callable[[T], bool]
iterable, # type: Iterable[T]
):
# type: (...) -> Tuple[Iterable[T], Iterable[T]]
"""
Use a predicate to partition entries into false entries and true entries,
like
partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9
"""
t1, t2 = tee(iterable)
return filterfalse(pred, t1), filter(pred, t2)

View File

@ -10,6 +10,8 @@ class KeyBasedCompareMixin(object):
"""Provides comparison capabilities that is based on a key
"""
__slots__ = ['_compare_key', '_defining_class']
def __init__(self, key, defining_class):
self._compare_key = key
self._defining_class = defining_class

View File

@ -0,0 +1,107 @@
"""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.dummy import Pool as ThreadPool
from pip._vendor.requests.adapters import DEFAULT_POOLSIZE
from pip._vendor.six import PY2
from pip._vendor.six.moves import map
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Callable, Iterable, Iterator, Union, TypeVar
from multiprocessing import pool
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 or PY2:
map_multiprocess = map_multithread = _map_fallback
else:
map_multiprocess = _map_multiprocess
map_multithread = _map_multithread

View File

@ -1,6 +1,3 @@
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import logging
@ -188,6 +185,8 @@ def call_subprocess(
stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, cwd=cwd, env=env,
)
assert proc.stdin
assert proc.stdout
proc.stdin.close()
except Exception as exc:
if log_failed_cmd:
@ -208,6 +207,7 @@ def call_subprocess(
log_subprocess(line)
# Update the spinner.
if use_spinner:
assert spinner
spinner.spin()
try:
proc.wait()
@ -218,6 +218,7 @@ def call_subprocess(
proc.returncode and proc.returncode not in extra_ok_returncodes
)
if use_spinner:
assert spinner
if proc_had_error:
spinner.finish("error")
else:
@ -241,8 +242,10 @@ def call_subprocess(
raise InstallationError(exc_msg)
elif on_returncode == 'warn':
subprocess_logger.warning(
'Command "{}" had error code {} in {}'.format(
command_desc, proc.returncode, cwd)
'Command "%s" had error code %s in %s',
command_desc,
proc.returncode,
cwd,
)
elif on_returncode == 'ignore':
pass

View File

@ -8,6 +8,7 @@ import tempfile
from contextlib import contextmanager
from pip._vendor.contextlib2 import ExitStack
from pip._vendor.six import ensure_text
from pip._internal.utils.misc import enum, rmtree
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@ -184,7 +185,7 @@ class TempDirectory(object):
path = os.path.realpath(
tempfile.mkdtemp(prefix="pip-{}-".format(kind))
)
logger.debug("Created temporary directory: {}".format(path))
logger.debug("Created temporary directory: %s", path)
return path
def cleanup(self):
@ -193,7 +194,9 @@ class TempDirectory(object):
"""
self._deleted = True
if os.path.exists(self._path):
rmtree(self._path)
# Make sure to pass unicode on Python 2 to make the contents also
# use unicode, ensuring non-ASCII names and can be represented.
rmtree(ensure_text(self._path))
class AdjacentTempDirectory(TempDirectory):
@ -267,5 +270,5 @@ class AdjacentTempDirectory(TempDirectory):
tempfile.mkdtemp(prefix="pip-{}-".format(kind))
)
logger.debug("Created temporary directory: {}".format(path))
logger.debug("Created temporary directory: %s", path)
return path

View File

@ -1,10 +1,6 @@
"""Utilities related archives.
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import logging
@ -26,6 +22,7 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Iterable, List, Optional, Text, Union
from zipfile import ZipInfo
logger = logging.getLogger(__name__)
@ -48,6 +45,7 @@ except ImportError:
def current_umask():
# type: () -> int
"""Get the current umask which involves having to set it temporarily."""
mask = os.umask(0)
os.umask(mask)
@ -98,6 +96,23 @@ def is_within_directory(directory, target):
return prefix == abs_directory
def set_extracted_file_to_default_mode_plus_executable(path):
# type: (Union[str, Text]) -> None
"""
Make file present at path have execute for user/group/world
(chmod +x) is no-op on windows per python docs
"""
os.chmod(path, (0o777 & ~current_umask() | 0o111))
def zip_item_is_executable(info):
# type: (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
"""
@ -139,13 +154,8 @@ def unzip_file(filename, location, flatten=True):
shutil.copyfileobj(fp, destfp)
finally:
fp.close()
mode = info.external_attr >> 16
# if mode and regular file and any execute permissions for
# user/group/world?
if mode and stat.S_ISREG(mode) and mode & 0o111:
# make dest file have execute for user/group/world
# (chmod +x) no-op on windows per python docs
os.chmod(fn, (0o777 - current_umask() | 0o111))
if zip_item_is_executable(info):
set_extracted_file_to_default_mode_plus_executable(fn)
finally:
zipfp.close()
@ -219,6 +229,7 @@ def untar_file(filename, location):
)
continue
ensure_dir(os.path.dirname(path))
assert fp is not None
with open(path, 'wb') as destfp:
shutil.copyfileobj(fp, destfp)
fp.close()
@ -227,9 +238,7 @@ def untar_file(filename, location):
tar.utime(member, path) # type: ignore
# member have any execute permissions for user/group/world?
if member.mode & 0o111:
# make dest file have execute for user/group/world
# no-op on windows per python docs
os.chmod(path, (0o777 - current_umask() | 0o111))
set_extracted_file_to_default_mode_plus_executable(path)
finally:
tar.close()

View File

@ -1,5 +1,6 @@
from __future__ import absolute_import
import io
import logging
import os
import re
@ -51,7 +52,9 @@ def _get_pyvenv_cfg_lines():
"""
pyvenv_cfg_file = os.path.join(sys.prefix, 'pyvenv.cfg')
try:
with open(pyvenv_cfg_file) as f:
# Although PEP 405 does not specify, the built-in venv module always
# writes with UTF-8. (pypa/pip#8717)
with io.open(pyvenv_cfg_file, encoding='utf-8') as f:
return f.read().splitlines() # avoids trailing newlines
except IOError:
return None

View File

@ -121,7 +121,7 @@ def wheel_dist_info_dir(source, name):
it doesn't match the provided name.
"""
# Zip file path separators must be /
subdirs = list(set(p.split("/")[0] for p in source.namelist()))
subdirs = set(p.split("/", 1)[0] for p in source.namelist())
info_dirs = [s for s in subdirs if s.endswith('.dist-info')]

View File

@ -54,8 +54,7 @@ class Bazaar(VersionControl):
url, rev_options = self.get_url_rev_options(url)
self.run_command(
make_command('export', location, url, rev_options.to_args()),
show_stdout=False,
make_command('export', location, url, rev_options.to_args())
)
def fetch_new(self, dest, url, rev_options):
@ -92,7 +91,7 @@ class Bazaar(VersionControl):
@classmethod
def get_remote_url(cls, location):
urls = cls.run_command(['info'], show_stdout=False, cwd=location)
urls = cls.run_command(['info'], cwd=location)
for line in urls.splitlines():
line = line.strip()
for x in ('checkout of branch: ',
@ -107,7 +106,7 @@ class Bazaar(VersionControl):
@classmethod
def get_revision(cls, location):
revision = cls.run_command(
['revno'], show_stdout=False, cwd=location,
['revno'], cwd=location,
)
return revision.splitlines()[-1]

View File

@ -11,7 +11,7 @@ from pip._vendor.packaging.version import parse as parse_version
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._vendor.six.moves.urllib import request as urllib_request
from pip._internal.exceptions import BadCommand, InstallationError
from pip._internal.exceptions import BadCommand, SubProcessError
from pip._internal.utils.misc import display_path, hide_url
from pip._internal.utils.subprocess import make_command
from pip._internal.utils.temp_dir import TempDirectory
@ -78,7 +78,7 @@ class Git(VersionControl):
def get_git_version(self):
VERSION_PFX = 'git version '
version = self.run_command(['version'], show_stdout=False)
version = self.run_command(['version'])
if version.startswith(VERSION_PFX):
version = version[len(VERSION_PFX):].split()[0]
else:
@ -101,7 +101,7 @@ class Git(VersionControl):
# and to suppress the message to stderr.
args = ['symbolic-ref', '-q', 'HEAD']
output = cls.run_command(
args, extra_ok_returncodes=(1, ), show_stdout=False, cwd=location,
args, extra_ok_returncodes=(1, ), cwd=location,
)
ref = output.strip()
@ -120,7 +120,7 @@ class Git(VersionControl):
self.unpack(temp_dir.path, url=url)
self.run_command(
['checkout-index', '-a', '-f', '--prefix', location],
show_stdout=False, cwd=temp_dir.path
cwd=temp_dir.path
)
@classmethod
@ -134,8 +134,13 @@ class Git(VersionControl):
rev: the revision name.
"""
# Pass rev to pre-filter the list.
output = cls.run_command(['show-ref', rev], cwd=dest,
show_stdout=False, on_returncode='ignore')
output = ''
try:
output = cls.run_command(['show-ref', rev], cwd=dest)
except SubProcessError:
pass
refs = {}
for line in output.strip().splitlines():
try:
@ -286,7 +291,7 @@ class Git(VersionControl):
# 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, ), show_stdout=False, cwd=location,
extra_ok_returncodes=(1, ), cwd=location,
)
remotes = stdout.splitlines()
try:
@ -306,7 +311,7 @@ class Git(VersionControl):
if rev is None:
rev = 'HEAD'
current_rev = cls.run_command(
['rev-parse', rev], show_stdout=False, cwd=location,
['rev-parse', rev], cwd=location,
)
return current_rev.strip()
@ -319,7 +324,7 @@ class Git(VersionControl):
# find the repo root
git_dir = cls.run_command(
['rev-parse', '--git-dir'],
show_stdout=False, cwd=location).strip()
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, '..'))
@ -378,15 +383,13 @@ class Git(VersionControl):
r = cls.run_command(
['rev-parse', '--show-toplevel'],
cwd=location,
show_stdout=False,
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)
return None
except InstallationError:
except SubProcessError:
return None
return os.path.normpath(r.rstrip('\r\n'))

View File

@ -8,7 +8,7 @@ import os
from pip._vendor.six.moves import configparser
from pip._internal.exceptions import BadCommand, InstallationError
from pip._internal.exceptions import BadCommand, SubProcessError
from pip._internal.utils.misc import display_path
from pip._internal.utils.subprocess import make_command
from pip._internal.utils.temp_dir import TempDirectory
@ -47,7 +47,7 @@ class Mercurial(VersionControl):
self.unpack(temp_dir.path, url=url)
self.run_command(
['archive', location], show_stdout=False, cwd=temp_dir.path
['archive', location], cwd=temp_dir.path
)
def fetch_new(self, dest, url, rev_options):
@ -92,7 +92,7 @@ class Mercurial(VersionControl):
def get_remote_url(cls, location):
url = cls.run_command(
['showconfig', 'paths.default'],
show_stdout=False, cwd=location).strip()
cwd=location).strip()
if cls._is_local_repository(url):
url = path_to_url(url)
return url.strip()
@ -103,8 +103,7 @@ class Mercurial(VersionControl):
Return the repository-local changeset revision number, as an integer.
"""
current_revision = cls.run_command(
['parents', '--template={rev}'],
show_stdout=False, cwd=location).strip()
['parents', '--template={rev}'], cwd=location).strip()
return current_revision
@classmethod
@ -115,7 +114,7 @@ class Mercurial(VersionControl):
"""
current_rev_hash = cls.run_command(
['parents', '--template={node}'],
show_stdout=False, cwd=location).strip()
cwd=location).strip()
return current_rev_hash
@classmethod
@ -131,7 +130,7 @@ class Mercurial(VersionControl):
"""
# find the repo root
repo_root = cls.run_command(
['root'], show_stdout=False, cwd=location).strip()
['root'], 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_setup_from_repo_root(location, repo_root)
@ -145,15 +144,13 @@ class Mercurial(VersionControl):
r = cls.run_command(
['root'],
cwd=location,
show_stdout=False,
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)
return None
except InstallationError:
except SubProcessError:
return None
return os.path.normpath(r.rstrip('\r\n'))

View File

@ -56,7 +56,7 @@ class Subversion(VersionControl):
# Note: taken from setuptools.command.egg_info
revision = 0
for base, dirs, files in os.walk(location):
for base, dirs, _ in os.walk(location):
if cls.dirname not in dirs:
dirs[:] = []
continue # no sense walking uncontrolled subdirs
@ -132,7 +132,7 @@ class Subversion(VersionControl):
@classmethod
def _get_svn_url_rev(cls, location):
from pip._internal.exceptions import InstallationError
from pip._internal.exceptions import SubProcessError
entries_path = os.path.join(location, cls.dirname, 'entries')
if os.path.exists(entries_path):
@ -165,13 +165,12 @@ class Subversion(VersionControl):
# are only potentially needed for remote server requests.
xml = cls.run_command(
['info', '--xml', location],
show_stdout=False,
)
url = _svn_info_xml_url_re.search(xml).group(1)
revs = [
int(m.group(1)) for m in _svn_info_xml_rev_re.finditer(xml)
]
except InstallationError:
except SubProcessError:
url, revs = None, []
if revs:
@ -214,13 +213,16 @@ class Subversion(VersionControl):
# compiled Feb 25 2019, 14:20:39 on x86_64-apple-darwin17.0.0
# svn, version 1.7.14 (r1542130)
# 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)
version = self.run_command(['--version'])
if not version.startswith(version_prefix):
return ()
version = version[len(version_prefix):].split()[0]
version_list = version.split('.')
version_list = version.partition('-')[0].split('.')
try:
parsed_version = tuple(map(int, version_list))
except ValueError:
@ -297,7 +299,7 @@ class Subversion(VersionControl):
'export', self.get_remote_call_options(),
rev_options.to_args(), url, location,
)
self.run_command(cmd_args, show_stdout=False)
self.run_command(cmd_args)
def fetch_new(self, dest, url, rev_options):
# type: (str, HiddenText, RevOptions) -> None

View File

@ -6,13 +6,19 @@ import errno
import logging
import os
import shutil
import subprocess
import sys
from pip._vendor import pkg_resources
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._internal.exceptions import BadCommand, InstallationError
from pip._internal.utils.compat import samefile
from pip._internal.exceptions import (
BadCommand,
InstallationError,
SubProcessError,
)
from pip._internal.utils.compat import console_to_str, samefile
from pip._internal.utils.logging import subprocess_logger
from pip._internal.utils.misc import (
ask_path_exists,
backup_dir,
@ -21,16 +27,20 @@ from pip._internal.utils.misc import (
hide_value,
rmtree,
)
from pip._internal.utils.subprocess import call_subprocess, make_command
from pip._internal.utils.subprocess import (
format_command_args,
make_command,
make_subprocess_output_error,
reveal_command_args,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.urls import get_url_scheme
if MYPY_CHECK_RUNNING:
from typing import (
Any, Dict, Iterable, Iterator, List, Mapping, Optional, Text, Tuple,
Type, Union
Dict, Iterable, Iterator, List, Optional, Text, Tuple,
Type, Union, Mapping, Any
)
from pip._internal.cli.spinners import SpinnerInterface
from pip._internal.utils.misc import HiddenText
from pip._internal.utils.subprocess import CommandArgs
@ -71,6 +81,92 @@ def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None):
return req
def call_subprocess(
cmd, # type: Union[List[str], CommandArgs]
cwd=None, # type: Optional[str]
extra_environ=None, # type: Optional[Mapping[str, Any]]
extra_ok_returncodes=None, # type: Optional[Iterable[int]]
log_failed_cmd=True # type: Optional[bool]
):
# type: (...) -> Text
"""
Args:
extra_ok_returncodes: an iterable of integer return codes that are
acceptable, in addition to 0. Defaults to None, which means [].
log_failed_cmd: if false, failed commands are not logged,
only raised.
"""
if extra_ok_returncodes is None:
extra_ok_returncodes = []
# log the subprocess output at DEBUG level.
log_subprocess = subprocess_logger.debug
env = os.environ.copy()
if extra_environ:
env.update(extra_environ)
# Whether the subprocess will be visible in the console.
showing_subprocess = True
command_desc = format_command_args(cmd)
try:
proc = subprocess.Popen(
# Convert HiddenText objects to the underlying str.
reveal_command_args(cmd),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=cwd
)
if proc.stdin:
proc.stdin.close()
except Exception as exc:
if log_failed_cmd:
subprocess_logger.critical(
"Error %s while executing command %s", exc, command_desc,
)
raise
all_output = []
while True:
# The "line" value is a unicode string in Python 2.
line = None
if proc.stdout:
line = console_to_str(proc.stdout.readline())
if not line:
break
line = line.rstrip()
all_output.append(line + '\n')
# Show the line immediately.
log_subprocess(line)
try:
proc.wait()
finally:
if proc.stdout:
proc.stdout.close()
proc_had_error = (
proc.returncode and proc.returncode not in extra_ok_returncodes
)
if proc_had_error:
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,
)
subprocess_logger.error(msg)
exc_msg = (
'Command errored out with exit status {}: {} '
'Check the logs for full command output.'
).format(proc.returncode, command_desc)
raise SubProcessError(exc_msg)
return ''.join(all_output)
def find_path_to_setup_from_repo_root(location, repo_root):
# type: (str, str) -> Optional[str]
"""
@ -659,13 +755,9 @@ class VersionControl(object):
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]
extra_ok_returncodes=None, # type: Optional[Iterable[int]]
log_failed_cmd=True # type: bool
):
# type: (...) -> Text
@ -676,13 +768,9 @@ class VersionControl(object):
"""
cmd = make_command(cls.name, *cmd)
try:
return call_subprocess(cmd, show_stdout, cwd,
on_returncode=on_returncode,
extra_ok_returncodes=extra_ok_returncodes,
command_desc=command_desc,
return call_subprocess(cmd, cwd,
extra_environ=extra_environ,
unset_environ=cls.unset_environ,
spinner=spinner,
extra_ok_returncodes=extra_ok_returncodes,
log_failed_cmd=log_failed_cmd)
except OSError as e:
# errno.ENOENT = no such file or directory

View File

@ -1,9 +1,6 @@
"""Orchestrator for building wheels from InstallRequirements.
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import logging
import os.path
import re
@ -23,7 +20,7 @@ from pip._internal.vcs import vcs
if MYPY_CHECK_RUNNING:
from typing import (
Any, Callable, Iterable, List, Optional, Pattern, Tuple,
Any, Callable, Iterable, List, Optional, Tuple,
)
from pip._internal.cache import WheelCache
@ -34,10 +31,11 @@ if MYPY_CHECK_RUNNING:
logger = logging.getLogger(__name__)
_egg_info_re = re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.IGNORECASE)
def _contains_egg_info(
s, _egg_info_re=re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.I)):
# type: (str, Pattern[str]) -> bool
def _contains_egg_info(s):
# type: (str) -> bool
"""Determine whether the string looks like an egg_info.
:param s: The string to parse. E.g. foo-2.1
@ -82,7 +80,7 @@ def _should_build(
if not req.use_pep517 and not is_wheel_installed():
# we don't build legacy requirements if wheel is not installed
logger.info(
"Using legacy setup.py install for %s, "
"Using legacy 'setup.py install' for %s, "
"since package 'wheel' is not installed.", req.name,
)
return False
@ -118,11 +116,8 @@ def _should_cache(
wheel cache, assuming the wheel cache is available, and _should_build()
has determined a wheel needs to be built.
"""
if not should_build_for_install_command(
req, check_binary_allowed=_always_true
):
# never cache if pip install would not have built
# (editable mode, etc)
if req.editable or not req.source_dir:
# never cache editable requirements
return False
if req.link and req.link.is_vcs:
@ -136,6 +131,7 @@ def _should_cache(
return True
return False
assert req.link
base, ext = req.link.splitext()
if _contains_egg_info(base):
return True
@ -153,6 +149,7 @@ def _get_cache_dir(
wheel need to be stored.
"""
cache_available = bool(wheel_cache.cache_dir)
assert req.link
if cache_available and _should_cache(req):
cache_dir = wheel_cache.get_path_for_link(req.link)
else:
@ -200,7 +197,9 @@ def _build_one_inside_env(
):
# type: (...) -> Optional[str]
with TempDirectory(kind="wheel") as temp_dir:
assert req.name
if req.use_pep517:
assert req.metadata_directory
wheel_path = build_wheel_pep517(
name=req.name,
backend=req.pep517_backend,
@ -275,7 +274,7 @@ def build(
# Build the wheels.
logger.info(
'Building wheels for collected packages: %s',
', '.join(req.name for req in requirements),
', '.join(req.name for req in requirements), # type: ignore
)
with indent_log():
@ -298,12 +297,12 @@ def build(
if build_successes:
logger.info(
'Successfully built %s',
' '.join([req.name for req in build_successes]),
' '.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]),
' '.join([req.name for req in build_failures]), # type: ignore
)
# Return a list of requirements that failed to build
return build_successes, build_failures

View File

@ -32,15 +32,11 @@ def vendored(modulename):
try:
__import__(modulename, globals(), locals(), level=0)
except ImportError:
# We can just silently allow import failures to pass here. If we
# got to this point it means that ``import pip._vendor.whatever``
# failed and so did ``import whatever``. Since we're importing this
# upfront in an attempt to alias imports, not erroring here will
# just mean we get a regular import error whenever pip *actually*
# tries to import one of these modules to use it, which actually
# gives us a better error message than we would have otherwise
# gotten.
pass
# This error used to be silenced in earlier variants of this file, to instead
# raise the error when pip actually tries to use the missing module.
# Based on inputs in #5354, this was changed to explicitly raise the error.
# Re-raising the exception without modifying it is an intentional choice.
raise
else:
sys.modules[vendored_name] = sys.modules[modulename]
base, head = vendored_name.rsplit(".", 1)

View File

@ -13,8 +13,8 @@ See <http://github.com/ActiveState/appdirs> for details and usage.
# - 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_info__ = (1, 4, 3)
__version__ = '.'.join(map(str, __version_info__))
__version__ = "1.4.4"
__version_info__ = tuple(int(segment) for segment in __version__.split("."))
import sys

View File

@ -1,3 +1,3 @@
from .core import contents, where
__version__ = "2020.04.05.1"
__version__ = "2020.06.20"

View File

@ -58,38 +58,6 @@ AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----END CERTIFICATE-----
# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only
# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only
# Label: "Verisign Class 3 Public Primary Certification Authority - G3"
# Serial: 206684696279472310254277870180966723415
# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09
# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6
# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44
-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----
# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
# Label: "Entrust.net Premium 2048 Secure Server CA"
@ -152,39 +120,6 @@ ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----
# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network
# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network
# Label: "AddTrust External Root"
# Serial: 1
# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f
# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68
# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----
# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
# Label: "Entrust Root Certification Authority"
@ -1499,47 +1434,6 @@ uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----
# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden
# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden
# Label: "Staat der Nederlanden Root CA - G2"
# Serial: 10000012
# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a
# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16
# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f
-----BEGIN CERTIFICATE-----
MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX
DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291
qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp
uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU
Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE
pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp
5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M
UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN
GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy
5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv
6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK
eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6
B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/
BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov
L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG
SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS
CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen
5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897
IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK
gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL
+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL
vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm
bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk
N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC
Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z
ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==
-----END CERTIFICATE-----
# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post
# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post
# Label: "Hongkong Post Root CA 1"
@ -3788,47 +3682,6 @@ CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----
# Issuer: CN=LuxTrust Global Root 2 O=LuxTrust S.A.
# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A.
# Label: "LuxTrust Global Root 2"
# Serial: 59914338225734147123941058376788110305822489521
# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c
# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f
# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5
-----BEGIN CERTIFICATE-----
MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQEL
BQAwRjELMAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNV
BAMMFkx1eFRydXN0IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUw
MzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5B
LjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcN
AQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wmKb3F
ibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTem
hfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1
EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn
Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4
zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ
96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5m
j5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4g
DEa/a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+
8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2j
X5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmH
hFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGByuB
KwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0
Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT
+Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQEL
BQADggIBAGoZFO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9
BzZAcg4atmpZ1gDlaCDdLnINH2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTO
jFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9
loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIuZY+kt9J/Z93I055c
qqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWAVWe+
2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/
JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre
zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQf
LSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+
x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6
oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr
-----END CERTIFICATE-----
# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM
# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM
# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1"
@ -4639,3 +4492,129 @@ IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk
5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY
n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw==
-----END CERTIFICATE-----
# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation
# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation
# Label: "Microsoft ECC Root Certificate Authority 2017"
# Serial: 136839042543790627607696632466672567020
# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67
# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5
# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02
-----BEGIN CERTIFICATE-----
MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw
CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD
VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw
MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV
UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy
b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR
ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb
hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3
FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV
L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB
iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
-----END CERTIFICATE-----
# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation
# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation
# Label: "Microsoft RSA Root Certificate Authority 2017"
# Serial: 40975477897264996090493496164228220339
# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47
# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74
# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0
-----BEGIN CERTIFICATE-----
MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG
EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N
aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ
Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0
ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1
HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm
gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ
jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc
aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG
YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6
W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K
UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH
+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q
W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC
NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC
LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC
gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6
tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh
SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2
TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3
pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR
xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp
GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9
dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN
AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB
RA+GsCyRxj3qrg+E
-----END CERTIFICATE-----
# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd.
# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd.
# Label: "e-Szigno Root CA 2017"
# Serial: 411379200276854331539784714
# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98
# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1
# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99
-----BEGIN CERTIFICATE-----
MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV
BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk
LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv
b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ
BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg
THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v
IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv
xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H
Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB
eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo
jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ
+efcMQ==
-----END CERTIFICATE-----
# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2
# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2
# Label: "certSIGN Root CA G2"
# Serial: 313609486401300475190
# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7
# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32
# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV
BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g
Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ
BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ
R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF
dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw
vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ
uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp
n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs
cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW
xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P
rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF
DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx
DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy
LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C
eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ
d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq
kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC
b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl
qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0
OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c
NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk
ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO
pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj
03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk
PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE
1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX
QRBdJ3NghVdJIgc=
-----END CERTIFICATE-----

View File

@ -9,7 +9,36 @@ This module returns the installation location of cacert.pem or its contents.
import os
try:
from importlib.resources import read_text
from importlib.resources import path as get_path, read_text
_CACERT_CTX = None
_CACERT_PATH = None
def where():
# This is slightly terrible, but we want to delay extracting the file
# in cases where we're inside of a zipimport situation until someone
# actually calls where(), but we don't want to re-extract the file
# on every call of where(), so we'll do it once then store it in a
# global variable.
global _CACERT_CTX
global _CACERT_PATH
if _CACERT_PATH is None:
# This is slightly janky, the importlib.resources API wants you to
# manage the cleanup of this file, so it doesn't actually return a
# path, it returns a context manager that will give you the path
# when you enter it and will do any cleanup when you leave it. In
# the common case of not needing a temporary file, it will just
# return the file system location and the __exit__() is a no-op.
#
# We also have to hold onto the actual context manager, because
# it will do the cleanup whenever it gets garbage collected, so
# we will also store that at the global level as well.
_CACERT_CTX = get_path("pip._vendor.certifi", "cacert.pem")
_CACERT_PATH = str(_CACERT_CTX.__enter__())
return _CACERT_PATH
except ImportError:
# This fallback will work for Python versions prior to 3.7 that lack the
# importlib.resources module but relies on the existing `where` function
@ -19,11 +48,12 @@ except ImportError:
with open(where(), "r", encoding=encoding) as data:
return data.read()
# If we don't have importlib.resources, then we will just do the old logic
# of assuming we're on the filesystem and munge the path directly.
def where():
f = os.path.dirname(__file__)
def where():
f = os.path.dirname(__file__)
return os.path.join(f, "cacert.pem")
return os.path.join(f, "cacert.pem")
def contents():

View File

@ -6,7 +6,7 @@
#
import logging
__version__ = '0.3.0'
__version__ = '0.3.1'
class DistlibException(Exception):
pass

View File

@ -14,7 +14,10 @@ import sys
import stat
from os.path import abspath
import fnmatch
import collections
try:
from collections.abc import Callable
except ImportError:
from collections import Callable
import errno
from . import tarfile
@ -528,7 +531,7 @@ def register_archive_format(name, function, extra_args=None, description=''):
"""
if extra_args is None:
extra_args = []
if not isinstance(function, collections.Callable):
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')
@ -621,7 +624,7 @@ def _check_unpack_options(extensions, function, extra_args):
raise RegistryError(msg % (extension,
existing_extensions[extension]))
if not isinstance(function, collections.Callable):
if not isinstance(function, Callable):
raise TypeError('The registered function must be a callable')

View File

@ -319,7 +319,7 @@ except ImportError: # pragma: no cover
try:
callable = callable
except NameError: # pragma: no cover
from collections import Callable
from collections.abc import Callable
def callable(obj):
return isinstance(obj, Callable)

View File

@ -550,7 +550,7 @@ class InstalledDistribution(BaseInstalledDistribution):
r = finder.find(WHEEL_METADATA_FILENAME)
# Temporary - for legacy support
if r is None:
r = finder.find('METADATA')
r = finder.find(LEGACY_METADATA_FILENAME)
if r is None:
raise ValueError('no %s found in %s' % (METADATA_FILENAME,
path))

View File

@ -5,7 +5,7 @@
#
"""Implementation of the Metadata for Python packages PEPs.
Supports all metadata formats (1.0, 1.1, 1.2, and 2.0 experimental).
Supports all metadata formats (1.0, 1.1, 1.2, 1.3/2.1 and withdrawn 2.0).
"""
from __future__ import unicode_literals
@ -194,38 +194,12 @@ def _best_version(fields):
return '2.0'
# This follows the rules about transforming keys as described in
# https://www.python.org/dev/peps/pep-0566/#id17
_ATTR2FIELD = {
'metadata_version': 'Metadata-Version',
'name': 'Name',
'version': 'Version',
'platform': 'Platform',
'supported_platform': 'Supported-Platform',
'summary': 'Summary',
'description': 'Description',
'keywords': 'Keywords',
'home_page': 'Home-page',
'author': 'Author',
'author_email': 'Author-email',
'maintainer': 'Maintainer',
'maintainer_email': 'Maintainer-email',
'license': 'License',
'classifier': 'Classifier',
'download_url': 'Download-URL',
'obsoletes_dist': 'Obsoletes-Dist',
'provides_dist': 'Provides-Dist',
'requires_dist': 'Requires-Dist',
'setup_requires_dist': 'Setup-Requires-Dist',
'requires_python': 'Requires-Python',
'requires_external': 'Requires-External',
'requires': 'Requires',
'provides': 'Provides',
'obsoletes': 'Obsoletes',
'project_url': 'Project-URL',
'private_version': 'Private-Version',
'obsoleted_by': 'Obsoleted-By',
'extension': 'Extension',
'provides_extra': 'Provides-Extra',
name.lower().replace("-", "_"): name for name in _ALL_FIELDS
}
_FIELD2ATTR = {field: attr for attr, field in _ATTR2FIELD.items()}
_PREDICATE_FIELDS = ('Requires-Dist', 'Obsoletes-Dist', 'Provides-Dist')
_VERSIONS_FIELDS = ('Requires-Python',)
@ -262,7 +236,7 @@ def _get_name_and_version(name, version, for_filename=False):
class LegacyMetadata(object):
"""The legacy metadata of a release.
Supports versions 1.0, 1.1 and 1.2 (auto-detected). You can
Supports versions 1.0, 1.1, 1.2, 2.0 and 1.3/2.1 (auto-detected). You can
instantiate the class with one of these arguments (or none):
- *path*, the path to a metadata file
- *fileobj* give a file-like object with metadata as content
@ -381,6 +355,11 @@ class LegacyMetadata(object):
value = msg[field]
if value is not None and value != 'UNKNOWN':
self.set(field, value)
# PEP 566 specifies that the body be used for the description, if
# available
body = msg.get_payload()
self["Description"] = body if body else self["Description"]
# logger.debug('Attempting to set metadata for %s', self)
# self.set_metadata_version()
@ -567,57 +546,21 @@ class LegacyMetadata(object):
Field names will be converted to use the underscore-lowercase style
instead of hyphen-mixed case (i.e. home_page instead of Home-page).
This is as per https://www.python.org/dev/peps/pep-0566/#id17.
"""
self.set_metadata_version()
mapping_1_0 = (
('metadata_version', 'Metadata-Version'),
('name', 'Name'),
('version', 'Version'),
('summary', 'Summary'),
('home_page', 'Home-page'),
('author', 'Author'),
('author_email', 'Author-email'),
('license', 'License'),
('description', 'Description'),
('keywords', 'Keywords'),
('platform', 'Platform'),
('classifiers', 'Classifier'),
('download_url', 'Download-URL'),
)
fields = _version2fieldlist(self['Metadata-Version'])
data = {}
for key, field_name in mapping_1_0:
for field_name in fields:
if not skip_missing or field_name in self._fields:
data[key] = self[field_name]
if self['Metadata-Version'] == '1.2':
mapping_1_2 = (
('requires_dist', 'Requires-Dist'),
('requires_python', 'Requires-Python'),
('requires_external', 'Requires-External'),
('provides_dist', 'Provides-Dist'),
('obsoletes_dist', 'Obsoletes-Dist'),
('project_url', 'Project-URL'),
('maintainer', 'Maintainer'),
('maintainer_email', 'Maintainer-email'),
)
for key, field_name in mapping_1_2:
if not skip_missing or field_name in self._fields:
if key != 'project_url':
data[key] = self[field_name]
else:
data[key] = [','.join(u) for u in self[field_name]]
elif self['Metadata-Version'] == '1.1':
mapping_1_1 = (
('provides', 'Provides'),
('requires', 'Requires'),
('obsoletes', 'Obsoletes'),
)
for key, field_name in mapping_1_1:
if not skip_missing or field_name in self._fields:
key = _FIELD2ATTR[field_name]
if key != 'project_url':
data[key] = self[field_name]
else:
data[key] = [','.join(u) for u in self[field_name]]
return data
@ -1003,10 +946,14 @@ class Metadata(object):
LEGACY_MAPPING = {
'name': 'Name',
'version': 'Version',
'license': 'License',
('extensions', 'python.details', 'license'): 'License',
'summary': 'Summary',
'description': 'Description',
'classifiers': 'Classifier',
('extensions', 'python.project', 'project_urls', 'Home'): 'Home-page',
('extensions', 'python.project', 'contacts', 0, 'name'): 'Author',
('extensions', 'python.project', 'contacts', 0, 'email'): 'Author-email',
'source_url': 'Download-URL',
('extensions', 'python.details', 'classifiers'): 'Classifier',
}
def _to_legacy(self):
@ -1034,16 +981,29 @@ class Metadata(object):
assert self._data and not self._legacy
result = LegacyMetadata()
nmd = self._data
# import pdb; pdb.set_trace()
for nk, ok in self.LEGACY_MAPPING.items():
if nk in nmd:
result[ok] = nmd[nk]
if not isinstance(nk, tuple):
if nk in nmd:
result[ok] = nmd[nk]
else:
d = nmd
found = True
for k in nk:
try:
d = d[k]
except (KeyError, IndexError):
found = False
break
if found:
result[ok] = d
r1 = process_entries(self.run_requires + self.meta_requires)
r2 = process_entries(self.build_requires + self.dev_requires)
if self.extras:
result['Provides-Extra'] = sorted(self.extras)
result['Requires-Dist'] = sorted(r1)
result['Setup-Requires-Dist'] = sorted(r2)
# TODO: other fields such as contacts
# TODO: any other fields wanted
return result
def write(self, path=None, fileobj=None, legacy=False, skip_unknown=True):

View File

@ -48,7 +48,7 @@ if __name__ == '__main__':
'''
def _enquote_executable(executable):
def enquote_executable(executable):
if ' ' in executable:
# make sure we quote only the executable in case of env
# for example /usr/bin/env "/dir with spaces/bin/jython"
@ -63,6 +63,8 @@ def _enquote_executable(executable):
executable = '"%s"' % executable
return executable
# Keep the old name around (for now), as there is at least one project using it!
_enquote_executable = enquote_executable
class ScriptMaker(object):
"""
@ -88,6 +90,7 @@ class ScriptMaker(object):
self._is_nt = os.name == 'nt' or (
os.name == 'java' and os._name == 'nt')
self.version_info = sys.version_info
def _get_alternate_executable(self, executable, options):
if options.get('gui', False) and self._is_nt: # pragma: no cover
@ -185,7 +188,7 @@ class ScriptMaker(object):
# If the user didn't specify an executable, it may be necessary to
# cater for executable paths with spaces (not uncommon on Windows)
if enquote:
executable = _enquote_executable(executable)
executable = enquote_executable(executable)
# Issue #51: don't use fsencode, since we later try to
# check that the shebang is decodable using utf-8.
executable = executable.encode('utf-8')
@ -293,10 +296,10 @@ class ScriptMaker(object):
if '' in self.variants:
scriptnames.add(name)
if 'X' in self.variants:
scriptnames.add('%s%s' % (name, sys.version_info[0]))
scriptnames.add('%s%s' % (name, self.version_info[0]))
if 'X.Y' in self.variants:
scriptnames.add('%s-%s.%s' % (name, sys.version_info[0],
sys.version_info[1]))
scriptnames.add('%s-%s.%s' % (name, self.version_info[0],
self.version_info[1]))
if options and options.get('gui', False):
ext = 'pyw'
else:

Some files were not shown because too many files have changed in this diff Show More