Updated venv
parent
aa3ee1a510
commit
7df5ee5baf
|
@ -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/
|
|
@ -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,,
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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__()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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+)_(.+)')
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')]
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
|
@ -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'))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
from .core import contents, where
|
||||
|
||||
__version__ = "2020.04.05.1"
|
||||
__version__ = "2020.06.20"
|
||||
|
|
|
@ -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-----
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#
|
||||
import logging
|
||||
|
||||
__version__ = '0.3.0'
|
||||
__version__ = '0.3.1'
|
||||
|
||||
class DistlibException(Exception):
|
||||
pass
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue