Brettapps commited on
Commit
9110c55
·
verified ·
1 Parent(s): d451ff3

Upload folder using huggingface_hub (part 4)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/INSTALLER +1 -0
  2. .mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/METADATA +90 -0
  3. .mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/RECORD +850 -0
  4. .mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/REQUESTED +0 -0
  5. .mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/WHEEL +5 -0
  6. .mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/entry_points.txt +3 -0
  7. .mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/licenses/AUTHORS.txt +821 -0
  8. .mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/licenses/LICENSE.txt +20 -0
  9. .mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/top_level.txt +1 -0
  10. .mcp-venv/lib/python3.13/site-packages/pip/_internal/models/installation_report.py +56 -0
  11. .mcp-venv/lib/python3.13/site-packages/pip/_internal/models/link.py +608 -0
  12. .mcp-venv/lib/python3.13/site-packages/pip/_internal/models/pylock.py +183 -0
  13. .mcp-venv/lib/python3.13/site-packages/pip/_internal/models/scheme.py +25 -0
  14. .mcp-venv/lib/python3.13/site-packages/pip/_internal/models/search_scope.py +127 -0
  15. .mcp-venv/lib/python3.13/site-packages/pip/_internal/models/selection_prefs.py +53 -0
  16. .mcp-venv/lib/python3.13/site-packages/pip/_internal/models/target_python.py +121 -0
  17. .mcp-venv/lib/python3.13/site-packages/pip/_internal/models/wheel.py +139 -0
  18. .mcp-venv/lib/python3.13/site-packages/pip/_internal/network/__init__.py +1 -0
  19. .mcp-venv/lib/python3.13/site-packages/pip/_internal/network/auth.py +566 -0
  20. .mcp-venv/lib/python3.13/site-packages/pip/_internal/network/cache.py +117 -0
  21. .mcp-venv/lib/python3.13/site-packages/pip/_internal/network/download.py +314 -0
  22. .mcp-venv/lib/python3.13/site-packages/pip/_internal/network/lazy_wheel.py +210 -0
  23. .mcp-venv/lib/python3.13/site-packages/pip/_internal/network/session.py +523 -0
  24. .mcp-venv/lib/python3.13/site-packages/pip/_internal/network/utils.py +98 -0
  25. .mcp-venv/lib/python3.13/site-packages/pip/_internal/network/xmlrpc.py +61 -0
  26. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/__init__.py +0 -0
  27. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/__init__.py +0 -0
  28. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/build_tracker.py +138 -0
  29. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/metadata.py +38 -0
  30. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/metadata_editable.py +41 -0
  31. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/metadata_legacy.py +73 -0
  32. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/wheel.py +37 -0
  33. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/wheel_editable.py +46 -0
  34. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/wheel_legacy.py +118 -0
  35. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/check.py +180 -0
  36. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/freeze.py +256 -0
  37. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/install/__init__.py +1 -0
  38. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/install/editable_legacy.py +46 -0
  39. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/install/wheel.py +738 -0
  40. .mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/prepare.py +737 -0
  41. .mcp-venv/lib/python3.13/site-packages/pip/_internal/pyproject.py +185 -0
  42. .mcp-venv/lib/python3.13/site-packages/pip/_internal/req/__init__.py +103 -0
  43. .mcp-venv/lib/python3.13/site-packages/pip/_internal/req/constructors.py +560 -0
  44. .mcp-venv/lib/python3.13/site-packages/pip/_internal/req/req_dependency_group.py +79 -0
  45. .mcp-venv/lib/python3.13/site-packages/pip/_internal/req/req_file.py +623 -0
  46. .mcp-venv/lib/python3.13/site-packages/pip/_internal/req/req_install.py +934 -0
  47. .mcp-venv/lib/python3.13/site-packages/pip/_internal/req/req_set.py +82 -0
  48. .mcp-venv/lib/python3.13/site-packages/pip/_internal/req/req_uninstall.py +636 -0
  49. .mcp-venv/lib/python3.13/site-packages/pip/_internal/resolution/__init__.py +0 -0
  50. .mcp-venv/lib/python3.13/site-packages/pip/_internal/resolution/base.py +20 -0
.mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
.mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/METADATA ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.4
2
+ Name: pip
3
+ Version: 25.1.1
4
+ Summary: The PyPA recommended tool for installing Python packages.
5
+ Author-email: The pip developers <distutils-sig@python.org>
6
+ License: MIT
7
+ Project-URL: Homepage, https://pip.pypa.io/
8
+ Project-URL: Documentation, https://pip.pypa.io
9
+ Project-URL: Source, https://github.com/pypa/pip
10
+ Project-URL: Changelog, https://pip.pypa.io/en/stable/news/
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Topic :: Software Development :: Build Tools
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: Implementation :: CPython
24
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/x-rst
27
+ License-File: LICENSE.txt
28
+ License-File: AUTHORS.txt
29
+ Dynamic: license-file
30
+
31
+ pip - The Python Package Installer
32
+ ==================================
33
+
34
+ .. |pypi-version| image:: https://img.shields.io/pypi/v/pip.svg
35
+ :target: https://pypi.org/project/pip/
36
+ :alt: PyPI
37
+
38
+ .. |python-versions| image:: https://img.shields.io/pypi/pyversions/pip
39
+ :target: https://pypi.org/project/pip
40
+ :alt: PyPI - Python Version
41
+
42
+ .. |docs-badge| image:: https://readthedocs.org/projects/pip/badge/?version=latest
43
+ :target: https://pip.pypa.io/en/latest
44
+ :alt: Documentation
45
+
46
+ |pypi-version| |python-versions| |docs-badge|
47
+
48
+ pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
49
+
50
+ Please take a look at our documentation for how to install and use pip:
51
+
52
+ * `Installation`_
53
+ * `Usage`_
54
+
55
+ We release updates regularly, with a new version every 3 months. Find more details in our documentation:
56
+
57
+ * `Release notes`_
58
+ * `Release process`_
59
+
60
+ If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms:
61
+
62
+ * `Issue tracking`_
63
+ * `Discourse channel`_
64
+ * `User IRC`_
65
+
66
+ If you want to get involved head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms:
67
+
68
+ * `GitHub page`_
69
+ * `Development documentation`_
70
+ * `Development IRC`_
71
+
72
+ Code of Conduct
73
+ ---------------
74
+
75
+ Everyone interacting in the pip project's codebases, issue trackers, chat
76
+ rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
77
+
78
+ .. _package installer: https://packaging.python.org/guides/tool-recommendations/
79
+ .. _Python Package Index: https://pypi.org
80
+ .. _Installation: https://pip.pypa.io/en/stable/installation/
81
+ .. _Usage: https://pip.pypa.io/en/stable/
82
+ .. _Release notes: https://pip.pypa.io/en/stable/news.html
83
+ .. _Release process: https://pip.pypa.io/en/latest/development/release-process/
84
+ .. _GitHub page: https://github.com/pypa/pip
85
+ .. _Development documentation: https://pip.pypa.io/en/latest/development
86
+ .. _Issue tracking: https://github.com/pypa/pip/issues
87
+ .. _Discourse channel: https://discuss.python.org/c/packaging
88
+ .. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa
89
+ .. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev
90
+ .. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
.mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/RECORD ADDED
@@ -0,0 +1,850 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ../../../bin/pip,sha256=_omqdOXb7p7c8STNozj_QmR3kiL3_czjVfHPL4hWlZs,277
2
+ ../../../bin/pip3,sha256=_omqdOXb7p7c8STNozj_QmR3kiL3_czjVfHPL4hWlZs,277
3
+ ../../../bin/pip3.13,sha256=_omqdOXb7p7c8STNozj_QmR3kiL3_czjVfHPL4hWlZs,277
4
+ pip-25.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
5
+ pip-25.1.1.dist-info/METADATA,sha256=QFxj1tLpk8hGWrgQLRhJYUpwo_1FqBr43OT0srIZcmU,3649
6
+ pip-25.1.1.dist-info/RECORD,,
7
+ pip-25.1.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ pip-25.1.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
9
+ pip-25.1.1.dist-info/entry_points.txt,sha256=eeIjuzfnfR2PrhbjnbzFU6MnSS70kZLxwaHHq6M-bD0,87
10
+ pip-25.1.1.dist-info/licenses/AUTHORS.txt,sha256=YzDFTYpeYnwpmODDFTgyKZNKWcfdO10L5Ex_U6kJLRc,11223
11
+ pip-25.1.1.dist-info/licenses/LICENSE.txt,sha256=Y0MApmnUmurmWxLGxIySTFGkzfPR_whtw0VtyLyqIQQ,1093
12
+ pip-25.1.1.dist-info/top_level.txt,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
13
+ pip/__init__.py,sha256=zQQ7Na8YWi0IN86IUKEzDAJtyVpXdJXYDkQ536caUiQ,357
14
+ pip/__main__.py,sha256=WzbhHXTbSE6gBY19mNN9m4s5o_365LOvTYSgqgbdBhE,854
15
+ pip/__pip-runner__.py,sha256=JOoEZTwrtv7jRaXBkgSQKAE04yNyfFmGHxqpHiGHvL0,1450
16
+ pip/__pycache__/__init__.cpython-313.pyc,,
17
+ pip/__pycache__/__main__.cpython-313.pyc,,
18
+ pip/__pycache__/__pip-runner__.cpython-313.pyc,,
19
+ pip/_internal/__init__.py,sha256=MfcoOluDZ8QMCFYal04IqOJ9q6m2V7a0aOsnI-WOxUo,513
20
+ pip/_internal/__pycache__/__init__.cpython-313.pyc,,
21
+ pip/_internal/__pycache__/build_env.cpython-313.pyc,,
22
+ pip/_internal/__pycache__/cache.cpython-313.pyc,,
23
+ pip/_internal/__pycache__/configuration.cpython-313.pyc,,
24
+ pip/_internal/__pycache__/exceptions.cpython-313.pyc,,
25
+ pip/_internal/__pycache__/main.cpython-313.pyc,,
26
+ pip/_internal/__pycache__/pyproject.cpython-313.pyc,,
27
+ pip/_internal/__pycache__/self_outdated_check.cpython-313.pyc,,
28
+ pip/_internal/__pycache__/wheel_builder.cpython-313.pyc,,
29
+ pip/_internal/build_env.py,sha256=60_espLI9X3C2db3Ww2gIcyjNk2cAPNcc5gsVO4DOqg,10924
30
+ pip/_internal/cache.py,sha256=SjhJK1C6NbonrU4AyYXKTOH0CGOk5cJrYt60mRANnPM,10368
31
+ pip/_internal/cli/__init__.py,sha256=Iqg_tKA771XuMO1P4t_sDHnSKPzkUb9D0DqunAmw_ko,131
32
+ pip/_internal/cli/__pycache__/__init__.cpython-313.pyc,,
33
+ pip/_internal/cli/__pycache__/autocompletion.cpython-313.pyc,,
34
+ pip/_internal/cli/__pycache__/base_command.cpython-313.pyc,,
35
+ pip/_internal/cli/__pycache__/cmdoptions.cpython-313.pyc,,
36
+ pip/_internal/cli/__pycache__/command_context.cpython-313.pyc,,
37
+ pip/_internal/cli/__pycache__/index_command.cpython-313.pyc,,
38
+ pip/_internal/cli/__pycache__/main.cpython-313.pyc,,
39
+ pip/_internal/cli/__pycache__/main_parser.cpython-313.pyc,,
40
+ pip/_internal/cli/__pycache__/parser.cpython-313.pyc,,
41
+ pip/_internal/cli/__pycache__/progress_bars.cpython-313.pyc,,
42
+ pip/_internal/cli/__pycache__/req_command.cpython-313.pyc,,
43
+ pip/_internal/cli/__pycache__/spinners.cpython-313.pyc,,
44
+ pip/_internal/cli/__pycache__/status_codes.cpython-313.pyc,,
45
+ pip/_internal/cli/autocompletion.py,sha256=fs0Wy16Ga5tX1IZKvww5BDi7i5zyzfCPvu7cgXlgXys,6864
46
+ pip/_internal/cli/base_command.py,sha256=0A8YuJVJh2YyXU8pdW0eidLg1eklCW5cU01mpI-FAxA,8351
47
+ pip/_internal/cli/cmdoptions.py,sha256=VHJ-32dTF1X3iQz1jaVPt3JxksyZcCfGZ5qrStZmvTM,31908
48
+ pip/_internal/cli/command_context.py,sha256=RHgIPwtObh5KhMrd3YZTkl8zbVG-6Okml7YbFX4Ehg0,774
49
+ pip/_internal/cli/index_command.py,sha256=kplkusUgCZy75jNCo-etaDmSG8UvqcR2W50ALDdm6dk,5720
50
+ pip/_internal/cli/main.py,sha256=1bXC7uL3tdb_EZlGVKs6_TgzC9tlKU7zhAZsbZA-IzY,2816
51
+ pip/_internal/cli/main_parser.py,sha256=chZqNmCuO_JYt8ynBCumh4crURaRoXBZ0RxoSYQIwCw,4337
52
+ pip/_internal/cli/parser.py,sha256=VCMtduzECUV87KaHNu-xJ-wLNL82yT3x16V4XBxOAqI,10825
53
+ pip/_internal/cli/progress_bars.py,sha256=r9BD4T2-egcInB1Uh9Jjw3EP9F3INy5kZhGwSePm9jo,4435
54
+ pip/_internal/cli/req_command.py,sha256=1yfssBvnUKNer8D7iT3OHqdJJNdCqRhwDqUFWgieppk,12934
55
+ pip/_internal/cli/spinners.py,sha256=hIJ83GerdFgFCdobIA23Jggetegl_uC4Sp586nzFbPE,5118
56
+ pip/_internal/cli/status_codes.py,sha256=sEFHUaUJbqv8iArL3HAtcztWZmGOFX01hTesSytDEh0,116
57
+ pip/_internal/commands/__init__.py,sha256=3405KyFv4l0ruxeF69oosFanxNQcC_fHBGv7Rpt0PXg,4009
58
+ pip/_internal/commands/__pycache__/__init__.cpython-313.pyc,,
59
+ pip/_internal/commands/__pycache__/cache.cpython-313.pyc,,
60
+ pip/_internal/commands/__pycache__/check.cpython-313.pyc,,
61
+ pip/_internal/commands/__pycache__/completion.cpython-313.pyc,,
62
+ pip/_internal/commands/__pycache__/configuration.cpython-313.pyc,,
63
+ pip/_internal/commands/__pycache__/debug.cpython-313.pyc,,
64
+ pip/_internal/commands/__pycache__/download.cpython-313.pyc,,
65
+ pip/_internal/commands/__pycache__/freeze.cpython-313.pyc,,
66
+ pip/_internal/commands/__pycache__/hash.cpython-313.pyc,,
67
+ pip/_internal/commands/__pycache__/help.cpython-313.pyc,,
68
+ pip/_internal/commands/__pycache__/index.cpython-313.pyc,,
69
+ pip/_internal/commands/__pycache__/inspect.cpython-313.pyc,,
70
+ pip/_internal/commands/__pycache__/install.cpython-313.pyc,,
71
+ pip/_internal/commands/__pycache__/list.cpython-313.pyc,,
72
+ pip/_internal/commands/__pycache__/lock.cpython-313.pyc,,
73
+ pip/_internal/commands/__pycache__/search.cpython-313.pyc,,
74
+ pip/_internal/commands/__pycache__/show.cpython-313.pyc,,
75
+ pip/_internal/commands/__pycache__/uninstall.cpython-313.pyc,,
76
+ pip/_internal/commands/__pycache__/wheel.cpython-313.pyc,,
77
+ pip/_internal/commands/cache.py,sha256=IOezTicHjGE5sWdBx2nwPVgbjuJHM3s-BZEkpZLemuY,8107
78
+ pip/_internal/commands/check.py,sha256=Hr_4eiMd9cgVDgEvjtIdw915NmL7ROIWW8enkr8slPQ,2268
79
+ pip/_internal/commands/completion.py,sha256=W9QFQTPLjy2tPACJ_y3g9EgB1pbsh7pvCUX8ocuIdPg,4554
80
+ pip/_internal/commands/configuration.py,sha256=n98enwp6y0b5G6fiRQjaZo43FlJKYve_daMhN-4BRNc,9766
81
+ pip/_internal/commands/debug.py,sha256=DNDRgE9YsKrbYzU0s3VKi8rHtKF4X13CJ_br_8PUXO0,6797
82
+ pip/_internal/commands/download.py,sha256=0qB0nys6ZEPsog451lDsjL5Bx7Z97t-B80oFZKhpzKM,5273
83
+ pip/_internal/commands/freeze.py,sha256=i9LzK6tJDgtfShSrS38Gn6aCfFlj7K4ZZTMbBwZTPmA,3215
84
+ pip/_internal/commands/hash.py,sha256=EVVOuvGtoPEdFi8SNnmdqlCQrhCxV-kJsdwtdcCnXGQ,1703
85
+ pip/_internal/commands/help.py,sha256=gcc6QDkcgHMOuAn5UxaZwAStsRBrnGSn_yxjS57JIoM,1132
86
+ pip/_internal/commands/index.py,sha256=8UucFVwx6FmM8cNbaPY8iI5kZdV3f6jhqDa-S8aGgpg,5068
87
+ pip/_internal/commands/inspect.py,sha256=PGrY9TRTRCM3y5Ml8Bdk8DEOXquWRfscr4DRo1LOTPc,3189
88
+ pip/_internal/commands/install.py,sha256=SRsiLpead7A8bLdxMqxTAJM3sUFHtgN9zgBT98UQz5E,29757
89
+ pip/_internal/commands/list.py,sha256=3ciKAsDVrmmuUWQKDttrKeGv9N5Dk8lUli8G5Q6YzAE,13371
90
+ pip/_internal/commands/lock.py,sha256=bUYrryKa769UXM61imojoeVVgc_1ZHK-9a0hIJmmlCg,5941
91
+ pip/_internal/commands/search.py,sha256=IrfvxcRCSoZY9A5XAlCF1wtl_y2HPcXslQdHcjzwMNk,5784
92
+ pip/_internal/commands/show.py,sha256=Yh5rGYhR2Io5TkL0fFCMWE1VqqM4xhPHjhbdS3QgEac,8028
93
+ pip/_internal/commands/uninstall.py,sha256=7pOR7enK76gimyxQbzxcG1OsyLXL3DvX939xmM8Fvtg,3892
94
+ pip/_internal/commands/wheel.py,sha256=NEfaVF4f41VBNSn93RL8gkfCEDmdGhbP9xu_dE6cdUk,6346
95
+ pip/_internal/configuration.py,sha256=-KOok6jh3hFzXMPQFPJ1_EFjBpAsge-RSreQuLHLmzo,14005
96
+ pip/_internal/distributions/__init__.py,sha256=Hq6kt6gXBgjNit5hTTWLAzeCNOKoB-N0pGYSqehrli8,858
97
+ pip/_internal/distributions/__pycache__/__init__.cpython-313.pyc,,
98
+ pip/_internal/distributions/__pycache__/base.cpython-313.pyc,,
99
+ pip/_internal/distributions/__pycache__/installed.cpython-313.pyc,,
100
+ pip/_internal/distributions/__pycache__/sdist.cpython-313.pyc,,
101
+ pip/_internal/distributions/__pycache__/wheel.cpython-313.pyc,,
102
+ pip/_internal/distributions/base.py,sha256=QeB9qvKXDIjLdPBDE5fMgpfGqMMCr-govnuoQnGuiF8,1783
103
+ pip/_internal/distributions/installed.py,sha256=QinHFbWAQ8oE0pbD8MFZWkwlnfU1QYTccA1vnhrlYOU,842
104
+ pip/_internal/distributions/sdist.py,sha256=PlcP4a6-R6c98XnOM-b6Lkb3rsvh9iG4ok8shaanrzs,6751
105
+ pip/_internal/distributions/wheel.py,sha256=THBYfnv7VVt8mYhMYUtH13S1E7FDwtDyDfmUcl8ai0E,1317
106
+ pip/_internal/exceptions.py,sha256=wpE11H0e4L9G6AH70sRG149z82X7wX530HK-9eA_DIQ,28464
107
+ pip/_internal/index/__init__.py,sha256=tzwMH_fhQeubwMqHdSivasg1cRgTSbNg2CiMVnzMmyU,29
108
+ pip/_internal/index/__pycache__/__init__.cpython-313.pyc,,
109
+ pip/_internal/index/__pycache__/collector.cpython-313.pyc,,
110
+ pip/_internal/index/__pycache__/package_finder.cpython-313.pyc,,
111
+ pip/_internal/index/__pycache__/sources.cpython-313.pyc,,
112
+ pip/_internal/index/collector.py,sha256=RdPO0JLAlmyBWPAWYHPyRoGjz3GNAeTngCNkbGey_mE,16265
113
+ pip/_internal/index/package_finder.py,sha256=RohRzzLExoXl7QDdTiqyxIaQEcHUn6UNOr9KzC1vjL0,38446
114
+ pip/_internal/index/sources.py,sha256=lPBLK5Xiy8Q6IQMio26Wl7ocfZOKkgGklIBNyUJ23fI,8632
115
+ pip/_internal/locations/__init__.py,sha256=vvTMNxghT0aEXrSdqpNtuRDGx08bzJxfDAUUfQ0Vb0A,14309
116
+ pip/_internal/locations/__pycache__/__init__.cpython-313.pyc,,
117
+ pip/_internal/locations/__pycache__/_distutils.cpython-313.pyc,,
118
+ pip/_internal/locations/__pycache__/_sysconfig.cpython-313.pyc,,
119
+ pip/_internal/locations/__pycache__/base.cpython-313.pyc,,
120
+ pip/_internal/locations/_distutils.py,sha256=x6nyVLj7X11Y4khIdf-mFlxMl2FWadtVEgeb8upc_WI,6013
121
+ pip/_internal/locations/_sysconfig.py,sha256=IGzds60qsFneRogC-oeBaY7bEh3lPt_v47kMJChQXsU,7724
122
+ pip/_internal/locations/base.py,sha256=RQiPi1d4FVM2Bxk04dQhXZ2PqkeljEL2fZZ9SYqIQ78,2556
123
+ pip/_internal/main.py,sha256=r-UnUe8HLo5XFJz8inTcOOTiu_sxNhgHb6VwlGUllOI,340
124
+ pip/_internal/metadata/__init__.py,sha256=nGWuZvjQlIHudlMz_-bsUs2LDA2ZKNPGevZoEGcd64Y,5723
125
+ pip/_internal/metadata/__pycache__/__init__.cpython-313.pyc,,
126
+ pip/_internal/metadata/__pycache__/_json.cpython-313.pyc,,
127
+ pip/_internal/metadata/__pycache__/base.cpython-313.pyc,,
128
+ pip/_internal/metadata/__pycache__/pkg_resources.cpython-313.pyc,,
129
+ pip/_internal/metadata/_json.py,sha256=ezrIYazHCINM2QUk1eA9wEAMj3aeGWeDVgGalgUzKpc,2707
130
+ pip/_internal/metadata/base.py,sha256=jCbzdIc8MgWnPR4rfrvSQhSVzSoOyKOXhj3xe8BoG8c,25467
131
+ pip/_internal/metadata/importlib/__init__.py,sha256=jUUidoxnHcfITHHaAWG1G2i5fdBYklv_uJcjo2x7VYE,135
132
+ pip/_internal/metadata/importlib/__pycache__/__init__.cpython-313.pyc,,
133
+ pip/_internal/metadata/importlib/__pycache__/_compat.cpython-313.pyc,,
134
+ pip/_internal/metadata/importlib/__pycache__/_dists.cpython-313.pyc,,
135
+ pip/_internal/metadata/importlib/__pycache__/_envs.cpython-313.pyc,,
136
+ pip/_internal/metadata/importlib/_compat.py,sha256=c6av8sP8BBjAZuFSJow1iWfygUXNM3xRTCn5nqw6B9M,2796
137
+ pip/_internal/metadata/importlib/_dists.py,sha256=ftmYiyfUGUIjnVwt6W-Ijsimy5c28KgmXly5Q5IQ2P4,8279
138
+ pip/_internal/metadata/importlib/_envs.py,sha256=X63CkdAPJCYPhefYSLiQzPf9ijMXm5nL_A_Z68yp2-w,5297
139
+ pip/_internal/metadata/pkg_resources.py,sha256=U07ETAINSGeSRBfWUG93E4tZZbaW_f7PGzEqZN0hulc,10542
140
+ pip/_internal/models/__init__.py,sha256=AjmCEBxX_MH9f_jVjIGNCFJKYCYeSEe18yyvNx4uRKQ,62
141
+ pip/_internal/models/__pycache__/__init__.cpython-313.pyc,,
142
+ pip/_internal/models/__pycache__/candidate.cpython-313.pyc,,
143
+ pip/_internal/models/__pycache__/direct_url.cpython-313.pyc,,
144
+ pip/_internal/models/__pycache__/format_control.cpython-313.pyc,,
145
+ pip/_internal/models/__pycache__/index.cpython-313.pyc,,
146
+ pip/_internal/models/__pycache__/installation_report.cpython-313.pyc,,
147
+ pip/_internal/models/__pycache__/link.cpython-313.pyc,,
148
+ pip/_internal/models/__pycache__/pylock.cpython-313.pyc,,
149
+ pip/_internal/models/__pycache__/scheme.cpython-313.pyc,,
150
+ pip/_internal/models/__pycache__/search_scope.cpython-313.pyc,,
151
+ pip/_internal/models/__pycache__/selection_prefs.cpython-313.pyc,,
152
+ pip/_internal/models/__pycache__/target_python.cpython-313.pyc,,
153
+ pip/_internal/models/__pycache__/wheel.cpython-313.pyc,,
154
+ pip/_internal/models/candidate.py,sha256=zzgFRuw_kWPjKpGw7LC0ZUMD2CQ2EberUIYs8izjdCA,753
155
+ pip/_internal/models/direct_url.py,sha256=lJ1fIVTgk5UG5SzTNR0FpgSGAQjChlH-3otgiEJAhIs,6576
156
+ pip/_internal/models/format_control.py,sha256=wtsQqSK9HaUiNxQEuB-C62eVimw6G4_VQFxV9-_KDBE,2486
157
+ pip/_internal/models/index.py,sha256=tYnL8oxGi4aSNWur0mG8DAP7rC6yuha_MwJO8xw0crI,1030
158
+ pip/_internal/models/installation_report.py,sha256=zRVZoaz-2vsrezj_H3hLOhMZCK9c7TbzWgC-jOalD00,2818
159
+ pip/_internal/models/link.py,sha256=wIAgxhiu05ycLhbtAibknXX5L6X9ju_PPLVnMcvh9B4,21511
160
+ pip/_internal/models/pylock.py,sha256=n3-I26bf2v-Kn6qcx4ATB_Zel2SLhaUxZBmsMeGgYAo,6196
161
+ pip/_internal/models/scheme.py,sha256=PakmHJM3e8OOWSZFtfz1Az7f1meONJnkGuQxFlt3wBE,575
162
+ pip/_internal/models/search_scope.py,sha256=67NEnsYY84784S-MM7ekQuo9KXLH-7MzFntXjapvAo0,4531
163
+ pip/_internal/models/selection_prefs.py,sha256=qaFfDs3ciqoXPg6xx45N1jPLqccLJw4N0s4P0PyHTQ8,2015
164
+ pip/_internal/models/target_python.py,sha256=2XaH2rZ5ZF-K5wcJbEMGEl7SqrTToDDNkrtQ2v_v_-Q,4271
165
+ pip/_internal/models/wheel.py,sha256=10NUTXIRjf2P8oe1Wzolv8oUv7-YurrOduVFUIaDhdM,5506
166
+ pip/_internal/network/__init__.py,sha256=FMy06P__y6jMjUc8z3ZcQdKF-pmZ2zM14_vBeHPGhUI,49
167
+ pip/_internal/network/__pycache__/__init__.cpython-313.pyc,,
168
+ pip/_internal/network/__pycache__/auth.cpython-313.pyc,,
169
+ pip/_internal/network/__pycache__/cache.cpython-313.pyc,,
170
+ pip/_internal/network/__pycache__/download.cpython-313.pyc,,
171
+ pip/_internal/network/__pycache__/lazy_wheel.cpython-313.pyc,,
172
+ pip/_internal/network/__pycache__/session.cpython-313.pyc,,
173
+ pip/_internal/network/__pycache__/utils.cpython-313.pyc,,
174
+ pip/_internal/network/__pycache__/xmlrpc.cpython-313.pyc,,
175
+ pip/_internal/network/auth.py,sha256=D4gASjUrqoDFlSt6gQ767KAAjv6PUyJU0puDlhXNVRE,20809
176
+ pip/_internal/network/cache.py,sha256=JGYT-BUaSMdEBwII_K1UE6qyBItz7hzGkyLl_JRzkBY,4613
177
+ pip/_internal/network/download.py,sha256=6IdZyoERWIsXXFUFL_h_e_xi8Z0G0UlpkodPy8qKv2U,11078
178
+ pip/_internal/network/lazy_wheel.py,sha256=PBdoMoNQQIA84Fhgne38jWF52W4x_KtsHjxgv4dkRKA,7622
179
+ pip/_internal/network/session.py,sha256=msM4es16LmmNEYNkrYyg8fTc7gAHbKFltawfKP27LOI,18771
180
+ pip/_internal/network/utils.py,sha256=Inaxel-NxBu4PQWkjyErdnfewsFCcgHph7dzR1-FboY,4088
181
+ pip/_internal/network/xmlrpc.py,sha256=jW9oDSWamMld3iZOO9RbonVC8ZStkHyppCszoevkuJg,1837
182
+ pip/_internal/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
183
+ pip/_internal/operations/__pycache__/__init__.cpython-313.pyc,,
184
+ pip/_internal/operations/__pycache__/check.cpython-313.pyc,,
185
+ pip/_internal/operations/__pycache__/freeze.cpython-313.pyc,,
186
+ pip/_internal/operations/__pycache__/prepare.cpython-313.pyc,,
187
+ pip/_internal/operations/build/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
188
+ pip/_internal/operations/build/__pycache__/__init__.cpython-313.pyc,,
189
+ pip/_internal/operations/build/__pycache__/build_tracker.cpython-313.pyc,,
190
+ pip/_internal/operations/build/__pycache__/metadata.cpython-313.pyc,,
191
+ pip/_internal/operations/build/__pycache__/metadata_editable.cpython-313.pyc,,
192
+ pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-313.pyc,,
193
+ pip/_internal/operations/build/__pycache__/wheel.cpython-313.pyc,,
194
+ pip/_internal/operations/build/__pycache__/wheel_editable.cpython-313.pyc,,
195
+ pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-313.pyc,,
196
+ pip/_internal/operations/build/build_tracker.py,sha256=-ARW_TcjHCOX7D2NUOGntB4Fgc6b4aolsXkAK6BWL7w,4774
197
+ pip/_internal/operations/build/metadata.py,sha256=INHaeiRfOiLYCXApfDNRo9Cw2xI4VwTc0KItvfdfOjk,1421
198
+ pip/_internal/operations/build/metadata_editable.py,sha256=oWudMsnjy4loO_Jy7g4N9nxsnaEX_iDlVRgCy7pu1rs,1509
199
+ pip/_internal/operations/build/metadata_legacy.py,sha256=wv8cFA0wTqF62Jlm9QwloYZsofOyQ7sWBBmvCcVvn1k,2189
200
+ pip/_internal/operations/build/wheel.py,sha256=sT12FBLAxDC6wyrDorh8kvcZ1jG5qInCRWzzP-UkJiQ,1075
201
+ pip/_internal/operations/build/wheel_editable.py,sha256=yOtoH6zpAkoKYEUtr8FhzrYnkNHQaQBjWQ2HYae1MQg,1417
202
+ pip/_internal/operations/build/wheel_legacy.py,sha256=KXpyGYoCQYcudXNZvohLXgWHaCk4Gf3z0dbS9ol4uu0,3620
203
+ pip/_internal/operations/check.py,sha256=4cnD_2eglsDe5s2CoYkxDt4HcRitTywzLMfTZ-tGQ4U,5911
204
+ pip/_internal/operations/freeze.py,sha256=1_M79jAQKnCxWr-KCCmHuVXOVFGaUJHmoWLfFzgh7K4,9843
205
+ pip/_internal/operations/install/__init__.py,sha256=ak-UETcQPKlFZaWoYKWu5QVXbpFBvg0sXc3i0O4vSYY,50
206
+ pip/_internal/operations/install/__pycache__/__init__.cpython-313.pyc,,
207
+ pip/_internal/operations/install/__pycache__/editable_legacy.cpython-313.pyc,,
208
+ pip/_internal/operations/install/__pycache__/wheel.cpython-313.pyc,,
209
+ pip/_internal/operations/install/editable_legacy.py,sha256=TI6wT8sLqDTprWZLYEOBOe7a6-1B9uwKb7kTBxLIaWY,1282
210
+ pip/_internal/operations/install/wheel.py,sha256=4NYSQ9ypl69iiduh5gUPCK3WNYqouTHZ0rMXoVgkiZw,27553
211
+ pip/_internal/operations/prepare.py,sha256=-i9dYwwJJjN7h6sZTabcz84tizgn7EAsY0sHnLAfs3Q,28363
212
+ pip/_internal/pyproject.py,sha256=GLJ6rWRS5_2noKdajohoLyDty57Z7QXhcUAYghmTnWc,7286
213
+ pip/_internal/req/__init__.py,sha256=dX2QGlfDwEqE5pLjOeM-f2qEgXFn6f2Vdi_zIHAYy1k,3096
214
+ pip/_internal/req/__pycache__/__init__.cpython-313.pyc,,
215
+ pip/_internal/req/__pycache__/constructors.cpython-313.pyc,,
216
+ pip/_internal/req/__pycache__/req_dependency_group.cpython-313.pyc,,
217
+ pip/_internal/req/__pycache__/req_file.cpython-313.pyc,,
218
+ pip/_internal/req/__pycache__/req_install.cpython-313.pyc,,
219
+ pip/_internal/req/__pycache__/req_set.cpython-313.pyc,,
220
+ pip/_internal/req/__pycache__/req_uninstall.cpython-313.pyc,,
221
+ pip/_internal/req/constructors.py,sha256=v1qzCN1mIldwx-nCrPc8JO4lxkm3Fv8M5RWvt8LISjc,18430
222
+ pip/_internal/req/req_dependency_group.py,sha256=5PGuZidqwDeTHZkP4tnNrlPrasfvJBONd1B5S0146zs,2677
223
+ pip/_internal/req/req_file.py,sha256=eys82McgaICOGic2UZRHjD720piKJPwmeSYdXlWwl6w,20234
224
+ pip/_internal/req/req_install.py,sha256=gMoFak9zrhjHlHaOQxPFheHKtIobppFgq1WrKel_nTE,35788
225
+ pip/_internal/req/req_set.py,sha256=j3esG0s6SzoVReX9rWn4rpYNtyET_fwxbwJPRimvRxo,2858
226
+ pip/_internal/req/req_uninstall.py,sha256=PQ6SyocDycUYsLAsTpjkbdwO_qjdo-y8BvQfZ5Avdrw,24075
227
+ pip/_internal/resolution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
228
+ pip/_internal/resolution/__pycache__/__init__.cpython-313.pyc,,
229
+ pip/_internal/resolution/__pycache__/base.cpython-313.pyc,,
230
+ pip/_internal/resolution/base.py,sha256=qlmh325SBVfvG6Me9gc5Nsh5sdwHBwzHBq6aEXtKsLA,583
231
+ pip/_internal/resolution/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
232
+ pip/_internal/resolution/legacy/__pycache__/__init__.cpython-313.pyc,,
233
+ pip/_internal/resolution/legacy/__pycache__/resolver.cpython-313.pyc,,
234
+ pip/_internal/resolution/legacy/resolver.py,sha256=3HZiJBRd1FTN6jQpI4qRO8-TbLYeIbUTS6PFvXnXs2w,24068
235
+ pip/_internal/resolution/resolvelib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
236
+ pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-313.pyc,,
237
+ pip/_internal/resolution/resolvelib/__pycache__/base.cpython-313.pyc,,
238
+ pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-313.pyc,,
239
+ pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-313.pyc,,
240
+ pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-313.pyc,,
241
+ pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-313.pyc,,
242
+ pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-313.pyc,,
243
+ pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-313.pyc,,
244
+ pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-313.pyc,,
245
+ pip/_internal/resolution/resolvelib/base.py,sha256=DCf669FsqyQY5uqXeePDHQY1e4QO-pBzWH8O0s9-K94,5023
246
+ pip/_internal/resolution/resolvelib/candidates.py,sha256=U3Qp83jhM_RiJviyrlPCijbps6wYO6VsTDaTnCf_B3o,20241
247
+ pip/_internal/resolution/resolvelib/factory.py,sha256=FCvHc9M8UJ_7iU63QtPiHuq_BmfdnBiMJ8WaDBJNFxk,32668
248
+ pip/_internal/resolution/resolvelib/found_candidates.py,sha256=6lAF_pLQ2_Z0CBOHIFxGp6NfvT1uwIpCui6e-GgI5tk,6000
249
+ pip/_internal/resolution/resolvelib/provider.py,sha256=8ptYOOjfa336D4FZ751EQHR0LDq8jJhIGJXDou8Cv8Y,11190
250
+ pip/_internal/resolution/resolvelib/reporter.py,sha256=EwJAHOjWZ8eTHQWss7zJjmQEj6ooP6oWSwwVXFtgpqQ,3260
251
+ pip/_internal/resolution/resolvelib/requirements.py,sha256=7JG4Z72e5Yk4vU0S5ulGvbqTy4FMQGYhY5zQhX9zTtY,8065
252
+ pip/_internal/resolution/resolvelib/resolver.py,sha256=9zcR4c7UZV1j2ILTmb68Ck_5HdvQvf4cmTBE2bWHkKg,12785
253
+ pip/_internal/self_outdated_check.py,sha256=1PFtttvLAeyCVR3tPoBq2sOlPD0IJ-KSqU6bc1HUk9c,8318
254
+ pip/_internal/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
255
+ pip/_internal/utils/__pycache__/__init__.cpython-313.pyc,,
256
+ pip/_internal/utils/__pycache__/_jaraco_text.cpython-313.pyc,,
257
+ pip/_internal/utils/__pycache__/_log.cpython-313.pyc,,
258
+ pip/_internal/utils/__pycache__/appdirs.cpython-313.pyc,,
259
+ pip/_internal/utils/__pycache__/compat.cpython-313.pyc,,
260
+ pip/_internal/utils/__pycache__/compatibility_tags.cpython-313.pyc,,
261
+ pip/_internal/utils/__pycache__/datetime.cpython-313.pyc,,
262
+ pip/_internal/utils/__pycache__/deprecation.cpython-313.pyc,,
263
+ pip/_internal/utils/__pycache__/direct_url_helpers.cpython-313.pyc,,
264
+ pip/_internal/utils/__pycache__/egg_link.cpython-313.pyc,,
265
+ pip/_internal/utils/__pycache__/entrypoints.cpython-313.pyc,,
266
+ pip/_internal/utils/__pycache__/filesystem.cpython-313.pyc,,
267
+ pip/_internal/utils/__pycache__/filetypes.cpython-313.pyc,,
268
+ pip/_internal/utils/__pycache__/glibc.cpython-313.pyc,,
269
+ pip/_internal/utils/__pycache__/hashes.cpython-313.pyc,,
270
+ pip/_internal/utils/__pycache__/logging.cpython-313.pyc,,
271
+ pip/_internal/utils/__pycache__/misc.cpython-313.pyc,,
272
+ pip/_internal/utils/__pycache__/packaging.cpython-313.pyc,,
273
+ pip/_internal/utils/__pycache__/retry.cpython-313.pyc,,
274
+ pip/_internal/utils/__pycache__/setuptools_build.cpython-313.pyc,,
275
+ pip/_internal/utils/__pycache__/subprocess.cpython-313.pyc,,
276
+ pip/_internal/utils/__pycache__/temp_dir.cpython-313.pyc,,
277
+ pip/_internal/utils/__pycache__/unpacking.cpython-313.pyc,,
278
+ pip/_internal/utils/__pycache__/urls.cpython-313.pyc,,
279
+ pip/_internal/utils/__pycache__/virtualenv.cpython-313.pyc,,
280
+ pip/_internal/utils/__pycache__/wheel.cpython-313.pyc,,
281
+ pip/_internal/utils/_jaraco_text.py,sha256=M15uUPIh5NpP1tdUGBxRau6q1ZAEtI8-XyLEETscFfE,3350
282
+ pip/_internal/utils/_log.py,sha256=-jHLOE_THaZz5BFcCnoSL9EYAtJ0nXem49s9of4jvKw,1015
283
+ pip/_internal/utils/appdirs.py,sha256=zrIISCn2QxlXYw-zJZZBTrFNTyy_0WNKiI-TOoN6wJo,1705
284
+ pip/_internal/utils/compat.py,sha256=ckkFveBiYQjRWjkNsajt_oWPS57tJvE8XxoC4OIYgCY,2399
285
+ pip/_internal/utils/compatibility_tags.py,sha256=q5W7IrNlqC5ke0AqWRG6aX5pimiqh--xuPCCNwCKPsU,6662
286
+ pip/_internal/utils/datetime.py,sha256=Gt29Ml4ToPSM88j54iu43WKtrU9A-moP4QmMiiqzedU,241
287
+ pip/_internal/utils/deprecation.py,sha256=k7Qg_UBAaaTdyq82YVARA6D7RmcGTXGv7fnfcgigj4Q,3707
288
+ pip/_internal/utils/direct_url_helpers.py,sha256=r2MRtkVDACv9AGqYODBUC9CjwgtsUU1s68hmgfCJMtA,3196
289
+ pip/_internal/utils/egg_link.py,sha256=0FePZoUYKv4RGQ2t6x7w5Z427wbA_Uo3WZnAkrgsuqo,2463
290
+ pip/_internal/utils/entrypoints.py,sha256=4CheZ81OBPPLb3Gn-X_WEPtllUibpwDVzlVQ4RFh7PM,3325
291
+ pip/_internal/utils/filesystem.py,sha256=ajvA-q4ocliW9kPp8Yquh-4vssXbu-UKbo5FV9V4X64,4950
292
+ pip/_internal/utils/filetypes.py,sha256=OCPzUxq3Aa6k_95MiI8DYgkOzutTs47fA-v-vYUJp9E,715
293
+ pip/_internal/utils/glibc.py,sha256=vUkWq_1pJuzcYNcGKLlQmABoUiisK8noYY1yc8Wq4w4,3734
294
+ pip/_internal/utils/hashes.py,sha256=XGGLL0AG8-RhWnyz87xF6MFZ--BKadHU35D47eApCKI,4972
295
+ pip/_internal/utils/logging.py,sha256=zMZK1NxfhM4QMGUyaU9q1grNuDhLSVbSkGCvZBKmaPw,12076
296
+ pip/_internal/utils/misc.py,sha256=DWnYxBUItjRp7hhxEg4ih6P6YpKrykM86dbi_EcU8SQ,23450
297
+ pip/_internal/utils/packaging.py,sha256=CjJOqLNENW-U88ojOllVL40f1ab2W2Bm3KHCavwNNfw,1603
298
+ pip/_internal/utils/retry.py,sha256=mhFbykXjhTnZfgzeuy-vl9c8nECnYn_CMtwNJX2tYzQ,1392
299
+ pip/_internal/utils/setuptools_build.py,sha256=J9EyRantVgu4V-xS_qfQy2mcPLVUM7A-227QdKGUZCA,4482
300
+ pip/_internal/utils/subprocess.py,sha256=EsvqSRiSMHF98T8Txmu6NLU3U--MpTTQjtNgKP0P--M,8988
301
+ pip/_internal/utils/temp_dir.py,sha256=5qOXe8M4JeY6vaFQM867d5zkp1bSwMZ-KT5jymmP0Zg,9310
302
+ pip/_internal/utils/unpacking.py,sha256=4yCqlRAI2zxl5tfxlnLoWLNcEn-k1c3vaet_oaJ42iI,11926
303
+ pip/_internal/utils/urls.py,sha256=qceSOZb5lbNDrHNsv7_S4L4Ytszja5NwPKUMnZHbYnM,1599
304
+ pip/_internal/utils/virtualenv.py,sha256=S6f7csYorRpiD6cvn3jISZYc3I8PJC43H5iMFpRAEDU,3456
305
+ pip/_internal/utils/wheel.py,sha256=MHObYn6d7VyZL10i-W1xoJZ2hT5-wB1WkII70AsYUE8,4493
306
+ pip/_internal/vcs/__init__.py,sha256=UAqvzpbi0VbZo3Ub6skEeZAw-ooIZR-zX_WpCbxyCoU,596
307
+ pip/_internal/vcs/__pycache__/__init__.cpython-313.pyc,,
308
+ pip/_internal/vcs/__pycache__/bazaar.cpython-313.pyc,,
309
+ pip/_internal/vcs/__pycache__/git.cpython-313.pyc,,
310
+ pip/_internal/vcs/__pycache__/mercurial.cpython-313.pyc,,
311
+ pip/_internal/vcs/__pycache__/subversion.cpython-313.pyc,,
312
+ pip/_internal/vcs/__pycache__/versioncontrol.cpython-313.pyc,,
313
+ pip/_internal/vcs/bazaar.py,sha256=EKStcQaKpNu0NK4p5Q10Oc4xb3DUxFw024XrJy40bFQ,3528
314
+ pip/_internal/vcs/git.py,sha256=3KLPrKsDL9xZchmz4H1Obo8fM2Fh8ChrhtDHWjbkj-I,18591
315
+ pip/_internal/vcs/mercurial.py,sha256=oULOhzJ2Uie-06d1omkL-_Gc6meGaUkyogvqG9ZCyPs,5249
316
+ pip/_internal/vcs/subversion.py,sha256=ddTugHBqHzV3ebKlU5QXHPN4gUqlyXbOx8q8NgXKvs8,11735
317
+ pip/_internal/vcs/versioncontrol.py,sha256=cvf_-hnTAjQLXJ3d17FMNhQfcO1AcKWUF10tfrYyP-c,22440
318
+ pip/_internal/wheel_builder.py,sha256=Z5Z2ANADbKdSHY9BHqw9zG5-1AxstO6YM6m9yLWe7Vw,11212
319
+ pip/_vendor/__init__.py,sha256=WzusPTGWIMeQQWSVJ0h2rafGkVTa9WKJ2HT-2-EoZrU,4907
320
+ pip/_vendor/__pycache__/__init__.cpython-313.pyc,,
321
+ pip/_vendor/__pycache__/typing_extensions.cpython-313.pyc,,
322
+ pip/_vendor/cachecontrol/__init__.py,sha256=x9ecUkiwNvAGfE3g0eaRx3VrJZnZWAluA2LRcvab3HQ,677
323
+ pip/_vendor/cachecontrol/__pycache__/__init__.cpython-313.pyc,,
324
+ pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-313.pyc,,
325
+ pip/_vendor/cachecontrol/__pycache__/adapter.cpython-313.pyc,,
326
+ pip/_vendor/cachecontrol/__pycache__/cache.cpython-313.pyc,,
327
+ pip/_vendor/cachecontrol/__pycache__/controller.cpython-313.pyc,,
328
+ pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-313.pyc,,
329
+ pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-313.pyc,,
330
+ pip/_vendor/cachecontrol/__pycache__/serialize.cpython-313.pyc,,
331
+ pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-313.pyc,,
332
+ pip/_vendor/cachecontrol/_cmd.py,sha256=iist2EpzJvDVIhMAxXq8iFnTBsiZAd6iplxfmNboNyk,1737
333
+ pip/_vendor/cachecontrol/adapter.py,sha256=8y6rTPXOzVHmDKCW5CR9sivLVuDv-cpdGcZYdRWNaPw,6599
334
+ pip/_vendor/cachecontrol/cache.py,sha256=OXwv7Fn2AwnKNiahJHnjtvaKLndvVLv_-zO-ltlV9qI,1953
335
+ pip/_vendor/cachecontrol/caches/__init__.py,sha256=dtrrroK5BnADR1GWjCZ19aZ0tFsMfvFBtLQQU1sp_ag,303
336
+ pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-313.pyc,,
337
+ pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-313.pyc,,
338
+ pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-313.pyc,,
339
+ pip/_vendor/cachecontrol/caches/file_cache.py,sha256=d8upFmy_zwaCmlbWEVBlLXFddt8Zw8c5SFpxeOZsdfw,4117
340
+ pip/_vendor/cachecontrol/caches/redis_cache.py,sha256=9rmqwtYu_ljVkW6_oLqbC7EaX_a8YT_yLuna-eS0dgo,1386
341
+ pip/_vendor/cachecontrol/controller.py,sha256=cx0Hl8xLZgUuXuy78Gih9AYjCtqurmYjVJxyA4yWt7w,19101
342
+ pip/_vendor/cachecontrol/filewrapper.py,sha256=2ktXNPE0KqnyzF24aOsKCA58HQq1xeC6l2g6_zwjghc,4291
343
+ pip/_vendor/cachecontrol/heuristics.py,sha256=gqMXU8w0gQuEQiSdu3Yg-0vd9kW7nrWKbLca75rheGE,4881
344
+ pip/_vendor/cachecontrol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
345
+ pip/_vendor/cachecontrol/serialize.py,sha256=HQd2IllQ05HzPkVLMXTF2uX5mjEQjDBkxCqUJUODpZk,5163
346
+ pip/_vendor/cachecontrol/wrapper.py,sha256=hsGc7g8QGQTT-4f8tgz3AM5qwScg6FO0BSdLSRdEvpU,1417
347
+ pip/_vendor/certifi/__init__.py,sha256=neIaAf7BM36ygmQCmy-ZsSyjnvjWghFeu13wwEAnjj0,94
348
+ pip/_vendor/certifi/__main__.py,sha256=1k3Cr95vCxxGRGDljrW3wMdpZdL3Nhf0u1n-k2qdsCY,255
349
+ pip/_vendor/certifi/__pycache__/__init__.cpython-313.pyc,,
350
+ pip/_vendor/certifi/__pycache__/__main__.cpython-313.pyc,,
351
+ pip/_vendor/certifi/__pycache__/core.cpython-313.pyc,,
352
+ pip/_vendor/certifi/cacert.pem,sha256=xVsh-Qf3-G1IrdCTVS-1ZRdJ_1-GBQjMu0I9bB-9gMc,297255
353
+ pip/_vendor/certifi/core.py,sha256=vHXkYJt_CVYbDuS-aPFRfDZu7RkKcP9ltm-DiL4Zo30,4738
354
+ pip/_vendor/certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
355
+ pip/_vendor/dependency_groups/__init__.py,sha256=C3OFu0NGwDzQ4LOmmSOFPsRSvkbBn-mdd4j_5YqJw-s,250
356
+ pip/_vendor/dependency_groups/__main__.py,sha256=UNTM7P5mfVtT7wDi9kOTXWgV3fu3e8bTrt1Qp1jvjKo,1709
357
+ pip/_vendor/dependency_groups/__pycache__/__init__.cpython-313.pyc,,
358
+ pip/_vendor/dependency_groups/__pycache__/__main__.cpython-313.pyc,,
359
+ pip/_vendor/dependency_groups/__pycache__/_implementation.cpython-313.pyc,,
360
+ pip/_vendor/dependency_groups/__pycache__/_lint_dependency_groups.cpython-313.pyc,,
361
+ pip/_vendor/dependency_groups/__pycache__/_pip_wrapper.cpython-313.pyc,,
362
+ pip/_vendor/dependency_groups/__pycache__/_toml_compat.cpython-313.pyc,,
363
+ pip/_vendor/dependency_groups/_implementation.py,sha256=Gqb2DlQELRakeHlKf6QtQSW0M-bcEomxHw4JsvID1ls,8041
364
+ pip/_vendor/dependency_groups/_lint_dependency_groups.py,sha256=yp-DDqKXtbkDTNa0ifa-FmOA8ra24lPZEXftW-R5AuI,1710
365
+ pip/_vendor/dependency_groups/_pip_wrapper.py,sha256=nuVW_w_ntVxpE26ELEvngMY0N04sFLsijXRyZZROFG8,1865
366
+ pip/_vendor/dependency_groups/_toml_compat.py,sha256=BHnXnFacm3DeolsA35GjI6qkDApvua-1F20kv3BfZWE,285
367
+ pip/_vendor/dependency_groups/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
368
+ pip/_vendor/distlib/__init__.py,sha256=dcwgYGYGQqAEawBXPDtIx80DO_3cOmFv8HTc8JMzknQ,625
369
+ pip/_vendor/distlib/__pycache__/__init__.cpython-313.pyc,,
370
+ pip/_vendor/distlib/__pycache__/compat.cpython-313.pyc,,
371
+ pip/_vendor/distlib/__pycache__/database.cpython-313.pyc,,
372
+ pip/_vendor/distlib/__pycache__/index.cpython-313.pyc,,
373
+ pip/_vendor/distlib/__pycache__/locators.cpython-313.pyc,,
374
+ pip/_vendor/distlib/__pycache__/manifest.cpython-313.pyc,,
375
+ pip/_vendor/distlib/__pycache__/markers.cpython-313.pyc,,
376
+ pip/_vendor/distlib/__pycache__/metadata.cpython-313.pyc,,
377
+ pip/_vendor/distlib/__pycache__/resources.cpython-313.pyc,,
378
+ pip/_vendor/distlib/__pycache__/scripts.cpython-313.pyc,,
379
+ pip/_vendor/distlib/__pycache__/util.cpython-313.pyc,,
380
+ pip/_vendor/distlib/__pycache__/version.cpython-313.pyc,,
381
+ pip/_vendor/distlib/__pycache__/wheel.cpython-313.pyc,,
382
+ pip/_vendor/distlib/compat.py,sha256=2jRSjRI4o-vlXeTK2BCGIUhkc6e9ZGhSsacRM5oseTw,41467
383
+ pip/_vendor/distlib/database.py,sha256=mHy_LxiXIsIVRb-T0-idBrVLw3Ffij5teHCpbjmJ9YU,51160
384
+ pip/_vendor/distlib/index.py,sha256=lTbw268rRhj8dw1sib3VZ_0EhSGgoJO3FKJzSFMOaeA,20797
385
+ pip/_vendor/distlib/locators.py,sha256=oBeAZpFuPQSY09MgNnLfQGGAXXvVO96BFpZyKMuK4tM,51026
386
+ pip/_vendor/distlib/manifest.py,sha256=3qfmAmVwxRqU1o23AlfXrQGZzh6g_GGzTAP_Hb9C5zQ,14168
387
+ pip/_vendor/distlib/markers.py,sha256=X6sDvkFGcYS8gUW8hfsWuKEKAqhQZAJ7iXOMLxRYjYk,5164
388
+ pip/_vendor/distlib/metadata.py,sha256=zil3sg2EUfLXVigljY2d_03IJt-JSs7nX-73fECMX2s,38724
389
+ pip/_vendor/distlib/resources.py,sha256=LwbPksc0A1JMbi6XnuPdMBUn83X7BPuFNWqPGEKI698,10820
390
+ pip/_vendor/distlib/scripts.py,sha256=BJliaDAZaVB7WAkwokgC3HXwLD2iWiHaVI50H7C6eG8,18608
391
+ pip/_vendor/distlib/util.py,sha256=vMPGvsS4j9hF6Y9k3Tyom1aaHLb0rFmZAEyzeAdel9w,66682
392
+ pip/_vendor/distlib/version.py,sha256=s5VIs8wBn0fxzGxWM_aA2ZZyx525HcZbMvcTlTyZ3Rg,23727
393
+ pip/_vendor/distlib/wheel.py,sha256=DFIVguEQHCdxnSdAO0dfFsgMcvVZitg7bCOuLwZ7A_s,43979
394
+ pip/_vendor/distro/__init__.py,sha256=2fHjF-SfgPvjyNZ1iHh_wjqWdR_Yo5ODHwZC0jLBPhc,981
395
+ pip/_vendor/distro/__main__.py,sha256=bu9d3TifoKciZFcqRBuygV3GSuThnVD_m2IK4cz96Vs,64
396
+ pip/_vendor/distro/__pycache__/__init__.cpython-313.pyc,,
397
+ pip/_vendor/distro/__pycache__/__main__.cpython-313.pyc,,
398
+ pip/_vendor/distro/__pycache__/distro.cpython-313.pyc,,
399
+ pip/_vendor/distro/distro.py,sha256=XqbefacAhDT4zr_trnbA15eY8vdK4GTghgmvUGrEM_4,49430
400
+ pip/_vendor/distro/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
401
+ pip/_vendor/idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868
402
+ pip/_vendor/idna/__pycache__/__init__.cpython-313.pyc,,
403
+ pip/_vendor/idna/__pycache__/codec.cpython-313.pyc,,
404
+ pip/_vendor/idna/__pycache__/compat.cpython-313.pyc,,
405
+ pip/_vendor/idna/__pycache__/core.cpython-313.pyc,,
406
+ pip/_vendor/idna/__pycache__/idnadata.cpython-313.pyc,,
407
+ pip/_vendor/idna/__pycache__/intranges.cpython-313.pyc,,
408
+ pip/_vendor/idna/__pycache__/package_data.cpython-313.pyc,,
409
+ pip/_vendor/idna/__pycache__/uts46data.cpython-313.pyc,,
410
+ pip/_vendor/idna/codec.py,sha256=PEew3ItwzjW4hymbasnty2N2OXvNcgHB-JjrBuxHPYY,3422
411
+ pip/_vendor/idna/compat.py,sha256=RzLy6QQCdl9784aFhb2EX9EKGCJjg0P3PilGdeXXcx8,316
412
+ pip/_vendor/idna/core.py,sha256=YJYyAMnwiQEPjVC4-Fqu_p4CJ6yKKuDGmppBNQNQpFs,13239
413
+ pip/_vendor/idna/idnadata.py,sha256=W30GcIGvtOWYwAjZj4ZjuouUutC6ffgNuyjJy7fZ-lo,78306
414
+ pip/_vendor/idna/intranges.py,sha256=amUtkdhYcQG8Zr-CoMM_kVRacxkivC1WgxN1b63KKdU,1898
415
+ pip/_vendor/idna/package_data.py,sha256=q59S3OXsc5VI8j6vSD0sGBMyk6zZ4vWFREE88yCJYKs,21
416
+ pip/_vendor/idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
417
+ pip/_vendor/idna/uts46data.py,sha256=rt90K9J40gUSwppDPCrhjgi5AA6pWM65dEGRSf6rIhM,239289
418
+ pip/_vendor/msgpack/__init__.py,sha256=reRaiOtEzSjPnr7TpxjgIvbfln5pV66FhricAs2eC-g,1109
419
+ pip/_vendor/msgpack/__pycache__/__init__.cpython-313.pyc,,
420
+ pip/_vendor/msgpack/__pycache__/exceptions.cpython-313.pyc,,
421
+ pip/_vendor/msgpack/__pycache__/ext.cpython-313.pyc,,
422
+ pip/_vendor/msgpack/__pycache__/fallback.cpython-313.pyc,,
423
+ pip/_vendor/msgpack/exceptions.py,sha256=dCTWei8dpkrMsQDcjQk74ATl9HsIBH0ybt8zOPNqMYc,1081
424
+ pip/_vendor/msgpack/ext.py,sha256=kteJv03n9tYzd5oo3xYopVTo4vRaAxonBQQJhXohZZo,5726
425
+ pip/_vendor/msgpack/fallback.py,sha256=0g1Pzp0vtmBEmJ5w9F3s_-JMVURP8RS4G1cc5TRaAsI,32390
426
+ pip/_vendor/packaging/__init__.py,sha256=_0cDiPVf2S-bNfVmZguxxzmrIYWlyASxpqph4qsJWUc,494
427
+ pip/_vendor/packaging/__pycache__/__init__.cpython-313.pyc,,
428
+ pip/_vendor/packaging/__pycache__/_elffile.cpython-313.pyc,,
429
+ pip/_vendor/packaging/__pycache__/_manylinux.cpython-313.pyc,,
430
+ pip/_vendor/packaging/__pycache__/_musllinux.cpython-313.pyc,,
431
+ pip/_vendor/packaging/__pycache__/_parser.cpython-313.pyc,,
432
+ pip/_vendor/packaging/__pycache__/_structures.cpython-313.pyc,,
433
+ pip/_vendor/packaging/__pycache__/_tokenizer.cpython-313.pyc,,
434
+ pip/_vendor/packaging/__pycache__/markers.cpython-313.pyc,,
435
+ pip/_vendor/packaging/__pycache__/metadata.cpython-313.pyc,,
436
+ pip/_vendor/packaging/__pycache__/requirements.cpython-313.pyc,,
437
+ pip/_vendor/packaging/__pycache__/specifiers.cpython-313.pyc,,
438
+ pip/_vendor/packaging/__pycache__/tags.cpython-313.pyc,,
439
+ pip/_vendor/packaging/__pycache__/utils.cpython-313.pyc,,
440
+ pip/_vendor/packaging/__pycache__/version.cpython-313.pyc,,
441
+ pip/_vendor/packaging/_elffile.py,sha256=UkrbDtW7aeq3qqoAfU16ojyHZ1xsTvGke_WqMTKAKd0,3286
442
+ pip/_vendor/packaging/_manylinux.py,sha256=t4y_-dTOcfr36gLY-ztiOpxxJFGO2ikC11HgfysGxiM,9596
443
+ pip/_vendor/packaging/_musllinux.py,sha256=p9ZqNYiOItGee8KcZFeHF_YcdhVwGHdK6r-8lgixvGQ,2694
444
+ pip/_vendor/packaging/_parser.py,sha256=gYfnj0pRHflVc4RHZit13KNTyN9iiVcU2RUCGi22BwM,10221
445
+ pip/_vendor/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
446
+ pip/_vendor/packaging/_tokenizer.py,sha256=OYzt7qKxylOAJ-q0XyK1qAycyPRYLfMPdGQKRXkZWyI,5310
447
+ pip/_vendor/packaging/licenses/__init__.py,sha256=3bx-gryo4sRv5LsrwApouy65VIs3u6irSORJzALkrzU,5727
448
+ pip/_vendor/packaging/licenses/__pycache__/__init__.cpython-313.pyc,,
449
+ pip/_vendor/packaging/licenses/__pycache__/_spdx.cpython-313.pyc,,
450
+ pip/_vendor/packaging/licenses/_spdx.py,sha256=oAm1ztPFwlsmCKe7lAAsv_OIOfS1cWDu9bNBkeu-2ns,48398
451
+ pip/_vendor/packaging/markers.py,sha256=P0we27jm1xUzgGMJxBjtUFCIWeBxTsMeJTOJ6chZmAY,12049
452
+ pip/_vendor/packaging/metadata.py,sha256=8IZErqQQnNm53dZZuYq4FGU4_dpyinMeH1QFBIWIkfE,34739
453
+ pip/_vendor/packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
454
+ pip/_vendor/packaging/requirements.py,sha256=gYyRSAdbrIyKDY66ugIDUQjRMvxkH2ALioTmX3tnL6o,2947
455
+ pip/_vendor/packaging/specifiers.py,sha256=yc9D_MycJEmwUpZvcs1OZL9HfiNFmyw0RZaeHRNHkPw,40079
456
+ pip/_vendor/packaging/tags.py,sha256=41s97W9Zatrq2Ed7Rc3qeBDaHe8pKKvYq2mGjwahfXk,22745
457
+ pip/_vendor/packaging/utils.py,sha256=0F3Hh9OFuRgrhTgGZUl5K22Fv1YP2tZl1z_2gO6kJiA,5050
458
+ pip/_vendor/packaging/version.py,sha256=oiHqzTUv_p12hpjgsLDVcaF5hT7pDaSOViUNMD4GTW0,16688
459
+ pip/_vendor/pkg_resources/__init__.py,sha256=jrhDRbOubP74QuPXxd7U7Po42PH2l-LZ2XfcO7llpZ4,124463
460
+ pip/_vendor/pkg_resources/__pycache__/__init__.cpython-313.pyc,,
461
+ pip/_vendor/platformdirs/__init__.py,sha256=UfeSHWl8AeTtbOBOoHAxK4dODOWkZtfy-m_i7cWdJ8c,22344
462
+ pip/_vendor/platformdirs/__main__.py,sha256=jBJ8zb7Mpx5ebcqF83xrpO94MaeCpNGHVf9cvDN2JLg,1505
463
+ pip/_vendor/platformdirs/__pycache__/__init__.cpython-313.pyc,,
464
+ pip/_vendor/platformdirs/__pycache__/__main__.cpython-313.pyc,,
465
+ pip/_vendor/platformdirs/__pycache__/android.cpython-313.pyc,,
466
+ pip/_vendor/platformdirs/__pycache__/api.cpython-313.pyc,,
467
+ pip/_vendor/platformdirs/__pycache__/macos.cpython-313.pyc,,
468
+ pip/_vendor/platformdirs/__pycache__/unix.cpython-313.pyc,,
469
+ pip/_vendor/platformdirs/__pycache__/version.cpython-313.pyc,,
470
+ pip/_vendor/platformdirs/__pycache__/windows.cpython-313.pyc,,
471
+ pip/_vendor/platformdirs/android.py,sha256=r0DshVBf-RO1jXJGX8C4Til7F1XWt-bkdWMgmvEiaYg,9013
472
+ pip/_vendor/platformdirs/api.py,sha256=U9EzI3EYxcXWUCtIGRllqrcN99i2LSY1mq2-GtsUwEQ,9277
473
+ pip/_vendor/platformdirs/macos.py,sha256=UlbyFZ8Rzu3xndCqQEHrfsYTeHwYdFap1Ioz-yxveT4,6154
474
+ pip/_vendor/platformdirs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
475
+ pip/_vendor/platformdirs/unix.py,sha256=WZmkUA--L3JNRGmz32s35YfoD3ica6xKIPdCV_HhLcs,10458
476
+ pip/_vendor/platformdirs/version.py,sha256=0fnw4ljascx7O5PfIeZ2yj6w3pAkqwp099vDcivxuvY,511
477
+ pip/_vendor/platformdirs/windows.py,sha256=IFpiohUBwxPtCzlyKwNtxyW4Jk8haa6W8o59mfrDXVo,10125
478
+ pip/_vendor/pygments/__init__.py,sha256=qMm7-KYqNpMrmjymZaqfH-_9iJtjnexAKodkb9G5D5g,2983
479
+ pip/_vendor/pygments/__main__.py,sha256=WrndpSe6i1ckX_SQ1KaxD9CTKGzD0EuCOFxcbwFpoLU,353
480
+ pip/_vendor/pygments/__pycache__/__init__.cpython-313.pyc,,
481
+ pip/_vendor/pygments/__pycache__/__main__.cpython-313.pyc,,
482
+ pip/_vendor/pygments/__pycache__/console.cpython-313.pyc,,
483
+ pip/_vendor/pygments/__pycache__/filter.cpython-313.pyc,,
484
+ pip/_vendor/pygments/__pycache__/formatter.cpython-313.pyc,,
485
+ pip/_vendor/pygments/__pycache__/lexer.cpython-313.pyc,,
486
+ pip/_vendor/pygments/__pycache__/modeline.cpython-313.pyc,,
487
+ pip/_vendor/pygments/__pycache__/plugin.cpython-313.pyc,,
488
+ pip/_vendor/pygments/__pycache__/regexopt.cpython-313.pyc,,
489
+ pip/_vendor/pygments/__pycache__/scanner.cpython-313.pyc,,
490
+ pip/_vendor/pygments/__pycache__/sphinxext.cpython-313.pyc,,
491
+ pip/_vendor/pygments/__pycache__/style.cpython-313.pyc,,
492
+ pip/_vendor/pygments/__pycache__/token.cpython-313.pyc,,
493
+ pip/_vendor/pygments/__pycache__/unistring.cpython-313.pyc,,
494
+ pip/_vendor/pygments/__pycache__/util.cpython-313.pyc,,
495
+ pip/_vendor/pygments/console.py,sha256=AagDWqwea2yBWf10KC9ptBgMpMjxKp8yABAmh-NQOVk,1718
496
+ pip/_vendor/pygments/filter.py,sha256=YLtpTnZiu07nY3oK9nfR6E9Y1FBHhP5PX8gvkJWcfag,1910
497
+ pip/_vendor/pygments/filters/__init__.py,sha256=4U4jtA0X3iP83uQnB9-TI-HDSw8E8y8zMYHa0UjbbaI,40392
498
+ pip/_vendor/pygments/filters/__pycache__/__init__.cpython-313.pyc,,
499
+ pip/_vendor/pygments/formatter.py,sha256=KZQMmyo_xkOIkQG8g66LYEkBh1bx7a0HyGCBcvhI9Ew,4390
500
+ pip/_vendor/pygments/formatters/__init__.py,sha256=KTwBmnXlaopJhQDOemVHYHskiDghuq-08YtP6xPNJPg,5385
501
+ pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-313.pyc,,
502
+ pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-313.pyc,,
503
+ pip/_vendor/pygments/formatters/_mapping.py,sha256=1Cw37FuQlNacnxRKmtlPX4nyLoX9_ttko5ZwscNUZZ4,4176
504
+ pip/_vendor/pygments/lexer.py,sha256=_kBrOJ_NT5Tl0IVM0rA9c8eysP6_yrlGzEQI0eVYB-A,35349
505
+ pip/_vendor/pygments/lexers/__init__.py,sha256=wbIME35GH7bI1B9rNPJFqWT-ij_RApZDYPUlZycaLzA,12115
506
+ pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-313.pyc,,
507
+ pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-313.pyc,,
508
+ pip/_vendor/pygments/lexers/__pycache__/python.cpython-313.pyc,,
509
+ pip/_vendor/pygments/lexers/_mapping.py,sha256=l4tCXM8e9aPC2BD6sjIr0deT-J-z5tHgCwL-p1fS0PE,77602
510
+ pip/_vendor/pygments/lexers/python.py,sha256=vxjn1cOHclIKJKxoyiBsQTY65GHbkZtZRuKQ2AVCKaw,53853
511
+ pip/_vendor/pygments/modeline.py,sha256=K5eSkR8GS1r5OkXXTHOcV0aM_6xpk9eWNEIAW-OOJ2g,1005
512
+ pip/_vendor/pygments/plugin.py,sha256=tPx0rJCTIZ9ioRgLNYG4pifCbAwTRUZddvLw-NfAk2w,1891
513
+ pip/_vendor/pygments/regexopt.py,sha256=wXaP9Gjp_hKAdnICqoDkRxAOQJSc4v3X6mcxx3z-TNs,3072
514
+ pip/_vendor/pygments/scanner.py,sha256=nNcETRR1tRuiTaHmHSTTECVYFPcLf6mDZu1e4u91A9E,3092
515
+ pip/_vendor/pygments/sphinxext.py,sha256=5x7Zh9YlU6ISJ31dMwduiaanb5dWZnKg3MyEQsseNnQ,7981
516
+ pip/_vendor/pygments/style.py,sha256=PlOZqlsnTVd58RGy50vkA2cXQ_lP5bF5EGMEBTno6DA,6420
517
+ pip/_vendor/pygments/styles/__init__.py,sha256=x9ebctfyvCAFpMTlMJ5YxwcNYBzjgq6zJaKkNm78r4M,2042
518
+ pip/_vendor/pygments/styles/__pycache__/__init__.cpython-313.pyc,,
519
+ pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-313.pyc,,
520
+ pip/_vendor/pygments/styles/_mapping.py,sha256=6lovFUE29tz6EsV3XYY4hgozJ7q1JL7cfO3UOlgnS8w,3312
521
+ pip/_vendor/pygments/token.py,sha256=WbdWGhYm_Vosb0DDxW9lHNPgITXfWTsQmHt6cy9RbcM,6226
522
+ pip/_vendor/pygments/unistring.py,sha256=al-_rBemRuGvinsrM6atNsHTmJ6DUbw24q2O2Ru1cBc,63208
523
+ pip/_vendor/pygments/util.py,sha256=oRtSpiAo5jM9ulntkvVbgXUdiAW57jnuYGB7t9fYuhc,10031
524
+ pip/_vendor/pyproject_hooks/__init__.py,sha256=cPB_a9LXz5xvsRbX1o2qyAdjLatZJdQ_Lc5McNX-X7Y,691
525
+ pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-313.pyc,,
526
+ pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-313.pyc,,
527
+ pip/_vendor/pyproject_hooks/_impl.py,sha256=jY-raxnmyRyB57ruAitrJRUzEexuAhGTpgMygqx67Z4,14936
528
+ pip/_vendor/pyproject_hooks/_in_process/__init__.py,sha256=MJNPpfIxcO-FghxpBbxkG1rFiQf6HOUbV4U5mq0HFns,557
529
+ pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-313.pyc,,
530
+ pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-313.pyc,,
531
+ pip/_vendor/pyproject_hooks/_in_process/_in_process.py,sha256=qcXMhmx__MIJq10gGHW3mA4Tl8dy8YzHMccwnNoKlw0,12216
532
+ pip/_vendor/pyproject_hooks/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
533
+ pip/_vendor/requests/__init__.py,sha256=HlB_HzhrzGtfD_aaYUwUh1zWXLZ75_YCLyit75d0Vz8,5057
534
+ pip/_vendor/requests/__pycache__/__init__.cpython-313.pyc,,
535
+ pip/_vendor/requests/__pycache__/__version__.cpython-313.pyc,,
536
+ pip/_vendor/requests/__pycache__/_internal_utils.cpython-313.pyc,,
537
+ pip/_vendor/requests/__pycache__/adapters.cpython-313.pyc,,
538
+ pip/_vendor/requests/__pycache__/api.cpython-313.pyc,,
539
+ pip/_vendor/requests/__pycache__/auth.cpython-313.pyc,,
540
+ pip/_vendor/requests/__pycache__/certs.cpython-313.pyc,,
541
+ pip/_vendor/requests/__pycache__/compat.cpython-313.pyc,,
542
+ pip/_vendor/requests/__pycache__/cookies.cpython-313.pyc,,
543
+ pip/_vendor/requests/__pycache__/exceptions.cpython-313.pyc,,
544
+ pip/_vendor/requests/__pycache__/help.cpython-313.pyc,,
545
+ pip/_vendor/requests/__pycache__/hooks.cpython-313.pyc,,
546
+ pip/_vendor/requests/__pycache__/models.cpython-313.pyc,,
547
+ pip/_vendor/requests/__pycache__/packages.cpython-313.pyc,,
548
+ pip/_vendor/requests/__pycache__/sessions.cpython-313.pyc,,
549
+ pip/_vendor/requests/__pycache__/status_codes.cpython-313.pyc,,
550
+ pip/_vendor/requests/__pycache__/structures.cpython-313.pyc,,
551
+ pip/_vendor/requests/__pycache__/utils.cpython-313.pyc,,
552
+ pip/_vendor/requests/__version__.py,sha256=FVfglgZmNQnmYPXpOohDU58F5EUb_-VnSTaAesS187g,435
553
+ pip/_vendor/requests/_internal_utils.py,sha256=nMQymr4hs32TqVo5AbCrmcJEhvPUh7xXlluyqwslLiQ,1495
554
+ pip/_vendor/requests/adapters.py,sha256=J7VeVxKBvawbtlX2DERVo05J9BXTcWYLMHNd1Baa-bk,27607
555
+ pip/_vendor/requests/api.py,sha256=_Zb9Oa7tzVIizTKwFrPjDEY9ejtm_OnSRERnADxGsQs,6449
556
+ pip/_vendor/requests/auth.py,sha256=kF75tqnLctZ9Mf_hm9TZIj4cQWnN5uxRz8oWsx5wmR0,10186
557
+ pip/_vendor/requests/certs.py,sha256=kHDlkK_beuHXeMPc5jta2wgl8gdKeUWt5f2nTDVrvt8,441
558
+ pip/_vendor/requests/compat.py,sha256=Mo9f9xZpefod8Zm-n9_StJcVTmwSukXR2p3IQyyVXvU,1485
559
+ pip/_vendor/requests/cookies.py,sha256=bNi-iqEj4NPZ00-ob-rHvzkvObzN3lEpgw3g6paS3Xw,18590
560
+ pip/_vendor/requests/exceptions.py,sha256=D1wqzYWne1mS2rU43tP9CeN1G7QAy7eqL9o1god6Ejw,4272
561
+ pip/_vendor/requests/help.py,sha256=hRKaf9u0G7fdwrqMHtF3oG16RKktRf6KiwtSq2Fo1_0,3813
562
+ pip/_vendor/requests/hooks.py,sha256=CiuysiHA39V5UfcCBXFIx83IrDpuwfN9RcTUgv28ftQ,733
563
+ pip/_vendor/requests/models.py,sha256=x4K4CmH-lC0l2Kb-iPfMN4dRXxHEcbOaEWBL_i09AwI,35483
564
+ pip/_vendor/requests/packages.py,sha256=_ZQDCJTJ8SP3kVWunSqBsRZNPzj2c1WFVqbdr08pz3U,1057
565
+ pip/_vendor/requests/sessions.py,sha256=ykTI8UWGSltOfH07HKollH7kTBGw4WhiBVaQGmckTw4,30495
566
+ pip/_vendor/requests/status_codes.py,sha256=iJUAeA25baTdw-6PfD0eF4qhpINDJRJI-yaMqxs4LEI,4322
567
+ pip/_vendor/requests/structures.py,sha256=-IbmhVz06S-5aPSZuUthZ6-6D9XOjRuTXHOabY041XM,2912
568
+ pip/_vendor/requests/utils.py,sha256=L79vnFbzJ3SFLKtJwpoWe41Tozi3RlZv94pY1TFIyow,33631
569
+ pip/_vendor/resolvelib/__init__.py,sha256=4LcBWHMH317EKEkpV5XLVnqiU1lrmCiygjsADuCgz4s,541
570
+ pip/_vendor/resolvelib/__pycache__/__init__.cpython-313.pyc,,
571
+ pip/_vendor/resolvelib/__pycache__/providers.cpython-313.pyc,,
572
+ pip/_vendor/resolvelib/__pycache__/reporters.cpython-313.pyc,,
573
+ pip/_vendor/resolvelib/__pycache__/structs.cpython-313.pyc,,
574
+ pip/_vendor/resolvelib/providers.py,sha256=pIWJbIdJJ9GFtNbtwTH0Ia43Vj6hYCEJj2DOLue15FM,8914
575
+ pip/_vendor/resolvelib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
576
+ pip/_vendor/resolvelib/reporters.py,sha256=8BNa7G9cKW4Lie4BhDhd7Z57J_Vlb1CYPGSgVN2erMA,2038
577
+ pip/_vendor/resolvelib/resolvers/__init__.py,sha256=GMYuhrbSsYTIjOij0tuJKLvlk6UXmp3nXQetn2sOvpQ,640
578
+ pip/_vendor/resolvelib/resolvers/__pycache__/__init__.cpython-313.pyc,,
579
+ pip/_vendor/resolvelib/resolvers/__pycache__/abstract.cpython-313.pyc,,
580
+ pip/_vendor/resolvelib/resolvers/__pycache__/criterion.cpython-313.pyc,,
581
+ pip/_vendor/resolvelib/resolvers/__pycache__/exceptions.cpython-313.pyc,,
582
+ pip/_vendor/resolvelib/resolvers/__pycache__/resolution.cpython-313.pyc,,
583
+ pip/_vendor/resolvelib/resolvers/abstract.py,sha256=jZOBVigE4PUub9i3F-bTvBwaIXX8S9EU3CGASBvFqEU,1558
584
+ pip/_vendor/resolvelib/resolvers/criterion.py,sha256=lcmZGv5sKHOnFD_RzZwvlGSj19MeA-5rCMpdf2Sgw7Y,1768
585
+ pip/_vendor/resolvelib/resolvers/exceptions.py,sha256=ln_jaQtgLlRUSFY627yiHG2gD7AgaXzRKaElFVh7fDQ,1768
586
+ pip/_vendor/resolvelib/resolvers/resolution.py,sha256=yQegMuOmlzAElLLpgD-k6NbPDMCQf29rWhiFC26OdkM,20671
587
+ pip/_vendor/resolvelib/structs.py,sha256=pu-EJiR2IBITr2SQeNPRa0rXhjlStfmO_GEgAhr3004,6420
588
+ pip/_vendor/rich/__init__.py,sha256=dRxjIL-SbFVY0q3IjSMrfgBTHrm1LZDgLOygVBwiYZc,6090
589
+ pip/_vendor/rich/__main__.py,sha256=eO7Cq8JnrgG8zVoeImiAs92q3hXNMIfp0w5lMsO7Q2Y,8477
590
+ pip/_vendor/rich/__pycache__/__init__.cpython-313.pyc,,
591
+ pip/_vendor/rich/__pycache__/__main__.cpython-313.pyc,,
592
+ pip/_vendor/rich/__pycache__/_cell_widths.cpython-313.pyc,,
593
+ pip/_vendor/rich/__pycache__/_emoji_codes.cpython-313.pyc,,
594
+ pip/_vendor/rich/__pycache__/_emoji_replace.cpython-313.pyc,,
595
+ pip/_vendor/rich/__pycache__/_export_format.cpython-313.pyc,,
596
+ pip/_vendor/rich/__pycache__/_extension.cpython-313.pyc,,
597
+ pip/_vendor/rich/__pycache__/_fileno.cpython-313.pyc,,
598
+ pip/_vendor/rich/__pycache__/_inspect.cpython-313.pyc,,
599
+ pip/_vendor/rich/__pycache__/_log_render.cpython-313.pyc,,
600
+ pip/_vendor/rich/__pycache__/_loop.cpython-313.pyc,,
601
+ pip/_vendor/rich/__pycache__/_null_file.cpython-313.pyc,,
602
+ pip/_vendor/rich/__pycache__/_palettes.cpython-313.pyc,,
603
+ pip/_vendor/rich/__pycache__/_pick.cpython-313.pyc,,
604
+ pip/_vendor/rich/__pycache__/_ratio.cpython-313.pyc,,
605
+ pip/_vendor/rich/__pycache__/_spinners.cpython-313.pyc,,
606
+ pip/_vendor/rich/__pycache__/_stack.cpython-313.pyc,,
607
+ pip/_vendor/rich/__pycache__/_timer.cpython-313.pyc,,
608
+ pip/_vendor/rich/__pycache__/_win32_console.cpython-313.pyc,,
609
+ pip/_vendor/rich/__pycache__/_windows.cpython-313.pyc,,
610
+ pip/_vendor/rich/__pycache__/_windows_renderer.cpython-313.pyc,,
611
+ pip/_vendor/rich/__pycache__/_wrap.cpython-313.pyc,,
612
+ pip/_vendor/rich/__pycache__/abc.cpython-313.pyc,,
613
+ pip/_vendor/rich/__pycache__/align.cpython-313.pyc,,
614
+ pip/_vendor/rich/__pycache__/ansi.cpython-313.pyc,,
615
+ pip/_vendor/rich/__pycache__/bar.cpython-313.pyc,,
616
+ pip/_vendor/rich/__pycache__/box.cpython-313.pyc,,
617
+ pip/_vendor/rich/__pycache__/cells.cpython-313.pyc,,
618
+ pip/_vendor/rich/__pycache__/color.cpython-313.pyc,,
619
+ pip/_vendor/rich/__pycache__/color_triplet.cpython-313.pyc,,
620
+ pip/_vendor/rich/__pycache__/columns.cpython-313.pyc,,
621
+ pip/_vendor/rich/__pycache__/console.cpython-313.pyc,,
622
+ pip/_vendor/rich/__pycache__/constrain.cpython-313.pyc,,
623
+ pip/_vendor/rich/__pycache__/containers.cpython-313.pyc,,
624
+ pip/_vendor/rich/__pycache__/control.cpython-313.pyc,,
625
+ pip/_vendor/rich/__pycache__/default_styles.cpython-313.pyc,,
626
+ pip/_vendor/rich/__pycache__/diagnose.cpython-313.pyc,,
627
+ pip/_vendor/rich/__pycache__/emoji.cpython-313.pyc,,
628
+ pip/_vendor/rich/__pycache__/errors.cpython-313.pyc,,
629
+ pip/_vendor/rich/__pycache__/file_proxy.cpython-313.pyc,,
630
+ pip/_vendor/rich/__pycache__/filesize.cpython-313.pyc,,
631
+ pip/_vendor/rich/__pycache__/highlighter.cpython-313.pyc,,
632
+ pip/_vendor/rich/__pycache__/json.cpython-313.pyc,,
633
+ pip/_vendor/rich/__pycache__/jupyter.cpython-313.pyc,,
634
+ pip/_vendor/rich/__pycache__/layout.cpython-313.pyc,,
635
+ pip/_vendor/rich/__pycache__/live.cpython-313.pyc,,
636
+ pip/_vendor/rich/__pycache__/live_render.cpython-313.pyc,,
637
+ pip/_vendor/rich/__pycache__/logging.cpython-313.pyc,,
638
+ pip/_vendor/rich/__pycache__/markup.cpython-313.pyc,,
639
+ pip/_vendor/rich/__pycache__/measure.cpython-313.pyc,,
640
+ pip/_vendor/rich/__pycache__/padding.cpython-313.pyc,,
641
+ pip/_vendor/rich/__pycache__/pager.cpython-313.pyc,,
642
+ pip/_vendor/rich/__pycache__/palette.cpython-313.pyc,,
643
+ pip/_vendor/rich/__pycache__/panel.cpython-313.pyc,,
644
+ pip/_vendor/rich/__pycache__/pretty.cpython-313.pyc,,
645
+ pip/_vendor/rich/__pycache__/progress.cpython-313.pyc,,
646
+ pip/_vendor/rich/__pycache__/progress_bar.cpython-313.pyc,,
647
+ pip/_vendor/rich/__pycache__/prompt.cpython-313.pyc,,
648
+ pip/_vendor/rich/__pycache__/protocol.cpython-313.pyc,,
649
+ pip/_vendor/rich/__pycache__/region.cpython-313.pyc,,
650
+ pip/_vendor/rich/__pycache__/repr.cpython-313.pyc,,
651
+ pip/_vendor/rich/__pycache__/rule.cpython-313.pyc,,
652
+ pip/_vendor/rich/__pycache__/scope.cpython-313.pyc,,
653
+ pip/_vendor/rich/__pycache__/screen.cpython-313.pyc,,
654
+ pip/_vendor/rich/__pycache__/segment.cpython-313.pyc,,
655
+ pip/_vendor/rich/__pycache__/spinner.cpython-313.pyc,,
656
+ pip/_vendor/rich/__pycache__/status.cpython-313.pyc,,
657
+ pip/_vendor/rich/__pycache__/style.cpython-313.pyc,,
658
+ pip/_vendor/rich/__pycache__/styled.cpython-313.pyc,,
659
+ pip/_vendor/rich/__pycache__/syntax.cpython-313.pyc,,
660
+ pip/_vendor/rich/__pycache__/table.cpython-313.pyc,,
661
+ pip/_vendor/rich/__pycache__/terminal_theme.cpython-313.pyc,,
662
+ pip/_vendor/rich/__pycache__/text.cpython-313.pyc,,
663
+ pip/_vendor/rich/__pycache__/theme.cpython-313.pyc,,
664
+ pip/_vendor/rich/__pycache__/themes.cpython-313.pyc,,
665
+ pip/_vendor/rich/__pycache__/traceback.cpython-313.pyc,,
666
+ pip/_vendor/rich/__pycache__/tree.cpython-313.pyc,,
667
+ pip/_vendor/rich/_cell_widths.py,sha256=fbmeyetEdHjzE_Vx2l1uK7tnPOhMs2X1lJfO3vsKDpA,10209
668
+ pip/_vendor/rich/_emoji_codes.py,sha256=hu1VL9nbVdppJrVoijVshRlcRRe_v3dju3Mmd2sKZdY,140235
669
+ pip/_vendor/rich/_emoji_replace.py,sha256=n-kcetsEUx2ZUmhQrfeMNc-teeGhpuSQ5F8VPBsyvDo,1064
670
+ pip/_vendor/rich/_export_format.py,sha256=RI08pSrm5tBSzPMvnbTqbD9WIalaOoN5d4M1RTmLq1Y,2128
671
+ pip/_vendor/rich/_extension.py,sha256=Xt47QacCKwYruzjDi-gOBq724JReDj9Cm9xUi5fr-34,265
672
+ pip/_vendor/rich/_fileno.py,sha256=HWZxP5C2ajMbHryvAQZseflVfQoGzsKOHzKGsLD8ynQ,799
673
+ pip/_vendor/rich/_inspect.py,sha256=QM05lEFnFoTaFqpnbx-zBEI6k8oIKrD3cvjEOQNhKig,9655
674
+ pip/_vendor/rich/_log_render.py,sha256=1ByI0PA1ZpxZY3CGJOK54hjlq4X-Bz_boIjIqCd8Kns,3225
675
+ pip/_vendor/rich/_loop.py,sha256=hV_6CLdoPm0va22Wpw4zKqM0RYsz3TZxXj0PoS-9eDQ,1236
676
+ pip/_vendor/rich/_null_file.py,sha256=ADGKp1yt-k70FMKV6tnqCqecB-rSJzp-WQsD7LPL-kg,1394
677
+ pip/_vendor/rich/_palettes.py,sha256=cdev1JQKZ0JvlguV9ipHgznTdnvlIzUFDBb0It2PzjI,7063
678
+ pip/_vendor/rich/_pick.py,sha256=evDt8QN4lF5CiwrUIXlOJCntitBCOsI3ZLPEIAVRLJU,423
679
+ pip/_vendor/rich/_ratio.py,sha256=Zt58apszI6hAAcXPpgdWKpu3c31UBWebOeR4mbyptvU,5471
680
+ pip/_vendor/rich/_spinners.py,sha256=U2r1_g_1zSjsjiUdAESc2iAMc3i4ri_S8PYP6kQ5z1I,19919
681
+ pip/_vendor/rich/_stack.py,sha256=-C8OK7rxn3sIUdVwxZBBpeHhIzX0eI-VM3MemYfaXm0,351
682
+ pip/_vendor/rich/_timer.py,sha256=zelxbT6oPFZnNrwWPpc1ktUeAT-Vc4fuFcRZLQGLtMI,417
683
+ pip/_vendor/rich/_win32_console.py,sha256=BSaDRIMwBLITn_m0mTRLPqME5q-quGdSMuYMpYeYJwc,22755
684
+ pip/_vendor/rich/_windows.py,sha256=aBwaD_S56SbgopIvayVmpk0Y28uwY2C5Bab1wl3Bp-I,1925
685
+ pip/_vendor/rich/_windows_renderer.py,sha256=t74ZL3xuDCP3nmTp9pH1L5LiI2cakJuQRQleHCJerlk,2783
686
+ pip/_vendor/rich/_wrap.py,sha256=FlSsom5EX0LVkA3KWy34yHnCfLtqX-ZIepXKh-70rpc,3404
687
+ pip/_vendor/rich/abc.py,sha256=ON-E-ZqSSheZ88VrKX2M3PXpFbGEUUZPMa_Af0l-4f0,890
688
+ pip/_vendor/rich/align.py,sha256=Rh-3adnDaN1Ao07EjR2PhgE62PGLPgO8SMwJBku1urQ,10469
689
+ pip/_vendor/rich/ansi.py,sha256=Avs1LHbSdcyOvDOdpELZUoULcBiYewY76eNBp6uFBhs,6921
690
+ pip/_vendor/rich/bar.py,sha256=ldbVHOzKJOnflVNuv1xS7g6dLX2E3wMnXkdPbpzJTcs,3263
691
+ pip/_vendor/rich/box.py,sha256=nr5fYIUghB_iUCEq6y0Z3LlCT8gFPDrzN9u2kn7tJl4,10831
692
+ pip/_vendor/rich/cells.py,sha256=KrQkj5-LghCCpJLSNQIyAZjndc4bnEqOEmi5YuZ9UCY,5130
693
+ pip/_vendor/rich/color.py,sha256=3HSULVDj7qQkXUdFWv78JOiSZzfy5y1nkcYhna296V0,18211
694
+ pip/_vendor/rich/color_triplet.py,sha256=3lhQkdJbvWPoLDO-AnYImAWmJvV5dlgYNCVZ97ORaN4,1054
695
+ pip/_vendor/rich/columns.py,sha256=HUX0KcMm9dsKNi11fTbiM_h2iDtl8ySCaVcxlalEzq8,7131
696
+ pip/_vendor/rich/console.py,sha256=_RJizBQIn9qxr4Ln7Q_SC5N9ekPWPAxH0KGVxsgg69Y,100565
697
+ pip/_vendor/rich/constrain.py,sha256=1VIPuC8AgtKWrcncQrjBdYqA3JVWysu6jZo1rrh7c7Q,1288
698
+ pip/_vendor/rich/containers.py,sha256=c_56TxcedGYqDepHBMTuZdUIijitAQgnox-Qde0Z1qo,5502
699
+ pip/_vendor/rich/control.py,sha256=DSkHTUQLorfSERAKE_oTAEUFefZnZp4bQb4q8rHbKws,6630
700
+ pip/_vendor/rich/default_styles.py,sha256=khQFqqaoDs3bprMqWpHw8nO5UpG2DN6QtuTd6LzZwYc,8257
701
+ pip/_vendor/rich/diagnose.py,sha256=WNPjU2pEdrPICJ24KOaTD_hzP839qArpmF1JIM5x_EQ,998
702
+ pip/_vendor/rich/emoji.py,sha256=omTF9asaAnsM4yLY94eR_9dgRRSm1lHUszX20D1yYCQ,2501
703
+ pip/_vendor/rich/errors.py,sha256=5pP3Kc5d4QJ_c0KFsxrfyhjiPVe7J1zOqSFbFAzcV-Y,642
704
+ pip/_vendor/rich/file_proxy.py,sha256=Tl9THMDZ-Pk5Wm8sI1gGg_U5DhusmxD-FZ0fUbcU0W0,1683
705
+ pip/_vendor/rich/filesize.py,sha256=_iz9lIpRgvW7MNSeCZnLg-HwzbP4GETg543WqD8SFs0,2484
706
+ pip/_vendor/rich/highlighter.py,sha256=G_sn-8DKjM1sEjLG_oc4ovkWmiUpWvj8bXi0yed2LnY,9586
707
+ pip/_vendor/rich/json.py,sha256=vVEoKdawoJRjAFayPwXkMBPLy7RSTs-f44wSQDR2nJ0,5031
708
+ pip/_vendor/rich/jupyter.py,sha256=QyoKoE_8IdCbrtiSHp9TsTSNyTHY0FO5whE7jOTd9UE,3252
709
+ pip/_vendor/rich/layout.py,sha256=ajkSFAtEVv9EFTcFs-w4uZfft7nEXhNzL7ZVdgrT5rI,14004
710
+ pip/_vendor/rich/live.py,sha256=DhzAPEnjTxQuq9_0Y2xh2MUwQcP_aGPkenLfKETslwM,14270
711
+ pip/_vendor/rich/live_render.py,sha256=zJtB471jGziBtEwxc54x12wEQtH4BuQr1SA8v9kU82w,3666
712
+ pip/_vendor/rich/logging.py,sha256=ZgpKMMBY_BuMAI_BYzo-UtXak6t5oH9VK8m9Q2Lm0f4,12458
713
+ pip/_vendor/rich/markup.py,sha256=3euGKP5s41NCQwaSjTnJxus5iZMHjxpIM0W6fCxra38,8451
714
+ pip/_vendor/rich/measure.py,sha256=HmrIJX8sWRTHbgh8MxEay_83VkqNW_70s8aKP5ZcYI8,5305
715
+ pip/_vendor/rich/padding.py,sha256=KVEI3tOwo9sgK1YNSuH__M1_jUWmLZwRVV_KmOtVzyM,4908
716
+ pip/_vendor/rich/pager.py,sha256=SO_ETBFKbg3n_AgOzXm41Sv36YxXAyI3_R-KOY2_uSc,828
717
+ pip/_vendor/rich/palette.py,sha256=lInvR1ODDT2f3UZMfL1grq7dY_pDdKHw4bdUgOGaM4Y,3396
718
+ pip/_vendor/rich/panel.py,sha256=SUDaa3z4MU7vIjzvbi0SXuc6BslDzADwdY1AX4TbTdY,11225
719
+ pip/_vendor/rich/pretty.py,sha256=gy3S72u4FRg2ytoo7N1ZDWDIvB4unbzd5iUGdgm-8fc,36391
720
+ pip/_vendor/rich/progress.py,sha256=MtmCjTk5zYU_XtRHxRHTAEHG6hF9PeF7EMWbEPleIC0,60357
721
+ pip/_vendor/rich/progress_bar.py,sha256=mZTPpJUwcfcdgQCTTz3kyY-fc79ddLwtx6Ghhxfo064,8162
722
+ pip/_vendor/rich/prompt.py,sha256=l0RhQU-0UVTV9e08xW1BbIj0Jq2IXyChX4lC0lFNzt4,12447
723
+ pip/_vendor/rich/protocol.py,sha256=5hHHDDNHckdk8iWH5zEbi-zuIVSF5hbU2jIo47R7lTE,1391
724
+ pip/_vendor/rich/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
725
+ pip/_vendor/rich/region.py,sha256=rNT9xZrVZTYIXZC0NYn41CJQwYNbR-KecPOxTgQvB8Y,166
726
+ pip/_vendor/rich/repr.py,sha256=5MZJZmONgC6kud-QW-_m1okXwL2aR6u6y-pUcUCJz28,4431
727
+ pip/_vendor/rich/rule.py,sha256=0fNaS_aERa3UMRc3T5WMpN_sumtDxfaor2y3of1ftBk,4602
728
+ pip/_vendor/rich/scope.py,sha256=TMUU8qo17thyqQCPqjDLYpg_UU1k5qVd-WwiJvnJVas,2843
729
+ pip/_vendor/rich/screen.py,sha256=YoeReESUhx74grqb0mSSb9lghhysWmFHYhsbMVQjXO8,1591
730
+ pip/_vendor/rich/segment.py,sha256=otnKeKGEV-WRlQVosfJVeFDcDxAKHpvJ_hLzSu5lumM,24743
731
+ pip/_vendor/rich/spinner.py,sha256=PT5qgXPG3ZpqRj7n3EZQ6NW56mx3ldZqZCU7gEMyZk4,4364
732
+ pip/_vendor/rich/status.py,sha256=kkPph3YeAZBo-X-4wPp8gTqZyU466NLwZBA4PZTTewo,4424
733
+ pip/_vendor/rich/style.py,sha256=xpj4uMBZMtuNuNomfUiamigl3p1sDvTCZwrG1tcTVeg,27059
734
+ pip/_vendor/rich/styled.py,sha256=eZNnzGrI4ki_54pgY3Oj0T-x3lxdXTYh4_ryDB24wBU,1258
735
+ pip/_vendor/rich/syntax.py,sha256=qqAnEUZ4K57Po81_5RBxnsuU4KRzSdvDPAhKw8ma_3E,35763
736
+ pip/_vendor/rich/table.py,sha256=ZmT7V7MMCOYKw7TGY9SZLyYDf6JdM-WVf07FdVuVhTI,40049
737
+ pip/_vendor/rich/terminal_theme.py,sha256=1j5-ufJfnvlAo5Qsi_ACZiXDmwMXzqgmFByObT9-yJY,3370
738
+ pip/_vendor/rich/text.py,sha256=AO7JPCz6-gaN1thVLXMBntEmDPVYFgFNG1oM61_sanU,47552
739
+ pip/_vendor/rich/theme.py,sha256=oNyhXhGagtDlbDye3tVu3esWOWk0vNkuxFw-_unlaK0,3771
740
+ pip/_vendor/rich/themes.py,sha256=0xgTLozfabebYtcJtDdC5QkX5IVUEaviqDUJJh4YVFk,102
741
+ pip/_vendor/rich/traceback.py,sha256=ZA8Q67DyP5a_stpIh6GPf9IiXj_s3dAhDIr6Zbfkahk,35170
742
+ pip/_vendor/rich/tree.py,sha256=yWnQ6rAvRGJ3qZGqBrxS2SW2TKBTNrP0SdY8QxOFPuw,9451
743
+ pip/_vendor/tomli/__init__.py,sha256=PhNw_eyLgdn7McJ6nrAN8yIm3dXC75vr1sVGVVwDSpA,314
744
+ pip/_vendor/tomli/__pycache__/__init__.cpython-313.pyc,,
745
+ pip/_vendor/tomli/__pycache__/_parser.cpython-313.pyc,,
746
+ pip/_vendor/tomli/__pycache__/_re.cpython-313.pyc,,
747
+ pip/_vendor/tomli/__pycache__/_types.cpython-313.pyc,,
748
+ pip/_vendor/tomli/_parser.py,sha256=9w8LG0jB7fwmZZWB0vVXbeejDHcl4ANIJxB2scEnDlA,25591
749
+ pip/_vendor/tomli/_re.py,sha256=sh4sBDRgO94KJZwNIrgdcyV_qQast50YvzOAUGpRDKA,3171
750
+ pip/_vendor/tomli/_types.py,sha256=-GTG2VUqkpxwMqzmVO4F7ybKddIbAnuAHXfmWQcTi3Q,254
751
+ pip/_vendor/tomli/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
752
+ pip/_vendor/tomli_w/__init__.py,sha256=0F8yDtXx3Uunhm874KrAcP76srsM98y7WyHQwCulZbo,169
753
+ pip/_vendor/tomli_w/__pycache__/__init__.cpython-313.pyc,,
754
+ pip/_vendor/tomli_w/__pycache__/_writer.cpython-313.pyc,,
755
+ pip/_vendor/tomli_w/_writer.py,sha256=dsifFS2xYf1i76mmRyfz9y125xC7Z_HQ845ZKhJsYXs,6961
756
+ pip/_vendor/tomli_w/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
757
+ pip/_vendor/truststore/__init__.py,sha256=2wRSVijjRzPLVXUzWqvdZLNsEOhDfopKLd2EKAYLwKU,1320
758
+ pip/_vendor/truststore/__pycache__/__init__.cpython-313.pyc,,
759
+ pip/_vendor/truststore/__pycache__/_api.cpython-313.pyc,,
760
+ pip/_vendor/truststore/__pycache__/_macos.cpython-313.pyc,,
761
+ pip/_vendor/truststore/__pycache__/_openssl.cpython-313.pyc,,
762
+ pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-313.pyc,,
763
+ pip/_vendor/truststore/__pycache__/_windows.cpython-313.pyc,,
764
+ pip/_vendor/truststore/_api.py,sha256=40I0ojO2DnITiHvOnUYvJ1bfQMBKHOkci14noNxEnCs,11246
765
+ pip/_vendor/truststore/_macos.py,sha256=nZlLkOmszUE0g6ryRwBVGY5COzPyudcsiJtDWarM5LQ,20503
766
+ pip/_vendor/truststore/_openssl.py,sha256=LLUZ7ZGaio-i5dpKKjKCSeSufmn6T8pi9lDcFnvSyq0,2324
767
+ pip/_vendor/truststore/_ssl_constants.py,sha256=NUD4fVKdSD02ri7-db0tnO0VqLP9aHuzmStcW7tAl08,1130
768
+ pip/_vendor/truststore/_windows.py,sha256=rAHyKYD8M7t-bXfG8VgOVa3TpfhVhbt4rZQlO45YuP8,17993
769
+ pip/_vendor/truststore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
770
+ pip/_vendor/typing_extensions.py,sha256=ipKHUPtZCqL6c-HfvGl-9t0opsXcSL72y4GYjyJXs_g,172702
771
+ pip/_vendor/urllib3/__init__.py,sha256=iXLcYiJySn0GNbWOOZDDApgBL1JgP44EZ8i1760S8Mc,3333
772
+ pip/_vendor/urllib3/__pycache__/__init__.cpython-313.pyc,,
773
+ pip/_vendor/urllib3/__pycache__/_collections.cpython-313.pyc,,
774
+ pip/_vendor/urllib3/__pycache__/_version.cpython-313.pyc,,
775
+ pip/_vendor/urllib3/__pycache__/connection.cpython-313.pyc,,
776
+ pip/_vendor/urllib3/__pycache__/connectionpool.cpython-313.pyc,,
777
+ pip/_vendor/urllib3/__pycache__/exceptions.cpython-313.pyc,,
778
+ pip/_vendor/urllib3/__pycache__/fields.cpython-313.pyc,,
779
+ pip/_vendor/urllib3/__pycache__/filepost.cpython-313.pyc,,
780
+ pip/_vendor/urllib3/__pycache__/poolmanager.cpython-313.pyc,,
781
+ pip/_vendor/urllib3/__pycache__/request.cpython-313.pyc,,
782
+ pip/_vendor/urllib3/__pycache__/response.cpython-313.pyc,,
783
+ pip/_vendor/urllib3/_collections.py,sha256=pyASJJhW7wdOpqJj9QJA8FyGRfr8E8uUUhqUvhF0728,11372
784
+ pip/_vendor/urllib3/_version.py,sha256=t9wGB6ooOTXXgiY66K1m6BZS1CJyXHAU8EoWDTe6Shk,64
785
+ pip/_vendor/urllib3/connection.py,sha256=ttIA909BrbTUzwkqEe_TzZVh4JOOj7g61Ysei2mrwGg,20314
786
+ pip/_vendor/urllib3/connectionpool.py,sha256=e2eiAwNbFNCKxj4bwDKNK-w7HIdSz3OmMxU_TIt-evQ,40408
787
+ pip/_vendor/urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
788
+ pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-313.pyc,,
789
+ pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-313.pyc,,
790
+ pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-313.pyc,,
791
+ pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-313.pyc,,
792
+ pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-313.pyc,,
793
+ pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-313.pyc,,
794
+ pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-313.pyc,,
795
+ pip/_vendor/urllib3/contrib/_appengine_environ.py,sha256=bDbyOEhW2CKLJcQqAKAyrEHN-aklsyHFKq6vF8ZFsmk,957
796
+ pip/_vendor/urllib3/contrib/_securetransport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
797
+ pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-313.pyc,,
798
+ pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-313.pyc,,
799
+ pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-313.pyc,,
800
+ pip/_vendor/urllib3/contrib/_securetransport/bindings.py,sha256=4Xk64qIkPBt09A5q-RIFUuDhNc9mXilVapm7WnYnzRw,17632
801
+ pip/_vendor/urllib3/contrib/_securetransport/low_level.py,sha256=B2JBB2_NRP02xK6DCa1Pa9IuxrPwxzDzZbixQkb7U9M,13922
802
+ pip/_vendor/urllib3/contrib/appengine.py,sha256=VR68eAVE137lxTgjBDwCna5UiBZTOKa01Aj_-5BaCz4,11036
803
+ pip/_vendor/urllib3/contrib/ntlmpool.py,sha256=NlfkW7WMdW8ziqudopjHoW299og1BTWi0IeIibquFwk,4528
804
+ pip/_vendor/urllib3/contrib/pyopenssl.py,sha256=hDJh4MhyY_p-oKlFcYcQaVQRDv6GMmBGuW9yjxyeejM,17081
805
+ pip/_vendor/urllib3/contrib/securetransport.py,sha256=Fef1IIUUFHqpevzXiDPbIGkDKchY2FVKeVeLGR1Qq3g,34446
806
+ pip/_vendor/urllib3/contrib/socks.py,sha256=aRi9eWXo9ZEb95XUxef4Z21CFlnnjbEiAo9HOseoMt4,7097
807
+ pip/_vendor/urllib3/exceptions.py,sha256=0Mnno3KHTNfXRfY7638NufOPkUb6mXOm-Lqj-4x2w8A,8217
808
+ pip/_vendor/urllib3/fields.py,sha256=kvLDCg_JmH1lLjUUEY_FLS8UhY7hBvDPuVETbY8mdrM,8579
809
+ pip/_vendor/urllib3/filepost.py,sha256=5b_qqgRHVlL7uLtdAYBzBh-GHmU5AfJVt_2N0XS3PeY,2440
810
+ pip/_vendor/urllib3/packages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
811
+ pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-313.pyc,,
812
+ pip/_vendor/urllib3/packages/__pycache__/six.cpython-313.pyc,,
813
+ pip/_vendor/urllib3/packages/backports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
814
+ pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-313.pyc,,
815
+ pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-313.pyc,,
816
+ pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-313.pyc,,
817
+ pip/_vendor/urllib3/packages/backports/makefile.py,sha256=nbzt3i0agPVP07jqqgjhaYjMmuAi_W5E0EywZivVO8E,1417
818
+ pip/_vendor/urllib3/packages/backports/weakref_finalize.py,sha256=tRCal5OAhNSRyb0DhHp-38AtIlCsRP8BxF3NX-6rqIA,5343
819
+ pip/_vendor/urllib3/packages/six.py,sha256=b9LM0wBXv7E7SrbCjAm4wwN-hrH-iNxv18LgWNMMKPo,34665
820
+ pip/_vendor/urllib3/poolmanager.py,sha256=aWyhXRtNO4JUnCSVVqKTKQd8EXTvUm1VN9pgs2bcONo,19990
821
+ pip/_vendor/urllib3/request.py,sha256=YTWFNr7QIwh7E1W9dde9LM77v2VWTJ5V78XuTTw7D1A,6691
822
+ pip/_vendor/urllib3/response.py,sha256=fmDJAFkG71uFTn-sVSTh2Iw0WmcXQYqkbRjihvwBjU8,30641
823
+ pip/_vendor/urllib3/util/__init__.py,sha256=JEmSmmqqLyaw8P51gUImZh8Gwg9i1zSe-DoqAitn2nc,1155
824
+ pip/_vendor/urllib3/util/__pycache__/__init__.cpython-313.pyc,,
825
+ pip/_vendor/urllib3/util/__pycache__/connection.cpython-313.pyc,,
826
+ pip/_vendor/urllib3/util/__pycache__/proxy.cpython-313.pyc,,
827
+ pip/_vendor/urllib3/util/__pycache__/queue.cpython-313.pyc,,
828
+ pip/_vendor/urllib3/util/__pycache__/request.cpython-313.pyc,,
829
+ pip/_vendor/urllib3/util/__pycache__/response.cpython-313.pyc,,
830
+ pip/_vendor/urllib3/util/__pycache__/retry.cpython-313.pyc,,
831
+ pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-313.pyc,,
832
+ pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-313.pyc,,
833
+ pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-313.pyc,,
834
+ pip/_vendor/urllib3/util/__pycache__/timeout.cpython-313.pyc,,
835
+ pip/_vendor/urllib3/util/__pycache__/url.cpython-313.pyc,,
836
+ pip/_vendor/urllib3/util/__pycache__/wait.cpython-313.pyc,,
837
+ pip/_vendor/urllib3/util/connection.py,sha256=5Lx2B1PW29KxBn2T0xkN1CBgRBa3gGVJBKoQoRogEVk,4901
838
+ pip/_vendor/urllib3/util/proxy.py,sha256=zUvPPCJrp6dOF0N4GAVbOcl6o-4uXKSrGiTkkr5vUS4,1605
839
+ pip/_vendor/urllib3/util/queue.py,sha256=nRgX8_eX-_VkvxoX096QWoz8Ps0QHUAExILCY_7PncM,498
840
+ pip/_vendor/urllib3/util/request.py,sha256=C0OUt2tcU6LRiQJ7YYNP9GvPrSvl7ziIBekQ-5nlBZk,3997
841
+ pip/_vendor/urllib3/util/response.py,sha256=GJpg3Egi9qaJXRwBh5wv-MNuRWan5BIu40oReoxWP28,3510
842
+ pip/_vendor/urllib3/util/retry.py,sha256=6ENvOZ8PBDzh8kgixpql9lIrb2dxH-k7ZmBanJF2Ng4,22050
843
+ pip/_vendor/urllib3/util/ssl_.py,sha256=QDuuTxPSCj1rYtZ4xpD7Ux-r20TD50aHyqKyhQ7Bq4A,17460
844
+ pip/_vendor/urllib3/util/ssl_match_hostname.py,sha256=Ir4cZVEjmAk8gUAIHWSi7wtOO83UCYABY2xFD1Ql_WA,5758
845
+ pip/_vendor/urllib3/util/ssltransport.py,sha256=NA-u5rMTrDFDFC8QzRKUEKMG0561hOD4qBTr3Z4pv6E,6895
846
+ pip/_vendor/urllib3/util/timeout.py,sha256=cwq4dMk87mJHSBktK1miYJ-85G-3T3RmT20v7SFCpno,10168
847
+ pip/_vendor/urllib3/util/url.py,sha256=lCAE7M5myA8EDdW0sJuyyZhVB9K_j38ljWhHAnFaWoE,14296
848
+ pip/_vendor/urllib3/util/wait.py,sha256=fOX0_faozG2P7iVojQoE1mbydweNyTcm-hXEfFrTtLI,5403
849
+ pip/_vendor/vendor.txt,sha256=Fym1hhuw75IJOl33NPi5nIJJc66DioBSUWrVRIVtRUE,373
850
+ pip/py.typed,sha256=EBVvvPRTn_eIpz5e5QztSCdrMX7Qwd7VP93RSoIlZ2I,286
.mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/REQUESTED ADDED
File without changes
.mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/WHEEL ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
.mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/entry_points.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [console_scripts]
2
+ pip = pip._internal.cli.main:main
3
+ pip3 = pip._internal.cli.main:main
.mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/licenses/AUTHORS.txt ADDED
@@ -0,0 +1,821 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @Switch01
2
+ A_Rog
3
+ Aakanksha Agrawal
4
+ Abhinav Sagar
5
+ ABHYUDAY PRATAP SINGH
6
+ abs51295
7
+ AceGentile
8
+ Adam Chainz
9
+ Adam Tse
10
+ Adam Turner
11
+ Adam Wentz
12
+ admin
13
+ Adolfo Ochagavía
14
+ Adrien Morison
15
+ Agus
16
+ ahayrapetyan
17
+ Ahilya
18
+ AinsworthK
19
+ Akash Srivastava
20
+ Alan Yee
21
+ Albert Tugushev
22
+ Albert-Guan
23
+ albertg
24
+ Alberto Sottile
25
+ Aleks Bunin
26
+ Ales Erjavec
27
+ Alethea Flowers
28
+ Alex Gaynor
29
+ Alex Grönholm
30
+ Alex Hedges
31
+ Alex Loosley
32
+ Alex Morega
33
+ Alex Stachowiak
34
+ Alexander Regueiro
35
+ Alexander Shtyrov
36
+ Alexandre Conrad
37
+ Alexey Popravka
38
+ Aleš Erjavec
39
+ Alli
40
+ Ami Fischman
41
+ Ananya Maiti
42
+ Anatoly Techtonik
43
+ Anders Kaseorg
44
+ Andre Aguiar
45
+ Andreas Lutro
46
+ Andrei Geacar
47
+ Andrew Gaul
48
+ Andrew Shymanel
49
+ Andrey Bienkowski
50
+ Andrey Bulgakov
51
+ Andrés Delfino
52
+ Andy Freeland
53
+ Andy Kluger
54
+ Ani Hayrapetyan
55
+ Aniruddha Basak
56
+ Anish Tambe
57
+ Anrs Hu
58
+ Anthony Sottile
59
+ Antoine Musso
60
+ Anton Ovchinnikov
61
+ Anton Patrushev
62
+ Anton Zelenov
63
+ Antonio Alvarado Hernandez
64
+ Antony Lee
65
+ Antti Kaihola
66
+ Anubhav Patel
67
+ Anudit Nagar
68
+ Anuj Godase
69
+ AQNOUCH Mohammed
70
+ AraHaan
71
+ arena
72
+ arenasys
73
+ Arindam Choudhury
74
+ Armin Ronacher
75
+ Arnon Yaari
76
+ Artem
77
+ Arun Babu Neelicattu
78
+ Ashley Manton
79
+ Ashwin Ramaswami
80
+ atse
81
+ Atsushi Odagiri
82
+ Avinash Karhana
83
+ Avner Cohen
84
+ Awit (Ah-Wit) Ghirmai
85
+ Baptiste Mispelon
86
+ Barney Gale
87
+ barneygale
88
+ Bartek Ogryczak
89
+ Bastian Venthur
90
+ Ben Bodenmiller
91
+ Ben Darnell
92
+ Ben Hoyt
93
+ Ben Mares
94
+ Ben Rosser
95
+ Bence Nagy
96
+ Benjamin Peterson
97
+ Benjamin VanEvery
98
+ Benoit Pierre
99
+ Berker Peksag
100
+ Bernard
101
+ Bernard Tyers
102
+ Bernardo B. Marques
103
+ Bernhard M. Wiedemann
104
+ Bertil Hatt
105
+ Bhavam Vidyarthi
106
+ Blazej Michalik
107
+ Bogdan Opanchuk
108
+ BorisZZZ
109
+ Brad Erickson
110
+ Bradley Ayers
111
+ Bradley Reynolds
112
+ Branch Vincent
113
+ Brandon L. Reiss
114
+ Brandt Bucher
115
+ Brannon Dorsey
116
+ Brett Randall
117
+ Brett Rosen
118
+ Brian Cristante
119
+ Brian Rosner
120
+ briantracy
121
+ BrownTruck
122
+ Bruno Oliveira
123
+ Bruno Renié
124
+ Bruno S
125
+ Bstrdsmkr
126
+ Buck Golemon
127
+ burrows
128
+ Bussonnier Matthias
129
+ bwoodsend
130
+ c22
131
+ Caleb Brown
132
+ Caleb Martinez
133
+ Calvin Smith
134
+ Carl Meyer
135
+ Carlos Liam
136
+ Carol Willing
137
+ Carter Thayer
138
+ Cass
139
+ Chandrasekhar Atina
140
+ Charlie Marsh
141
+ charwick
142
+ Chih-Hsuan Yen
143
+ Chris Brinker
144
+ Chris Hunt
145
+ Chris Jerdonek
146
+ Chris Kuehl
147
+ Chris Markiewicz
148
+ Chris McDonough
149
+ Chris Pawley
150
+ Chris Pryer
151
+ Chris Wolfe
152
+ Christian Clauss
153
+ Christian Heimes
154
+ Christian Oudard
155
+ Christoph Reiter
156
+ Christopher Hunt
157
+ Christopher Snyder
158
+ chrysle
159
+ cjc7373
160
+ Clark Boylan
161
+ Claudio Jolowicz
162
+ Clay McClure
163
+ Cody
164
+ Cody Soyland
165
+ Colin Watson
166
+ Collin Anderson
167
+ Connor Osborn
168
+ Cooper Lees
169
+ Cooper Ry Lees
170
+ Cory Benfield
171
+ Cory Wright
172
+ Craig Kerstiens
173
+ Cristian Sorinel
174
+ Cristina
175
+ Cristina Muñoz
176
+ ctg123
177
+ Curtis Doty
178
+ cytolentino
179
+ Daan De Meyer
180
+ Dale
181
+ Damian
182
+ Damian Quiroga
183
+ Damian Shaw
184
+ Dan Black
185
+ Dan Savilonis
186
+ Dan Sully
187
+ Dane Hillard
188
+ daniel
189
+ Daniel Collins
190
+ Daniel Hahler
191
+ Daniel Holth
192
+ Daniel Jost
193
+ Daniel Katz
194
+ Daniel Shaulov
195
+ Daniele Esposti
196
+ Daniele Nicolodi
197
+ Daniele Procida
198
+ Daniil Konovalenko
199
+ Danny Hermes
200
+ Danny McClanahan
201
+ Darren Kavanagh
202
+ Dav Clark
203
+ Dave Abrahams
204
+ Dave Jones
205
+ David Aguilar
206
+ David Black
207
+ David Bordeynik
208
+ David Caro
209
+ David D Lowe
210
+ David Evans
211
+ David Hewitt
212
+ David Linke
213
+ David Poggi
214
+ David Poznik
215
+ David Pursehouse
216
+ David Runge
217
+ David Tucker
218
+ David Wales
219
+ Davidovich
220
+ ddelange
221
+ Deepak Sharma
222
+ Deepyaman Datta
223
+ Denise Yu
224
+ dependabot[bot]
225
+ derwolfe
226
+ Desetude
227
+ developer
228
+ Devesh Kumar Singh
229
+ devsagul
230
+ Diego Caraballo
231
+ Diego Ramirez
232
+ DiegoCaraballo
233
+ Dimitri Merejkowsky
234
+ Dimitri Papadopoulos
235
+ Dimitri Papadopoulos Orfanos
236
+ Dirk Stolle
237
+ Dmitry Gladkov
238
+ Dmitry Volodin
239
+ Domen Kožar
240
+ Dominic Davis-Foster
241
+ Donald Stufft
242
+ Dongweiming
243
+ doron zarhi
244
+ Dos Moonen
245
+ Douglas Thor
246
+ DrFeathers
247
+ Dustin Ingram
248
+ Dustin Rodrigues
249
+ Dwayne Bailey
250
+ Ed Morley
251
+ Edgar Ramírez
252
+ Edgar Ramírez Mondragón
253
+ Ee Durbin
254
+ Efflam Lemaillet
255
+ efflamlemaillet
256
+ Eitan Adler
257
+ ekristina
258
+ elainechan
259
+ Eli Schwartz
260
+ Elisha Hollander
261
+ Ellen Marie Dash
262
+ Emil Burzo
263
+ Emil Styrke
264
+ Emmanuel Arias
265
+ Endoh Takanao
266
+ enoch
267
+ Erdinc Mutlu
268
+ Eric Cousineau
269
+ Eric Gillingham
270
+ Eric Hanchrow
271
+ Eric Hopper
272
+ Erik M. Bray
273
+ Erik Rose
274
+ Erwin Janssen
275
+ Eugene Vereshchagin
276
+ everdimension
277
+ Federico
278
+ Felipe Peter
279
+ Felix Yan
280
+ fiber-space
281
+ Filip Kokosiński
282
+ Filipe Laíns
283
+ Finn Womack
284
+ finnagin
285
+ Flavio Amurrio
286
+ Florian Briand
287
+ Florian Rathgeber
288
+ Francesco
289
+ Francesco Montesano
290
+ Fredrik Orderud
291
+ Fredrik Roubert
292
+ Frost Ming
293
+ Gabriel Curio
294
+ Gabriel de Perthuis
295
+ Garry Polley
296
+ gavin
297
+ gdanielson
298
+ Geoffrey Sneddon
299
+ George Margaritis
300
+ George Song
301
+ Georgi Valkov
302
+ Georgy Pchelkin
303
+ ghost
304
+ Giftlin Rajaiah
305
+ gizmoguy1
306
+ gkdoc
307
+ Godefroid Chapelle
308
+ Gopinath M
309
+ GOTO Hayato
310
+ gousaiyang
311
+ gpiks
312
+ Greg Roodt
313
+ Greg Ward
314
+ Guilherme Espada
315
+ Guillaume Seguin
316
+ gutsytechster
317
+ Guy Rozendorn
318
+ Guy Tuval
319
+ gzpan123
320
+ Hanjun Kim
321
+ Hari Charan
322
+ Harsh Vardhan
323
+ harupy
324
+ Harutaka Kawamura
325
+ hauntsaninja
326
+ Henrich Hartzer
327
+ Henry Schreiner
328
+ Herbert Pfennig
329
+ Holly Stotelmyer
330
+ Honnix
331
+ Hsiaoming Yang
332
+ Hugo Lopes Tavares
333
+ Hugo van Kemenade
334
+ Hugues Bruant
335
+ Hynek Schlawack
336
+ Ian Bicking
337
+ Ian Cordasco
338
+ Ian Lee
339
+ Ian Stapleton Cordasco
340
+ Ian Wienand
341
+ Igor Kuzmitshov
342
+ Igor Sobreira
343
+ Ikko Ashimine
344
+ Ilan Schnell
345
+ Illia Volochii
346
+ Ilya Baryshev
347
+ Inada Naoki
348
+ Ionel Cristian Mărieș
349
+ Ionel Maries Cristian
350
+ Itamar Turner-Trauring
351
+ iTrooz
352
+ Ivan Pozdeev
353
+ J. Nick Koston
354
+ Jacob Kim
355
+ Jacob Walls
356
+ Jaime Sanz
357
+ Jake Lishman
358
+ jakirkham
359
+ Jakub Kuczys
360
+ Jakub Stasiak
361
+ Jakub Vysoky
362
+ Jakub Wilk
363
+ James Cleveland
364
+ James Curtin
365
+ James Firth
366
+ James Gerity
367
+ James Polley
368
+ Jan Pokorný
369
+ Jannis Leidel
370
+ Jarek Potiuk
371
+ jarondl
372
+ Jason Curtis
373
+ Jason R. Coombs
374
+ JasonMo
375
+ JasonMo1
376
+ Jay Graves
377
+ Jean Abou Samra
378
+ Jean-Christophe Fillion-Robin
379
+ Jeff Barber
380
+ Jeff Dairiki
381
+ Jeff Widman
382
+ Jelmer Vernooij
383
+ jenix21
384
+ Jeremy Fleischman
385
+ Jeremy Stanley
386
+ Jeremy Zafran
387
+ Jesse Rittner
388
+ Jiashuo Li
389
+ Jim Fisher
390
+ Jim Garrison
391
+ Jinzhe Zeng
392
+ Jiun Bae
393
+ Jivan Amara
394
+ Joa
395
+ Joe Bylund
396
+ Joe Michelini
397
+ Johannes Altmanninger
398
+ John Paton
399
+ John Sirois
400
+ John T. Wodder II
401
+ John-Scott Atlakson
402
+ johnthagen
403
+ Jon Banafato
404
+ Jon Dufresne
405
+ Jon Parise
406
+ Jonas Nockert
407
+ Jonathan Herbert
408
+ Joonatan Partanen
409
+ Joost Molenaar
410
+ Jorge Niedbalski
411
+ Joseph Bylund
412
+ Joseph Long
413
+ Josh Bronson
414
+ Josh Cannon
415
+ Josh Hansen
416
+ Josh Schneier
417
+ Joshua
418
+ JoshuaPerdue
419
+ Juan Luis Cano Rodríguez
420
+ Juanjo Bazán
421
+ Judah Rand
422
+ Julian Berman
423
+ Julian Gethmann
424
+ Julien Demoor
425
+ July Tikhonov
426
+ Jussi Kukkonen
427
+ Justin van Heek
428
+ jwg4
429
+ Jyrki Pulliainen
430
+ Kai Chen
431
+ Kai Mueller
432
+ Kamal Bin Mustafa
433
+ Karolina Surma
434
+ kasium
435
+ kaustav haldar
436
+ keanemind
437
+ Keith Maxwell
438
+ Kelsey Hightower
439
+ Kenneth Belitzky
440
+ Kenneth Reitz
441
+ Kevin Burke
442
+ Kevin Carter
443
+ Kevin Frommelt
444
+ Kevin R Patterson
445
+ Kexuan Sun
446
+ Kit Randel
447
+ Klaas van Schelven
448
+ KOLANICH
449
+ konstin
450
+ kpinc
451
+ Krishan Bhasin
452
+ Krishna Oza
453
+ Kumar McMillan
454
+ Kuntal Majumder
455
+ Kurt McKee
456
+ Kyle Persohn
457
+ lakshmanaram
458
+ Laszlo Kiss-Kollar
459
+ Laurent Bristiel
460
+ Laurent LAPORTE
461
+ Laurie O
462
+ Laurie Opperman
463
+ layday
464
+ Leon Sasson
465
+ Lev Givon
466
+ Lincoln de Sousa
467
+ Lipis
468
+ lorddavidiii
469
+ Loren Carvalho
470
+ Lucas Cimon
471
+ Ludovic Gasc
472
+ Luis Medel
473
+ Lukas Geiger
474
+ Lukas Juhrich
475
+ Luke Macken
476
+ Luo Jiebin
477
+ luojiebin
478
+ luz.paz
479
+ László Kiss Kollár
480
+ M00nL1ght
481
+ Malcolm Smith
482
+ Marc Abramowitz
483
+ Marc Tamlyn
484
+ Marcus Smith
485
+ Mariatta
486
+ Mark Kohler
487
+ Mark McLoughlin
488
+ Mark Williams
489
+ Markus Hametner
490
+ Martey Dodoo
491
+ Martin Fischer
492
+ Martin Häcker
493
+ Martin Pavlasek
494
+ Masaki
495
+ Masklinn
496
+ Matej Stuchlik
497
+ Mathew Jennings
498
+ Mathieu Bridon
499
+ Mathieu Kniewallner
500
+ Matt Bacchi
501
+ Matt Good
502
+ Matt Maker
503
+ Matt Robenolt
504
+ Matt Wozniski
505
+ matthew
506
+ Matthew Einhorn
507
+ Matthew Feickert
508
+ Matthew Gilliard
509
+ Matthew Hughes
510
+ Matthew Iversen
511
+ Matthew Treinish
512
+ Matthew Trumbell
513
+ Matthew Willson
514
+ Matthias Bussonnier
515
+ mattip
516
+ Maurits van Rees
517
+ Max W Chase
518
+ Maxim Kurnikov
519
+ Maxime Rouyrre
520
+ mayeut
521
+ mbaluna
522
+ Md Sujauddin Sekh
523
+ mdebi
524
+ memoselyk
525
+ meowmeowcat
526
+ Michael
527
+ Michael Aquilina
528
+ Michael E. Karpeles
529
+ Michael Klich
530
+ Michael Mintz
531
+ Michael Williamson
532
+ michaelpacer
533
+ Michał Górny
534
+ Mickaël Schoentgen
535
+ Miguel Araujo Perez
536
+ Mihir Singh
537
+ Mike
538
+ Mike Hendricks
539
+ Min RK
540
+ MinRK
541
+ Miro Hrončok
542
+ Monica Baluna
543
+ montefra
544
+ Monty Taylor
545
+ morotti
546
+ mrKazzila
547
+ Muha Ajjan
548
+ Nadav Wexler
549
+ Nahuel Ambrosini
550
+ Nate Coraor
551
+ Nate Prewitt
552
+ Nathan Houghton
553
+ Nathaniel J. Smith
554
+ Nehal J Wani
555
+ Neil Botelho
556
+ Nguyễn Gia Phong
557
+ Nicholas Serra
558
+ Nick Coghlan
559
+ Nick Stenning
560
+ Nick Timkovich
561
+ Nicolas Bock
562
+ Nicole Harris
563
+ Nikhil Benesch
564
+ Nikhil Ladha
565
+ Nikita Chepanov
566
+ Nikolay Korolev
567
+ Nipunn Koorapati
568
+ Nitesh Sharma
569
+ Niyas Sait
570
+ Noah
571
+ Noah Gorny
572
+ Nowell Strite
573
+ NtaleGrey
574
+ nucccc
575
+ nvdv
576
+ OBITORASU
577
+ Ofek Lev
578
+ ofrinevo
579
+ Oleg Burnaev
580
+ Oliver Freund
581
+ Oliver Jeeves
582
+ Oliver Mannion
583
+ Oliver Tonnhofer
584
+ Olivier Girardot
585
+ Olivier Grisel
586
+ Ollie Rutherfurd
587
+ OMOTO Kenji
588
+ Omry Yadan
589
+ onlinejudge95
590
+ Oren Held
591
+ Oscar Benjamin
592
+ Oz N Tiram
593
+ Pachwenko
594
+ Patrick Dubroy
595
+ Patrick Jenkins
596
+ Patrick Lawson
597
+ patricktokeeffe
598
+ Patrik Kopkan
599
+ Paul Ganssle
600
+ Paul Kehrer
601
+ Paul Moore
602
+ Paul Nasrat
603
+ Paul Oswald
604
+ Paul van der Linden
605
+ Paulus Schoutsen
606
+ Pavel Safronov
607
+ Pavithra Eswaramoorthy
608
+ Pawel Jasinski
609
+ Paweł Szramowski
610
+ Pekka Klärck
611
+ Peter Gessler
612
+ Peter Lisák
613
+ Peter Shen
614
+ Peter Waller
615
+ Petr Viktorin
616
+ petr-tik
617
+ Phaneendra Chiruvella
618
+ Phil Elson
619
+ Phil Freo
620
+ Phil Pennock
621
+ Phil Whelan
622
+ Philip Jägenstedt
623
+ Philip Molloy
624
+ Philippe Ombredanne
625
+ Pi Delport
626
+ Pierre-Yves Rofes
627
+ Pieter Degroote
628
+ pip
629
+ Prabakaran Kumaresshan
630
+ Prabhjyotsing Surjit Singh Sodhi
631
+ Prabhu Marappan
632
+ Pradyun Gedam
633
+ Prashant Sharma
634
+ Pratik Mallya
635
+ pre-commit-ci[bot]
636
+ Preet Thakkar
637
+ Preston Holmes
638
+ Przemek Wrzos
639
+ Pulkit Goyal
640
+ q0w
641
+ Qiangning Hong
642
+ Qiming Xu
643
+ Quentin Lee
644
+ Quentin Pradet
645
+ R. David Murray
646
+ Rafael Caricio
647
+ Ralf Schmitt
648
+ Ran Benita
649
+ Randy Döring
650
+ Razzi Abuissa
651
+ rdb
652
+ Reece Dunham
653
+ Remi Rampin
654
+ Rene Dudfield
655
+ Riccardo Magliocchetti
656
+ Riccardo Schirone
657
+ Richard Jones
658
+ Richard Si
659
+ Ricky Ng-Adam
660
+ Rishi
661
+ rmorotti
662
+ RobberPhex
663
+ Robert Collins
664
+ Robert McGibbon
665
+ Robert Pollak
666
+ Robert T. McGibbon
667
+ robin elisha robinson
668
+ Roey Berman
669
+ Rohan Jain
670
+ Roman Bogorodskiy
671
+ Roman Donchenko
672
+ Romuald Brunet
673
+ ronaudinho
674
+ Ronny Pfannschmidt
675
+ Rory McCann
676
+ Ross Brattain
677
+ Roy Wellington Ⅳ
678
+ Ruairidh MacLeod
679
+ Russell Keith-Magee
680
+ Ryan Shepherd
681
+ Ryan Wooden
682
+ ryneeverett
683
+ S. Guliaev
684
+ Sachi King
685
+ Salvatore Rinchiera
686
+ sandeepkiran-js
687
+ Sander Van Balen
688
+ Savio Jomton
689
+ schlamar
690
+ Scott Kitterman
691
+ Sean
692
+ seanj
693
+ Sebastian Jordan
694
+ Sebastian Schaetz
695
+ Segev Finer
696
+ SeongSoo Cho
697
+ Sergey Vasilyev
698
+ Seth Michael Larson
699
+ Seth Woodworth
700
+ Shahar Epstein
701
+ Shantanu
702
+ shenxianpeng
703
+ shireenrao
704
+ Shivansh-007
705
+ Shixian Sheng
706
+ Shlomi Fish
707
+ Shovan Maity
708
+ Simeon Visser
709
+ Simon Cross
710
+ Simon Pichugin
711
+ sinoroc
712
+ sinscary
713
+ snook92
714
+ socketubs
715
+ Sorin Sbarnea
716
+ Srinivas Nyayapati
717
+ Srishti Hegde
718
+ Stavros Korokithakis
719
+ Stefan Scherfke
720
+ Stefano Rivera
721
+ Stephan Erb
722
+ Stephen Rosen
723
+ stepshal
724
+ Steve (Gadget) Barnes
725
+ Steve Barnes
726
+ Steve Dower
727
+ Steve Kowalik
728
+ Steven Myint
729
+ Steven Silvester
730
+ stonebig
731
+ studioj
732
+ Stéphane Bidoul
733
+ Stéphane Bidoul (ACSONE)
734
+ Stéphane Klein
735
+ Sumana Harihareswara
736
+ Surbhi Sharma
737
+ Sviatoslav Sydorenko
738
+ Sviatoslav Sydorenko (Святослав Сидоренко)
739
+ Swat009
740
+ Sylvain
741
+ Takayuki SHIMIZUKAWA
742
+ Taneli Hukkinen
743
+ tbeswick
744
+ Thiago
745
+ Thijs Triemstra
746
+ Thomas Fenzl
747
+ Thomas Grainger
748
+ Thomas Guettler
749
+ Thomas Johansson
750
+ Thomas Kluyver
751
+ Thomas Smith
752
+ Thomas VINCENT
753
+ Tim D. Smith
754
+ Tim Gates
755
+ Tim Harder
756
+ Tim Heap
757
+ tim smith
758
+ tinruufu
759
+ Tobias Hermann
760
+ Tom Forbes
761
+ Tom Freudenheim
762
+ Tom V
763
+ Tomas Hrnciar
764
+ Tomas Orsava
765
+ Tomer Chachamu
766
+ Tommi Enenkel | AnB
767
+ Tomáš Hrnčiar
768
+ Tony Beswick
769
+ Tony Narlock
770
+ Tony Zhaocheng Tan
771
+ TonyBeswick
772
+ toonarmycaptain
773
+ Toshio Kuratomi
774
+ toxinu
775
+ Travis Swicegood
776
+ Tushar Sadhwani
777
+ Tzu-ping Chung
778
+ Valentin Haenel
779
+ Victor Stinner
780
+ victorvpaulo
781
+ Vikram - Google
782
+ Viktor Szépe
783
+ Ville Skyttä
784
+ Vinay Sajip
785
+ Vincent Philippon
786
+ Vinicyus Macedo
787
+ Vipul Kumar
788
+ Vitaly Babiy
789
+ Vladimir Fokow
790
+ Vladimir Rutsky
791
+ W. Trevor King
792
+ Wil Tan
793
+ Wilfred Hughes
794
+ William Edwards
795
+ William ML Leslie
796
+ William T Olson
797
+ William Woodruff
798
+ Wilson Mo
799
+ wim glenn
800
+ Winson Luk
801
+ Wolfgang Maier
802
+ Wu Zhenyu
803
+ XAMES3
804
+ Xavier Fernandez
805
+ Xianpeng Shen
806
+ xoviat
807
+ xtreak
808
+ YAMAMOTO Takashi
809
+ Yen Chi Hsuan
810
+ Yeray Diaz Diaz
811
+ Yoval P
812
+ Yu Jian
813
+ Yuan Jing Vincent Yan
814
+ Yusuke Hayashi
815
+ Zearin
816
+ Zhiping Deng
817
+ ziebam
818
+ Zvezdan Petkovic
819
+ Łukasz Langa
820
+ Роман Донченко
821
+ Семён Марьясин
.mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/licenses/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2008-present The pip developers (see AUTHORS.txt file)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
.mcp-venv/lib/python3.13/site-packages/pip-25.1.1.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
.mcp-venv/lib/python3.13/site-packages/pip/_internal/models/installation_report.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict, Sequence
2
+
3
+ from pip._vendor.packaging.markers import default_environment
4
+
5
+ from pip import __version__
6
+ from pip._internal.req.req_install import InstallRequirement
7
+
8
+
9
+ class InstallationReport:
10
+ def __init__(self, install_requirements: Sequence[InstallRequirement]):
11
+ self._install_requirements = install_requirements
12
+
13
+ @classmethod
14
+ def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
15
+ assert ireq.download_info, f"No download_info for {ireq}"
16
+ res = {
17
+ # PEP 610 json for the download URL. download_info.archive_info.hashes may
18
+ # be absent when the requirement was installed from the wheel cache
19
+ # and the cache entry was populated by an older pip version that did not
20
+ # record origin.json.
21
+ "download_info": ireq.download_info.to_dict(),
22
+ # is_direct is true if the requirement was a direct URL reference (which
23
+ # includes editable requirements), and false if the requirement was
24
+ # downloaded from a PEP 503 index or --find-links.
25
+ "is_direct": ireq.is_direct,
26
+ # is_yanked is true if the requirement was yanked from the index, but
27
+ # was still selected by pip to conform to PEP 592.
28
+ "is_yanked": ireq.link.is_yanked if ireq.link else False,
29
+ # requested is true if the requirement was specified by the user (aka
30
+ # top level requirement), and false if it was installed as a dependency of a
31
+ # requirement. https://peps.python.org/pep-0376/#requested
32
+ "requested": ireq.user_supplied,
33
+ # PEP 566 json encoding for metadata
34
+ # https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata
35
+ "metadata": ireq.get_dist().metadata_dict,
36
+ }
37
+ if ireq.user_supplied and ireq.extras:
38
+ # For top level requirements, the list of requested extras, if any.
39
+ res["requested_extras"] = sorted(ireq.extras)
40
+ return res
41
+
42
+ def to_dict(self) -> Dict[str, Any]:
43
+ return {
44
+ "version": "1",
45
+ "pip_version": __version__,
46
+ "install": [
47
+ self._install_req_to_dict(ireq) for ireq in self._install_requirements
48
+ ],
49
+ # https://peps.python.org/pep-0508/#environment-markers
50
+ # TODO: currently, the resolver uses the default environment to evaluate
51
+ # environment markers, so that is what we report here. In the future, it
52
+ # should also take into account options such as --python-version or
53
+ # --platform, perhaps under the form of an environment_override field?
54
+ # https://github.com/pypa/pip/issues/11198
55
+ "environment": default_environment(),
56
+ }
.mcp-venv/lib/python3.13/site-packages/pip/_internal/models/link.py ADDED
@@ -0,0 +1,608 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import itertools
3
+ import logging
4
+ import os
5
+ import posixpath
6
+ import re
7
+ import urllib.parse
8
+ from dataclasses import dataclass
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Any,
12
+ Dict,
13
+ List,
14
+ Mapping,
15
+ NamedTuple,
16
+ Optional,
17
+ Tuple,
18
+ Union,
19
+ )
20
+
21
+ from pip._internal.utils.deprecation import deprecated
22
+ from pip._internal.utils.filetypes import WHEEL_EXTENSION
23
+ from pip._internal.utils.hashes import Hashes
24
+ from pip._internal.utils.misc import (
25
+ pairwise,
26
+ redact_auth_from_url,
27
+ split_auth_from_netloc,
28
+ splitext,
29
+ )
30
+ from pip._internal.utils.urls import path_to_url, url_to_path
31
+
32
+ if TYPE_CHECKING:
33
+ from pip._internal.index.collector import IndexContent
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ # Order matters, earlier hashes have a precedence over later hashes for what
39
+ # we will pick to use.
40
+ _SUPPORTED_HASHES = ("sha512", "sha384", "sha256", "sha224", "sha1", "md5")
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class LinkHash:
45
+ """Links to content may have embedded hash values. This class parses those.
46
+
47
+ `name` must be any member of `_SUPPORTED_HASHES`.
48
+
49
+ This class can be converted to and from `ArchiveInfo`. While ArchiveInfo intends to
50
+ be JSON-serializable to conform to PEP 610, this class contains the logic for
51
+ parsing a hash name and value for correctness, and then checking whether that hash
52
+ conforms to a schema with `.is_hash_allowed()`."""
53
+
54
+ name: str
55
+ value: str
56
+
57
+ _hash_url_fragment_re = re.compile(
58
+ # NB: we do not validate that the second group (.*) is a valid hex
59
+ # digest. Instead, we simply keep that string in this class, and then check it
60
+ # against Hashes when hash-checking is needed. This is easier to debug than
61
+ # proactively discarding an invalid hex digest, as we handle incorrect hashes
62
+ # and malformed hashes in the same place.
63
+ r"[#&]({choices})=([^&]*)".format(
64
+ choices="|".join(re.escape(hash_name) for hash_name in _SUPPORTED_HASHES)
65
+ ),
66
+ )
67
+
68
+ def __post_init__(self) -> None:
69
+ assert self.name in _SUPPORTED_HASHES
70
+
71
+ @classmethod
72
+ @functools.lru_cache(maxsize=None)
73
+ def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]:
74
+ """Search a string for a checksum algorithm name and encoded output value."""
75
+ match = cls._hash_url_fragment_re.search(url)
76
+ if match is None:
77
+ return None
78
+ name, value = match.groups()
79
+ return cls(name=name, value=value)
80
+
81
+ def as_dict(self) -> Dict[str, str]:
82
+ return {self.name: self.value}
83
+
84
+ def as_hashes(self) -> Hashes:
85
+ """Return a Hashes instance which checks only for the current hash."""
86
+ return Hashes({self.name: [self.value]})
87
+
88
+ def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
89
+ """
90
+ Return True if the current hash is allowed by `hashes`.
91
+ """
92
+ if hashes is None:
93
+ return False
94
+ return hashes.is_hash_allowed(self.name, hex_digest=self.value)
95
+
96
+
97
+ @dataclass(frozen=True)
98
+ class MetadataFile:
99
+ """Information about a core metadata file associated with a distribution."""
100
+
101
+ hashes: Optional[Dict[str, str]]
102
+
103
+ def __post_init__(self) -> None:
104
+ if self.hashes is not None:
105
+ assert all(name in _SUPPORTED_HASHES for name in self.hashes)
106
+
107
+
108
+ def supported_hashes(hashes: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
109
+ # Remove any unsupported hash types from the mapping. If this leaves no
110
+ # supported hashes, return None
111
+ if hashes is None:
112
+ return None
113
+ hashes = {n: v for n, v in hashes.items() if n in _SUPPORTED_HASHES}
114
+ if not hashes:
115
+ return None
116
+ return hashes
117
+
118
+
119
+ def _clean_url_path_part(part: str) -> str:
120
+ """
121
+ Clean a "part" of a URL path (i.e. after splitting on "@" characters).
122
+ """
123
+ # We unquote prior to quoting to make sure nothing is double quoted.
124
+ return urllib.parse.quote(urllib.parse.unquote(part))
125
+
126
+
127
+ def _clean_file_url_path(part: str) -> str:
128
+ """
129
+ Clean the first part of a URL path that corresponds to a local
130
+ filesystem path (i.e. the first part after splitting on "@" characters).
131
+ """
132
+ # We unquote prior to quoting to make sure nothing is double quoted.
133
+ # Also, on Windows the path part might contain a drive letter which
134
+ # should not be quoted. On Linux where drive letters do not
135
+ # exist, the colon should be quoted. We rely on urllib.request
136
+ # to do the right thing here.
137
+ return urllib.request.pathname2url(urllib.request.url2pathname(part))
138
+
139
+
140
+ # percent-encoded: /
141
+ _reserved_chars_re = re.compile("(@|%2F)", re.IGNORECASE)
142
+
143
+
144
+ def _clean_url_path(path: str, is_local_path: bool) -> str:
145
+ """
146
+ Clean the path portion of a URL.
147
+ """
148
+ if is_local_path:
149
+ clean_func = _clean_file_url_path
150
+ else:
151
+ clean_func = _clean_url_path_part
152
+
153
+ # Split on the reserved characters prior to cleaning so that
154
+ # revision strings in VCS URLs are properly preserved.
155
+ parts = _reserved_chars_re.split(path)
156
+
157
+ cleaned_parts = []
158
+ for to_clean, reserved in pairwise(itertools.chain(parts, [""])):
159
+ cleaned_parts.append(clean_func(to_clean))
160
+ # Normalize %xx escapes (e.g. %2f -> %2F)
161
+ cleaned_parts.append(reserved.upper())
162
+
163
+ return "".join(cleaned_parts)
164
+
165
+
166
+ def _ensure_quoted_url(url: str) -> str:
167
+ """
168
+ Make sure a link is fully quoted.
169
+ For example, if ' ' occurs in the URL, it will be replaced with "%20",
170
+ and without double-quoting other characters.
171
+ """
172
+ # Split the URL into parts according to the general structure
173
+ # `scheme://netloc/path?query#fragment`.
174
+ result = urllib.parse.urlsplit(url)
175
+ # If the netloc is empty, then the URL refers to a local filesystem path.
176
+ is_local_path = not result.netloc
177
+ path = _clean_url_path(result.path, is_local_path=is_local_path)
178
+ return urllib.parse.urlunsplit(result._replace(path=path))
179
+
180
+
181
+ def _absolute_link_url(base_url: str, url: str) -> str:
182
+ """
183
+ A faster implementation of urllib.parse.urljoin with a shortcut
184
+ for absolute http/https URLs.
185
+ """
186
+ if url.startswith(("https://", "http://")):
187
+ return url
188
+ else:
189
+ return urllib.parse.urljoin(base_url, url)
190
+
191
+
192
+ @functools.total_ordering
193
+ class Link:
194
+ """Represents a parsed link from a Package Index's simple URL"""
195
+
196
+ __slots__ = [
197
+ "_parsed_url",
198
+ "_url",
199
+ "_path",
200
+ "_hashes",
201
+ "comes_from",
202
+ "requires_python",
203
+ "yanked_reason",
204
+ "metadata_file_data",
205
+ "cache_link_parsing",
206
+ "egg_fragment",
207
+ ]
208
+
209
+ def __init__(
210
+ self,
211
+ url: str,
212
+ comes_from: Optional[Union[str, "IndexContent"]] = None,
213
+ requires_python: Optional[str] = None,
214
+ yanked_reason: Optional[str] = None,
215
+ metadata_file_data: Optional[MetadataFile] = None,
216
+ cache_link_parsing: bool = True,
217
+ hashes: Optional[Mapping[str, str]] = None,
218
+ ) -> None:
219
+ """
220
+ :param url: url of the resource pointed to (href of the link)
221
+ :param comes_from: instance of IndexContent where the link was found,
222
+ or string.
223
+ :param requires_python: String containing the `Requires-Python`
224
+ metadata field, specified in PEP 345. This may be specified by
225
+ a data-requires-python attribute in the HTML link tag, as
226
+ described in PEP 503.
227
+ :param yanked_reason: the reason the file has been yanked, if the
228
+ file has been yanked, or None if the file hasn't been yanked.
229
+ This is the value of the "data-yanked" attribute, if present, in
230
+ a simple repository HTML link. If the file has been yanked but
231
+ no reason was provided, this should be the empty string. See
232
+ PEP 592 for more information and the specification.
233
+ :param metadata_file_data: the metadata attached to the file, or None if
234
+ no such metadata is provided. This argument, if not None, indicates
235
+ that a separate metadata file exists, and also optionally supplies
236
+ hashes for that file.
237
+ :param cache_link_parsing: A flag that is used elsewhere to determine
238
+ whether resources retrieved from this link should be cached. PyPI
239
+ URLs should generally have this set to False, for example.
240
+ :param hashes: A mapping of hash names to digests to allow us to
241
+ determine the validity of a download.
242
+ """
243
+
244
+ # The comes_from, requires_python, and metadata_file_data arguments are
245
+ # only used by classmethods of this class, and are not used in client
246
+ # code directly.
247
+
248
+ # url can be a UNC windows share
249
+ if url.startswith("\\\\"):
250
+ url = path_to_url(url)
251
+
252
+ self._parsed_url = urllib.parse.urlsplit(url)
253
+ # Store the url as a private attribute to prevent accidentally
254
+ # trying to set a new value.
255
+ self._url = url
256
+ # The .path property is hot, so calculate its value ahead of time.
257
+ self._path = urllib.parse.unquote(self._parsed_url.path)
258
+
259
+ link_hash = LinkHash.find_hash_url_fragment(url)
260
+ hashes_from_link = {} if link_hash is None else link_hash.as_dict()
261
+ if hashes is None:
262
+ self._hashes = hashes_from_link
263
+ else:
264
+ self._hashes = {**hashes, **hashes_from_link}
265
+
266
+ self.comes_from = comes_from
267
+ self.requires_python = requires_python if requires_python else None
268
+ self.yanked_reason = yanked_reason
269
+ self.metadata_file_data = metadata_file_data
270
+
271
+ self.cache_link_parsing = cache_link_parsing
272
+ self.egg_fragment = self._egg_fragment()
273
+
274
+ @classmethod
275
+ def from_json(
276
+ cls,
277
+ file_data: Dict[str, Any],
278
+ page_url: str,
279
+ ) -> Optional["Link"]:
280
+ """
281
+ Convert an pypi json document from a simple repository page into a Link.
282
+ """
283
+ file_url = file_data.get("url")
284
+ if file_url is None:
285
+ return None
286
+
287
+ url = _ensure_quoted_url(_absolute_link_url(page_url, file_url))
288
+ pyrequire = file_data.get("requires-python")
289
+ yanked_reason = file_data.get("yanked")
290
+ hashes = file_data.get("hashes", {})
291
+
292
+ # PEP 714: Indexes must use the name core-metadata, but
293
+ # clients should support the old name as a fallback for compatibility.
294
+ metadata_info = file_data.get("core-metadata")
295
+ if metadata_info is None:
296
+ metadata_info = file_data.get("dist-info-metadata")
297
+
298
+ # The metadata info value may be a boolean, or a dict of hashes.
299
+ if isinstance(metadata_info, dict):
300
+ # The file exists, and hashes have been supplied
301
+ metadata_file_data = MetadataFile(supported_hashes(metadata_info))
302
+ elif metadata_info:
303
+ # The file exists, but there are no hashes
304
+ metadata_file_data = MetadataFile(None)
305
+ else:
306
+ # False or not present: the file does not exist
307
+ metadata_file_data = None
308
+
309
+ # The Link.yanked_reason expects an empty string instead of a boolean.
310
+ if yanked_reason and not isinstance(yanked_reason, str):
311
+ yanked_reason = ""
312
+ # The Link.yanked_reason expects None instead of False.
313
+ elif not yanked_reason:
314
+ yanked_reason = None
315
+
316
+ return cls(
317
+ url,
318
+ comes_from=page_url,
319
+ requires_python=pyrequire,
320
+ yanked_reason=yanked_reason,
321
+ hashes=hashes,
322
+ metadata_file_data=metadata_file_data,
323
+ )
324
+
325
+ @classmethod
326
+ def from_element(
327
+ cls,
328
+ anchor_attribs: Dict[str, Optional[str]],
329
+ page_url: str,
330
+ base_url: str,
331
+ ) -> Optional["Link"]:
332
+ """
333
+ Convert an anchor element's attributes in a simple repository page to a Link.
334
+ """
335
+ href = anchor_attribs.get("href")
336
+ if not href:
337
+ return None
338
+
339
+ url = _ensure_quoted_url(_absolute_link_url(base_url, href))
340
+ pyrequire = anchor_attribs.get("data-requires-python")
341
+ yanked_reason = anchor_attribs.get("data-yanked")
342
+
343
+ # PEP 714: Indexes must use the name data-core-metadata, but
344
+ # clients should support the old name as a fallback for compatibility.
345
+ metadata_info = anchor_attribs.get("data-core-metadata")
346
+ if metadata_info is None:
347
+ metadata_info = anchor_attribs.get("data-dist-info-metadata")
348
+ # The metadata info value may be the string "true", or a string of
349
+ # the form "hashname=hashval"
350
+ if metadata_info == "true":
351
+ # The file exists, but there are no hashes
352
+ metadata_file_data = MetadataFile(None)
353
+ elif metadata_info is None:
354
+ # The file does not exist
355
+ metadata_file_data = None
356
+ else:
357
+ # The file exists, and hashes have been supplied
358
+ hashname, sep, hashval = metadata_info.partition("=")
359
+ if sep == "=":
360
+ metadata_file_data = MetadataFile(supported_hashes({hashname: hashval}))
361
+ else:
362
+ # Error - data is wrong. Treat as no hashes supplied.
363
+ logger.debug(
364
+ "Index returned invalid data-dist-info-metadata value: %s",
365
+ metadata_info,
366
+ )
367
+ metadata_file_data = MetadataFile(None)
368
+
369
+ return cls(
370
+ url,
371
+ comes_from=page_url,
372
+ requires_python=pyrequire,
373
+ yanked_reason=yanked_reason,
374
+ metadata_file_data=metadata_file_data,
375
+ )
376
+
377
+ def __str__(self) -> str:
378
+ if self.requires_python:
379
+ rp = f" (requires-python:{self.requires_python})"
380
+ else:
381
+ rp = ""
382
+ if self.comes_from:
383
+ return f"{self.redacted_url} (from {self.comes_from}){rp}"
384
+ else:
385
+ return self.redacted_url
386
+
387
+ def __repr__(self) -> str:
388
+ return f"<Link {self}>"
389
+
390
+ def __hash__(self) -> int:
391
+ return hash(self.url)
392
+
393
+ def __eq__(self, other: Any) -> bool:
394
+ if not isinstance(other, Link):
395
+ return NotImplemented
396
+ return self.url == other.url
397
+
398
+ def __lt__(self, other: Any) -> bool:
399
+ if not isinstance(other, Link):
400
+ return NotImplemented
401
+ return self.url < other.url
402
+
403
+ @property
404
+ def url(self) -> str:
405
+ return self._url
406
+
407
+ @property
408
+ def redacted_url(self) -> str:
409
+ return redact_auth_from_url(self.url)
410
+
411
+ @property
412
+ def filename(self) -> str:
413
+ path = self.path.rstrip("/")
414
+ name = posixpath.basename(path)
415
+ if not name:
416
+ # Make sure we don't leak auth information if the netloc
417
+ # includes a username and password.
418
+ netloc, user_pass = split_auth_from_netloc(self.netloc)
419
+ return netloc
420
+
421
+ name = urllib.parse.unquote(name)
422
+ assert name, f"URL {self._url!r} produced no filename"
423
+ return name
424
+
425
+ @property
426
+ def file_path(self) -> str:
427
+ return url_to_path(self.url)
428
+
429
+ @property
430
+ def scheme(self) -> str:
431
+ return self._parsed_url.scheme
432
+
433
+ @property
434
+ def netloc(self) -> str:
435
+ """
436
+ This can contain auth information.
437
+ """
438
+ return self._parsed_url.netloc
439
+
440
+ @property
441
+ def path(self) -> str:
442
+ return self._path
443
+
444
+ def splitext(self) -> Tuple[str, str]:
445
+ return splitext(posixpath.basename(self.path.rstrip("/")))
446
+
447
+ @property
448
+ def ext(self) -> str:
449
+ return self.splitext()[1]
450
+
451
+ @property
452
+ def url_without_fragment(self) -> str:
453
+ scheme, netloc, path, query, fragment = self._parsed_url
454
+ return urllib.parse.urlunsplit((scheme, netloc, path, query, ""))
455
+
456
+ _egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")
457
+
458
+ # Per PEP 508.
459
+ _project_name_re = re.compile(
460
+ r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
461
+ )
462
+
463
+ def _egg_fragment(self) -> Optional[str]:
464
+ match = self._egg_fragment_re.search(self._url)
465
+ if not match:
466
+ return None
467
+
468
+ # An egg fragment looks like a PEP 508 project name, along with
469
+ # an optional extras specifier. Anything else is invalid.
470
+ project_name = match.group(1)
471
+ if not self._project_name_re.match(project_name):
472
+ deprecated(
473
+ reason=f"{self} contains an egg fragment with a non-PEP 508 name.",
474
+ replacement="to use the req @ url syntax, and remove the egg fragment",
475
+ gone_in="25.2",
476
+ issue=13157,
477
+ )
478
+
479
+ return project_name
480
+
481
+ _subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
482
+
483
+ @property
484
+ def subdirectory_fragment(self) -> Optional[str]:
485
+ match = self._subdirectory_fragment_re.search(self._url)
486
+ if not match:
487
+ return None
488
+ return match.group(1)
489
+
490
+ def metadata_link(self) -> Optional["Link"]:
491
+ """Return a link to the associated core metadata file (if any)."""
492
+ if self.metadata_file_data is None:
493
+ return None
494
+ metadata_url = f"{self.url_without_fragment}.metadata"
495
+ if self.metadata_file_data.hashes is None:
496
+ return Link(metadata_url)
497
+ return Link(metadata_url, hashes=self.metadata_file_data.hashes)
498
+
499
+ def as_hashes(self) -> Hashes:
500
+ return Hashes({k: [v] for k, v in self._hashes.items()})
501
+
502
+ @property
503
+ def hash(self) -> Optional[str]:
504
+ return next(iter(self._hashes.values()), None)
505
+
506
+ @property
507
+ def hash_name(self) -> Optional[str]:
508
+ return next(iter(self._hashes), None)
509
+
510
+ @property
511
+ def show_url(self) -> str:
512
+ return posixpath.basename(self._url.split("#", 1)[0].split("?", 1)[0])
513
+
514
+ @property
515
+ def is_file(self) -> bool:
516
+ return self.scheme == "file"
517
+
518
+ def is_existing_dir(self) -> bool:
519
+ return self.is_file and os.path.isdir(self.file_path)
520
+
521
+ @property
522
+ def is_wheel(self) -> bool:
523
+ return self.ext == WHEEL_EXTENSION
524
+
525
+ @property
526
+ def is_vcs(self) -> bool:
527
+ from pip._internal.vcs import vcs
528
+
529
+ return self.scheme in vcs.all_schemes
530
+
531
+ @property
532
+ def is_yanked(self) -> bool:
533
+ return self.yanked_reason is not None
534
+
535
+ @property
536
+ def has_hash(self) -> bool:
537
+ return bool(self._hashes)
538
+
539
+ def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
540
+ """
541
+ Return True if the link has a hash and it is allowed by `hashes`.
542
+ """
543
+ if hashes is None:
544
+ return False
545
+ return any(hashes.is_hash_allowed(k, v) for k, v in self._hashes.items())
546
+
547
+
548
+ class _CleanResult(NamedTuple):
549
+ """Convert link for equivalency check.
550
+
551
+ This is used in the resolver to check whether two URL-specified requirements
552
+ likely point to the same distribution and can be considered equivalent. This
553
+ equivalency logic avoids comparing URLs literally, which can be too strict
554
+ (e.g. "a=1&b=2" vs "b=2&a=1") and produce conflicts unexpecting to users.
555
+
556
+ Currently this does three things:
557
+
558
+ 1. Drop the basic auth part. This is technically wrong since a server can
559
+ serve different content based on auth, but if it does that, it is even
560
+ impossible to guarantee two URLs without auth are equivalent, since
561
+ the user can input different auth information when prompted. So the
562
+ practical solution is to assume the auth doesn't affect the response.
563
+ 2. Parse the query to avoid the ordering issue. Note that ordering under the
564
+ same key in the query are NOT cleaned; i.e. "a=1&a=2" and "a=2&a=1" are
565
+ still considered different.
566
+ 3. Explicitly drop most of the fragment part, except ``subdirectory=`` and
567
+ hash values, since it should have no impact the downloaded content. Note
568
+ that this drops the "egg=" part historically used to denote the requested
569
+ project (and extras), which is wrong in the strictest sense, but too many
570
+ people are supplying it inconsistently to cause superfluous resolution
571
+ conflicts, so we choose to also ignore them.
572
+ """
573
+
574
+ parsed: urllib.parse.SplitResult
575
+ query: Dict[str, List[str]]
576
+ subdirectory: str
577
+ hashes: Dict[str, str]
578
+
579
+
580
+ def _clean_link(link: Link) -> _CleanResult:
581
+ parsed = link._parsed_url
582
+ netloc = parsed.netloc.rsplit("@", 1)[-1]
583
+ # According to RFC 8089, an empty host in file: means localhost.
584
+ if parsed.scheme == "file" and not netloc:
585
+ netloc = "localhost"
586
+ fragment = urllib.parse.parse_qs(parsed.fragment)
587
+ if "egg" in fragment:
588
+ logger.debug("Ignoring egg= fragment in %s", link)
589
+ try:
590
+ # If there are multiple subdirectory values, use the first one.
591
+ # This matches the behavior of Link.subdirectory_fragment.
592
+ subdirectory = fragment["subdirectory"][0]
593
+ except (IndexError, KeyError):
594
+ subdirectory = ""
595
+ # If there are multiple hash values under the same algorithm, use the
596
+ # first one. This matches the behavior of Link.hash_value.
597
+ hashes = {k: fragment[k][0] for k in _SUPPORTED_HASHES if k in fragment}
598
+ return _CleanResult(
599
+ parsed=parsed._replace(netloc=netloc, query="", fragment=""),
600
+ query=urllib.parse.parse_qs(parsed.query),
601
+ subdirectory=subdirectory,
602
+ hashes=hashes,
603
+ )
604
+
605
+
606
+ @functools.lru_cache(maxsize=None)
607
+ def links_equivalent(link1: Link, link2: Link) -> bool:
608
+ return _clean_link(link1) == _clean_link(link2)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/models/pylock.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dataclasses
2
+ import re
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Any, Dict, Iterable, List, Optional, Tuple
6
+
7
+ from pip._vendor import tomli_w
8
+ from pip._vendor.typing_extensions import Self
9
+
10
+ from pip._internal.models.direct_url import ArchiveInfo, DirInfo, VcsInfo
11
+ from pip._internal.models.link import Link
12
+ from pip._internal.req.req_install import InstallRequirement
13
+ from pip._internal.utils.urls import url_to_path
14
+
15
+ PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")
16
+
17
+
18
+ def is_valid_pylock_file_name(path: Path) -> bool:
19
+ return path.name == "pylock.toml" or bool(re.match(PYLOCK_FILE_NAME_RE, path.name))
20
+
21
+
22
+ def _toml_dict_factory(data: List[Tuple[str, Any]]) -> Dict[str, Any]:
23
+ return {key.replace("_", "-"): value for key, value in data if value is not None}
24
+
25
+
26
+ @dataclass
27
+ class PackageVcs:
28
+ type: str
29
+ url: Optional[str]
30
+ # (not supported) path: Optional[str]
31
+ requested_revision: Optional[str]
32
+ commit_id: str
33
+ subdirectory: Optional[str]
34
+
35
+
36
+ @dataclass
37
+ class PackageDirectory:
38
+ path: str
39
+ editable: Optional[bool]
40
+ subdirectory: Optional[str]
41
+
42
+
43
+ @dataclass
44
+ class PackageArchive:
45
+ url: Optional[str]
46
+ # (not supported) path: Optional[str]
47
+ # (not supported) size: Optional[int]
48
+ # (not supported) upload_time: Optional[datetime]
49
+ hashes: Dict[str, str]
50
+ subdirectory: Optional[str]
51
+
52
+
53
+ @dataclass
54
+ class PackageSdist:
55
+ name: str
56
+ # (not supported) upload_time: Optional[datetime]
57
+ url: Optional[str]
58
+ # (not supported) path: Optional[str]
59
+ # (not supported) size: Optional[int]
60
+ hashes: Dict[str, str]
61
+
62
+
63
+ @dataclass
64
+ class PackageWheel:
65
+ name: str
66
+ # (not supported) upload_time: Optional[datetime]
67
+ url: Optional[str]
68
+ # (not supported) path: Optional[str]
69
+ # (not supported) size: Optional[int]
70
+ hashes: Dict[str, str]
71
+
72
+
73
+ @dataclass
74
+ class Package:
75
+ name: str
76
+ version: Optional[str] = None
77
+ # (not supported) marker: Optional[str]
78
+ # (not supported) requires_python: Optional[str]
79
+ # (not supported) dependencies
80
+ vcs: Optional[PackageVcs] = None
81
+ directory: Optional[PackageDirectory] = None
82
+ archive: Optional[PackageArchive] = None
83
+ # (not supported) index: Optional[str]
84
+ sdist: Optional[PackageSdist] = None
85
+ wheels: Optional[List[PackageWheel]] = None
86
+ # (not supported) attestation_identities: Optional[List[Dict[str, Any]]]
87
+ # (not supported) tool: Optional[Dict[str, Any]]
88
+
89
+ @classmethod
90
+ def from_install_requirement(cls, ireq: InstallRequirement, base_dir: Path) -> Self:
91
+ base_dir = base_dir.resolve()
92
+ dist = ireq.get_dist()
93
+ download_info = ireq.download_info
94
+ assert download_info
95
+ package = cls(name=dist.canonical_name)
96
+ if ireq.is_direct:
97
+ if isinstance(download_info.info, VcsInfo):
98
+ package.vcs = PackageVcs(
99
+ type=download_info.info.vcs,
100
+ url=download_info.url,
101
+ requested_revision=download_info.info.requested_revision,
102
+ commit_id=download_info.info.commit_id,
103
+ subdirectory=download_info.subdirectory,
104
+ )
105
+ elif isinstance(download_info.info, DirInfo):
106
+ package.directory = PackageDirectory(
107
+ path=(
108
+ Path(url_to_path(download_info.url))
109
+ .resolve()
110
+ .relative_to(base_dir)
111
+ .as_posix()
112
+ ),
113
+ editable=(
114
+ download_info.info.editable
115
+ if download_info.info.editable
116
+ else None
117
+ ),
118
+ subdirectory=download_info.subdirectory,
119
+ )
120
+ elif isinstance(download_info.info, ArchiveInfo):
121
+ if not download_info.info.hashes:
122
+ raise NotImplementedError()
123
+ package.archive = PackageArchive(
124
+ url=download_info.url,
125
+ hashes=download_info.info.hashes,
126
+ subdirectory=download_info.subdirectory,
127
+ )
128
+ else:
129
+ # should never happen
130
+ raise NotImplementedError()
131
+ else:
132
+ package.version = str(dist.version)
133
+ if isinstance(download_info.info, ArchiveInfo):
134
+ if not download_info.info.hashes:
135
+ raise NotImplementedError()
136
+ link = Link(download_info.url)
137
+ if link.is_wheel:
138
+ package.wheels = [
139
+ PackageWheel(
140
+ name=link.filename,
141
+ url=download_info.url,
142
+ hashes=download_info.info.hashes,
143
+ )
144
+ ]
145
+ else:
146
+ package.sdist = PackageSdist(
147
+ name=link.filename,
148
+ url=download_info.url,
149
+ hashes=download_info.info.hashes,
150
+ )
151
+ else:
152
+ # should never happen
153
+ raise NotImplementedError()
154
+ return package
155
+
156
+
157
+ @dataclass
158
+ class Pylock:
159
+ lock_version: str = "1.0"
160
+ # (not supported) environments: Optional[List[str]]
161
+ # (not supported) requires_python: Optional[str]
162
+ # (not supported) extras: List[str] = []
163
+ # (not supported) dependency_groups: List[str] = []
164
+ created_by: str = "pip"
165
+ packages: List[Package] = dataclasses.field(default_factory=list)
166
+ # (not supported) tool: Optional[Dict[str, Any]]
167
+
168
+ def as_toml(self) -> str:
169
+ return tomli_w.dumps(dataclasses.asdict(self, dict_factory=_toml_dict_factory))
170
+
171
+ @classmethod
172
+ def from_install_requirements(
173
+ cls, install_requirements: Iterable[InstallRequirement], base_dir: Path
174
+ ) -> Self:
175
+ return cls(
176
+ packages=sorted(
177
+ (
178
+ Package.from_install_requirement(ireq, base_dir)
179
+ for ireq in install_requirements
180
+ ),
181
+ key=lambda p: p.name,
182
+ )
183
+ )
.mcp-venv/lib/python3.13/site-packages/pip/_internal/models/scheme.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ For types associated with installation schemes.
3
+
4
+ For a general overview of available schemes and their context, see
5
+ https://docs.python.org/3/install/index.html#alternate-installation.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+
10
+ SCHEME_KEYS = ["platlib", "purelib", "headers", "scripts", "data"]
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class Scheme:
15
+ """A Scheme holds paths which are used as the base directories for
16
+ artifacts associated with a Python package.
17
+ """
18
+
19
+ __slots__ = SCHEME_KEYS
20
+
21
+ platlib: str
22
+ purelib: str
23
+ headers: str
24
+ scripts: str
25
+ data: str
.mcp-venv/lib/python3.13/site-packages/pip/_internal/models/search_scope.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import itertools
2
+ import logging
3
+ import os
4
+ import posixpath
5
+ import urllib.parse
6
+ from dataclasses import dataclass
7
+ from typing import List
8
+
9
+ from pip._vendor.packaging.utils import canonicalize_name
10
+
11
+ from pip._internal.models.index import PyPI
12
+ from pip._internal.utils.compat import has_tls
13
+ from pip._internal.utils.misc import normalize_path, redact_auth_from_url
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class SearchScope:
20
+ """
21
+ Encapsulates the locations that pip is configured to search.
22
+ """
23
+
24
+ __slots__ = ["find_links", "index_urls", "no_index"]
25
+
26
+ find_links: List[str]
27
+ index_urls: List[str]
28
+ no_index: bool
29
+
30
+ @classmethod
31
+ def create(
32
+ cls,
33
+ find_links: List[str],
34
+ index_urls: List[str],
35
+ no_index: bool,
36
+ ) -> "SearchScope":
37
+ """
38
+ Create a SearchScope object after normalizing the `find_links`.
39
+ """
40
+ # Build find_links. If an argument starts with ~, it may be
41
+ # a local file relative to a home directory. So try normalizing
42
+ # it and if it exists, use the normalized version.
43
+ # This is deliberately conservative - it might be fine just to
44
+ # blindly normalize anything starting with a ~...
45
+ built_find_links: List[str] = []
46
+ for link in find_links:
47
+ if link.startswith("~"):
48
+ new_link = normalize_path(link)
49
+ if os.path.exists(new_link):
50
+ link = new_link
51
+ built_find_links.append(link)
52
+
53
+ # If we don't have TLS enabled, then WARN if anyplace we're looking
54
+ # relies on TLS.
55
+ if not has_tls():
56
+ for link in itertools.chain(index_urls, built_find_links):
57
+ parsed = urllib.parse.urlparse(link)
58
+ if parsed.scheme == "https":
59
+ logger.warning(
60
+ "pip is configured with locations that require "
61
+ "TLS/SSL, however the ssl module in Python is not "
62
+ "available."
63
+ )
64
+ break
65
+
66
+ return cls(
67
+ find_links=built_find_links,
68
+ index_urls=index_urls,
69
+ no_index=no_index,
70
+ )
71
+
72
+ def get_formatted_locations(self) -> str:
73
+ lines = []
74
+ redacted_index_urls = []
75
+ if self.index_urls and self.index_urls != [PyPI.simple_url]:
76
+ for url in self.index_urls:
77
+ redacted_index_url = redact_auth_from_url(url)
78
+
79
+ # Parse the URL
80
+ purl = urllib.parse.urlsplit(redacted_index_url)
81
+
82
+ # URL is generally invalid if scheme and netloc is missing
83
+ # there are issues with Python and URL parsing, so this test
84
+ # is a bit crude. See bpo-20271, bpo-23505. Python doesn't
85
+ # always parse invalid URLs correctly - it should raise
86
+ # exceptions for malformed URLs
87
+ if not purl.scheme and not purl.netloc:
88
+ logger.warning(
89
+ 'The index url "%s" seems invalid, please provide a scheme.',
90
+ redacted_index_url,
91
+ )
92
+
93
+ redacted_index_urls.append(redacted_index_url)
94
+
95
+ lines.append(
96
+ "Looking in indexes: {}".format(", ".join(redacted_index_urls))
97
+ )
98
+
99
+ if self.find_links:
100
+ lines.append(
101
+ "Looking in links: {}".format(
102
+ ", ".join(redact_auth_from_url(url) for url in self.find_links)
103
+ )
104
+ )
105
+ return "\n".join(lines)
106
+
107
+ def get_index_urls_locations(self, project_name: str) -> List[str]:
108
+ """Returns the locations found via self.index_urls
109
+
110
+ Checks the url_name on the main (first in the list) index and
111
+ use this url_name to produce all locations
112
+ """
113
+
114
+ def mkurl_pypi_url(url: str) -> str:
115
+ loc = posixpath.join(
116
+ url, urllib.parse.quote(canonicalize_name(project_name))
117
+ )
118
+ # For maximum compatibility with easy_install, ensure the path
119
+ # ends in a trailing slash. Although this isn't in the spec
120
+ # (and PyPI can handle it without the slash) some other index
121
+ # implementations might break if they relied on easy_install's
122
+ # behavior.
123
+ if not loc.endswith("/"):
124
+ loc = loc + "/"
125
+ return loc
126
+
127
+ return [mkurl_pypi_url(url) for url in self.index_urls]
.mcp-venv/lib/python3.13/site-packages/pip/_internal/models/selection_prefs.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ from pip._internal.models.format_control import FormatControl
4
+
5
+
6
+ # TODO: This needs Python 3.10's improved slots support for dataclasses
7
+ # to be converted into a dataclass.
8
+ class SelectionPreferences:
9
+ """
10
+ Encapsulates the candidate selection preferences for downloading
11
+ and installing files.
12
+ """
13
+
14
+ __slots__ = [
15
+ "allow_yanked",
16
+ "allow_all_prereleases",
17
+ "format_control",
18
+ "prefer_binary",
19
+ "ignore_requires_python",
20
+ ]
21
+
22
+ # Don't include an allow_yanked default value to make sure each call
23
+ # site considers whether yanked releases are allowed. This also causes
24
+ # that decision to be made explicit in the calling code, which helps
25
+ # people when reading the code.
26
+ def __init__(
27
+ self,
28
+ allow_yanked: bool,
29
+ allow_all_prereleases: bool = False,
30
+ format_control: Optional[FormatControl] = None,
31
+ prefer_binary: bool = False,
32
+ ignore_requires_python: Optional[bool] = None,
33
+ ) -> None:
34
+ """Create a SelectionPreferences object.
35
+
36
+ :param allow_yanked: Whether files marked as yanked (in the sense
37
+ of PEP 592) are permitted to be candidates for install.
38
+ :param format_control: A FormatControl object or None. Used to control
39
+ the selection of source packages / binary packages when consulting
40
+ the index and links.
41
+ :param prefer_binary: Whether to prefer an old, but valid, binary
42
+ dist over a new source dist.
43
+ :param ignore_requires_python: Whether to ignore incompatible
44
+ "Requires-Python" values in links. Defaults to False.
45
+ """
46
+ if ignore_requires_python is None:
47
+ ignore_requires_python = False
48
+
49
+ self.allow_yanked = allow_yanked
50
+ self.allow_all_prereleases = allow_all_prereleases
51
+ self.format_control = format_control
52
+ self.prefer_binary = prefer_binary
53
+ self.ignore_requires_python = ignore_requires_python
.mcp-venv/lib/python3.13/site-packages/pip/_internal/models/target_python.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from typing import List, Optional, Set, Tuple
3
+
4
+ from pip._vendor.packaging.tags import Tag
5
+
6
+ from pip._internal.utils.compatibility_tags import get_supported, version_info_to_nodot
7
+ from pip._internal.utils.misc import normalize_version_info
8
+
9
+
10
+ class TargetPython:
11
+ """
12
+ Encapsulates the properties of a Python interpreter one is targeting
13
+ for a package install, download, etc.
14
+ """
15
+
16
+ __slots__ = [
17
+ "_given_py_version_info",
18
+ "abis",
19
+ "implementation",
20
+ "platforms",
21
+ "py_version",
22
+ "py_version_info",
23
+ "_valid_tags",
24
+ "_valid_tags_set",
25
+ ]
26
+
27
+ def __init__(
28
+ self,
29
+ platforms: Optional[List[str]] = None,
30
+ py_version_info: Optional[Tuple[int, ...]] = None,
31
+ abis: Optional[List[str]] = None,
32
+ implementation: Optional[str] = None,
33
+ ) -> None:
34
+ """
35
+ :param platforms: A list of strings or None. If None, searches for
36
+ packages that are supported by the current system. Otherwise, will
37
+ find packages that can be built on the platforms passed in. These
38
+ packages will only be downloaded for distribution: they will
39
+ not be built locally.
40
+ :param py_version_info: An optional tuple of ints representing the
41
+ Python version information to use (e.g. `sys.version_info[:3]`).
42
+ This can have length 1, 2, or 3 when provided.
43
+ :param abis: A list of strings or None. This is passed to
44
+ compatibility_tags.py's get_supported() function as is.
45
+ :param implementation: A string or None. This is passed to
46
+ compatibility_tags.py's get_supported() function as is.
47
+ """
48
+ # Store the given py_version_info for when we call get_supported().
49
+ self._given_py_version_info = py_version_info
50
+
51
+ if py_version_info is None:
52
+ py_version_info = sys.version_info[:3]
53
+ else:
54
+ py_version_info = normalize_version_info(py_version_info)
55
+
56
+ py_version = ".".join(map(str, py_version_info[:2]))
57
+
58
+ self.abis = abis
59
+ self.implementation = implementation
60
+ self.platforms = platforms
61
+ self.py_version = py_version
62
+ self.py_version_info = py_version_info
63
+
64
+ # This is used to cache the return value of get_(un)sorted_tags.
65
+ self._valid_tags: Optional[List[Tag]] = None
66
+ self._valid_tags_set: Optional[Set[Tag]] = None
67
+
68
+ def format_given(self) -> str:
69
+ """
70
+ Format the given, non-None attributes for display.
71
+ """
72
+ display_version = None
73
+ if self._given_py_version_info is not None:
74
+ display_version = ".".join(
75
+ str(part) for part in self._given_py_version_info
76
+ )
77
+
78
+ key_values = [
79
+ ("platforms", self.platforms),
80
+ ("version_info", display_version),
81
+ ("abis", self.abis),
82
+ ("implementation", self.implementation),
83
+ ]
84
+ return " ".join(
85
+ f"{key}={value!r}" for key, value in key_values if value is not None
86
+ )
87
+
88
+ def get_sorted_tags(self) -> List[Tag]:
89
+ """
90
+ Return the supported PEP 425 tags to check wheel candidates against.
91
+
92
+ The tags are returned in order of preference (most preferred first).
93
+ """
94
+ if self._valid_tags is None:
95
+ # Pass versions=None if no py_version_info was given since
96
+ # versions=None uses special default logic.
97
+ py_version_info = self._given_py_version_info
98
+ if py_version_info is None:
99
+ version = None
100
+ else:
101
+ version = version_info_to_nodot(py_version_info)
102
+
103
+ tags = get_supported(
104
+ version=version,
105
+ platforms=self.platforms,
106
+ abis=self.abis,
107
+ impl=self.implementation,
108
+ )
109
+ self._valid_tags = tags
110
+
111
+ return self._valid_tags
112
+
113
+ def get_unsorted_tags(self) -> Set[Tag]:
114
+ """Exactly the same as get_sorted_tags, but returns a set.
115
+
116
+ This is important for performance.
117
+ """
118
+ if self._valid_tags_set is None:
119
+ self._valid_tags_set = set(self.get_sorted_tags())
120
+
121
+ return self._valid_tags_set
.mcp-venv/lib/python3.13/site-packages/pip/_internal/models/wheel.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Represents a wheel file and provides access to the various parts of the
2
+ name that have meaning.
3
+ """
4
+
5
+ import re
6
+ from typing import Dict, Iterable, List, Optional
7
+
8
+ from pip._vendor.packaging.tags import Tag
9
+ from pip._vendor.packaging.utils import BuildTag, parse_wheel_filename
10
+ from pip._vendor.packaging.utils import (
11
+ InvalidWheelFilename as _PackagingInvalidWheelFilename,
12
+ )
13
+
14
+ from pip._internal.exceptions import InvalidWheelFilename
15
+ from pip._internal.utils.deprecation import deprecated
16
+
17
+
18
+ class Wheel:
19
+ """A wheel file"""
20
+
21
+ legacy_wheel_file_re = re.compile(
22
+ r"""^(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]*?))
23
+ ((-(?P<build>\d[^-]*?))?-(?P<pyver>[^\s-]+?)-(?P<abi>[^\s-]+?)-(?P<plat>[^\s-]+?)
24
+ \.whl|\.dist-info)$""",
25
+ re.VERBOSE,
26
+ )
27
+
28
+ def __init__(self, filename: str) -> None:
29
+ self.filename = filename
30
+
31
+ # To make mypy happy specify type hints that can come from either
32
+ # parse_wheel_filename or the legacy_wheel_file_re match.
33
+ self.name: str
34
+ self._build_tag: Optional[BuildTag] = None
35
+
36
+ try:
37
+ wheel_info = parse_wheel_filename(filename)
38
+ self.name, _version, self._build_tag, self.file_tags = wheel_info
39
+ self.version = str(_version)
40
+ except _PackagingInvalidWheelFilename as e:
41
+ # Check if the wheel filename is in the legacy format
42
+ legacy_wheel_info = self.legacy_wheel_file_re.match(filename)
43
+ if not legacy_wheel_info:
44
+ raise InvalidWheelFilename(e.args[0]) from None
45
+
46
+ deprecated(
47
+ reason=(
48
+ f"Wheel filename {filename!r} is not correctly normalised. "
49
+ "Future versions of pip will raise the following error:\n"
50
+ f"{e.args[0]}\n\n"
51
+ ),
52
+ replacement=(
53
+ "to rename the wheel to use a correctly normalised "
54
+ "name (this may require updating the version in "
55
+ "the project metadata)"
56
+ ),
57
+ gone_in="25.3",
58
+ issue=12938,
59
+ )
60
+
61
+ self.name = legacy_wheel_info.group("name").replace("_", "-")
62
+ self.version = legacy_wheel_info.group("ver").replace("_", "-")
63
+
64
+ # Generate the file tags from the legacy wheel filename
65
+ pyversions = legacy_wheel_info.group("pyver").split(".")
66
+ abis = legacy_wheel_info.group("abi").split(".")
67
+ plats = legacy_wheel_info.group("plat").split(".")
68
+ self.file_tags = frozenset(
69
+ Tag(interpreter=py, abi=abi, platform=plat)
70
+ for py in pyversions
71
+ for abi in abis
72
+ for plat in plats
73
+ )
74
+
75
+ @property
76
+ def build_tag(self) -> BuildTag:
77
+ if self._build_tag is not None:
78
+ return self._build_tag
79
+
80
+ # Parse the build tag from the legacy wheel filename
81
+ legacy_wheel_info = self.legacy_wheel_file_re.match(self.filename)
82
+ assert legacy_wheel_info is not None, "guaranteed by filename validation"
83
+ build_tag = legacy_wheel_info.group("build")
84
+ match = re.match(r"^(\d+)(.*)$", build_tag)
85
+ assert match is not None, "guaranteed by filename validation"
86
+ build_tag_groups = match.groups()
87
+ self._build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
88
+
89
+ return self._build_tag
90
+
91
+ def get_formatted_file_tags(self) -> List[str]:
92
+ """Return the wheel's tags as a sorted list of strings."""
93
+ return sorted(str(tag) for tag in self.file_tags)
94
+
95
+ def support_index_min(self, tags: List[Tag]) -> int:
96
+ """Return the lowest index that one of the wheel's file_tag combinations
97
+ achieves in the given list of supported tags.
98
+
99
+ For example, if there are 8 supported tags and one of the file tags
100
+ is first in the list, then return 0.
101
+
102
+ :param tags: the PEP 425 tags to check the wheel against, in order
103
+ with most preferred first.
104
+
105
+ :raises ValueError: If none of the wheel's file tags match one of
106
+ the supported tags.
107
+ """
108
+ try:
109
+ return next(i for i, t in enumerate(tags) if t in self.file_tags)
110
+ except StopIteration:
111
+ raise ValueError()
112
+
113
+ def find_most_preferred_tag(
114
+ self, tags: List[Tag], tag_to_priority: Dict[Tag, int]
115
+ ) -> int:
116
+ """Return the priority of the most preferred tag that one of the wheel's file
117
+ tag combinations achieves in the given list of supported tags using the given
118
+ tag_to_priority mapping, where lower priorities are more-preferred.
119
+
120
+ This is used in place of support_index_min in some cases in order to avoid
121
+ an expensive linear scan of a large list of tags.
122
+
123
+ :param tags: the PEP 425 tags to check the wheel against.
124
+ :param tag_to_priority: a mapping from tag to priority of that tag, where
125
+ lower is more preferred.
126
+
127
+ :raises ValueError: If none of the wheel's file tags match one of
128
+ the supported tags.
129
+ """
130
+ return min(
131
+ tag_to_priority[tag] for tag in self.file_tags if tag in tag_to_priority
132
+ )
133
+
134
+ def supported(self, tags: Iterable[Tag]) -> bool:
135
+ """Return whether the wheel is compatible with one of the given tags.
136
+
137
+ :param tags: the PEP 425 tags to check the wheel against.
138
+ """
139
+ return not self.file_tags.isdisjoint(tags)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/network/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Contains purely network-related utilities."""
.mcp-venv/lib/python3.13/site-packages/pip/_internal/network/auth.py ADDED
@@ -0,0 +1,566 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Network Authentication Helpers
2
+
3
+ Contains interface (MultiDomainBasicAuth) and associated glue code for
4
+ providing credentials in the context of network requests.
5
+ """
6
+
7
+ import logging
8
+ import os
9
+ import shutil
10
+ import subprocess
11
+ import sysconfig
12
+ import typing
13
+ import urllib.parse
14
+ from abc import ABC, abstractmethod
15
+ from functools import lru_cache
16
+ from os.path import commonprefix
17
+ from pathlib import Path
18
+ from typing import Any, Dict, List, NamedTuple, Optional, Tuple
19
+
20
+ from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
21
+ from pip._vendor.requests.models import Request, Response
22
+ from pip._vendor.requests.utils import get_netrc_auth
23
+
24
+ from pip._internal.utils.logging import getLogger
25
+ from pip._internal.utils.misc import (
26
+ ask,
27
+ ask_input,
28
+ ask_password,
29
+ remove_auth_from_url,
30
+ split_auth_netloc_from_url,
31
+ )
32
+ from pip._internal.vcs.versioncontrol import AuthInfo
33
+
34
+ logger = getLogger(__name__)
35
+
36
+ KEYRING_DISABLED = False
37
+
38
+
39
+ class Credentials(NamedTuple):
40
+ url: str
41
+ username: str
42
+ password: str
43
+
44
+
45
+ class KeyRingBaseProvider(ABC):
46
+ """Keyring base provider interface"""
47
+
48
+ has_keyring: bool
49
+
50
+ @abstractmethod
51
+ def get_auth_info(
52
+ self, url: str, username: Optional[str]
53
+ ) -> Optional[AuthInfo]: ...
54
+
55
+ @abstractmethod
56
+ def save_auth_info(self, url: str, username: str, password: str) -> None: ...
57
+
58
+
59
+ class KeyRingNullProvider(KeyRingBaseProvider):
60
+ """Keyring null provider"""
61
+
62
+ has_keyring = False
63
+
64
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
65
+ return None
66
+
67
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
68
+ return None
69
+
70
+
71
+ class KeyRingPythonProvider(KeyRingBaseProvider):
72
+ """Keyring interface which uses locally imported `keyring`"""
73
+
74
+ has_keyring = True
75
+
76
+ def __init__(self) -> None:
77
+ import keyring
78
+
79
+ self.keyring = keyring
80
+
81
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
82
+ # Support keyring's get_credential interface which supports getting
83
+ # credentials without a username. This is only available for
84
+ # keyring>=15.2.0.
85
+ if hasattr(self.keyring, "get_credential"):
86
+ logger.debug("Getting credentials from keyring for %s", url)
87
+ cred = self.keyring.get_credential(url, username)
88
+ if cred is not None:
89
+ return cred.username, cred.password
90
+ return None
91
+
92
+ if username is not None:
93
+ logger.debug("Getting password from keyring for %s", url)
94
+ password = self.keyring.get_password(url, username)
95
+ if password:
96
+ return username, password
97
+ return None
98
+
99
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
100
+ self.keyring.set_password(url, username, password)
101
+
102
+
103
+ class KeyRingCliProvider(KeyRingBaseProvider):
104
+ """Provider which uses `keyring` cli
105
+
106
+ Instead of calling the keyring package installed alongside pip
107
+ we call keyring on the command line which will enable pip to
108
+ use which ever installation of keyring is available first in
109
+ PATH.
110
+ """
111
+
112
+ has_keyring = True
113
+
114
+ def __init__(self, cmd: str) -> None:
115
+ self.keyring = cmd
116
+
117
+ def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
118
+ # This is the default implementation of keyring.get_credential
119
+ # https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
120
+ if username is not None:
121
+ password = self._get_password(url, username)
122
+ if password is not None:
123
+ return username, password
124
+ return None
125
+
126
+ def save_auth_info(self, url: str, username: str, password: str) -> None:
127
+ return self._set_password(url, username, password)
128
+
129
+ def _get_password(self, service_name: str, username: str) -> Optional[str]:
130
+ """Mirror the implementation of keyring.get_password using cli"""
131
+ if self.keyring is None:
132
+ return None
133
+
134
+ cmd = [self.keyring, "get", service_name, username]
135
+ env = os.environ.copy()
136
+ env["PYTHONIOENCODING"] = "utf-8"
137
+ res = subprocess.run(
138
+ cmd,
139
+ stdin=subprocess.DEVNULL,
140
+ stdout=subprocess.PIPE,
141
+ env=env,
142
+ )
143
+ if res.returncode:
144
+ return None
145
+ return res.stdout.decode("utf-8").strip(os.linesep)
146
+
147
+ def _set_password(self, service_name: str, username: str, password: str) -> None:
148
+ """Mirror the implementation of keyring.set_password using cli"""
149
+ if self.keyring is None:
150
+ return None
151
+ env = os.environ.copy()
152
+ env["PYTHONIOENCODING"] = "utf-8"
153
+ subprocess.run(
154
+ [self.keyring, "set", service_name, username],
155
+ input=f"{password}{os.linesep}".encode(),
156
+ env=env,
157
+ check=True,
158
+ )
159
+ return None
160
+
161
+
162
+ @lru_cache(maxsize=None)
163
+ def get_keyring_provider(provider: str) -> KeyRingBaseProvider:
164
+ logger.verbose("Keyring provider requested: %s", provider)
165
+
166
+ # keyring has previously failed and been disabled
167
+ if KEYRING_DISABLED:
168
+ provider = "disabled"
169
+ if provider in ["import", "auto"]:
170
+ try:
171
+ impl = KeyRingPythonProvider()
172
+ logger.verbose("Keyring provider set: import")
173
+ return impl
174
+ except ImportError:
175
+ pass
176
+ except Exception as exc:
177
+ # In the event of an unexpected exception
178
+ # we should warn the user
179
+ msg = "Installed copy of keyring fails with exception %s"
180
+ if provider == "auto":
181
+ msg = msg + ", trying to find a keyring executable as a fallback"
182
+ logger.warning(msg, exc, exc_info=logger.isEnabledFor(logging.DEBUG))
183
+ if provider in ["subprocess", "auto"]:
184
+ cli = shutil.which("keyring")
185
+ if cli and cli.startswith(sysconfig.get_path("scripts")):
186
+ # all code within this function is stolen from shutil.which implementation
187
+ @typing.no_type_check
188
+ def PATH_as_shutil_which_determines_it() -> str:
189
+ path = os.environ.get("PATH", None)
190
+ if path is None:
191
+ try:
192
+ path = os.confstr("CS_PATH")
193
+ except (AttributeError, ValueError):
194
+ # os.confstr() or CS_PATH is not available
195
+ path = os.defpath
196
+ # bpo-35755: Don't use os.defpath if the PATH environment variable is
197
+ # set to an empty string
198
+
199
+ return path
200
+
201
+ scripts = Path(sysconfig.get_path("scripts"))
202
+
203
+ paths = []
204
+ for path in PATH_as_shutil_which_determines_it().split(os.pathsep):
205
+ p = Path(path)
206
+ try:
207
+ if not p.samefile(scripts):
208
+ paths.append(path)
209
+ except FileNotFoundError:
210
+ pass
211
+
212
+ path = os.pathsep.join(paths)
213
+
214
+ cli = shutil.which("keyring", path=path)
215
+
216
+ if cli:
217
+ logger.verbose("Keyring provider set: subprocess with executable %s", cli)
218
+ return KeyRingCliProvider(cli)
219
+
220
+ logger.verbose("Keyring provider set: disabled")
221
+ return KeyRingNullProvider()
222
+
223
+
224
+ class MultiDomainBasicAuth(AuthBase):
225
+ def __init__(
226
+ self,
227
+ prompting: bool = True,
228
+ index_urls: Optional[List[str]] = None,
229
+ keyring_provider: str = "auto",
230
+ ) -> None:
231
+ self.prompting = prompting
232
+ self.index_urls = index_urls
233
+ self.keyring_provider = keyring_provider # type: ignore[assignment]
234
+ self.passwords: Dict[str, AuthInfo] = {}
235
+ # When the user is prompted to enter credentials and keyring is
236
+ # available, we will offer to save them. If the user accepts,
237
+ # this value is set to the credentials they entered. After the
238
+ # request authenticates, the caller should call
239
+ # ``save_credentials`` to save these.
240
+ self._credentials_to_save: Optional[Credentials] = None
241
+
242
+ @property
243
+ def keyring_provider(self) -> KeyRingBaseProvider:
244
+ return get_keyring_provider(self._keyring_provider)
245
+
246
+ @keyring_provider.setter
247
+ def keyring_provider(self, provider: str) -> None:
248
+ # The free function get_keyring_provider has been decorated with
249
+ # functools.cache. If an exception occurs in get_keyring_auth that
250
+ # cache will be cleared and keyring disabled, take that into account
251
+ # if you want to remove this indirection.
252
+ self._keyring_provider = provider
253
+
254
+ @property
255
+ def use_keyring(self) -> bool:
256
+ # We won't use keyring when --no-input is passed unless
257
+ # a specific provider is requested because it might require
258
+ # user interaction
259
+ return self.prompting or self._keyring_provider not in ["auto", "disabled"]
260
+
261
+ def _get_keyring_auth(
262
+ self,
263
+ url: Optional[str],
264
+ username: Optional[str],
265
+ ) -> Optional[AuthInfo]:
266
+ """Return the tuple auth for a given url from keyring."""
267
+ # Do nothing if no url was provided
268
+ if not url:
269
+ return None
270
+
271
+ try:
272
+ return self.keyring_provider.get_auth_info(url, username)
273
+ except Exception as exc:
274
+ # Log the full exception (with stacktrace) at debug, so it'll only
275
+ # show up when running in verbose mode.
276
+ logger.debug("Keyring is skipped due to an exception", exc_info=True)
277
+ # Always log a shortened version of the exception.
278
+ logger.warning(
279
+ "Keyring is skipped due to an exception: %s",
280
+ str(exc),
281
+ )
282
+ global KEYRING_DISABLED
283
+ KEYRING_DISABLED = True
284
+ get_keyring_provider.cache_clear()
285
+ return None
286
+
287
+ def _get_index_url(self, url: str) -> Optional[str]:
288
+ """Return the original index URL matching the requested URL.
289
+
290
+ Cached or dynamically generated credentials may work against
291
+ the original index URL rather than just the netloc.
292
+
293
+ The provided url should have had its username and password
294
+ removed already. If the original index url had credentials then
295
+ they will be included in the return value.
296
+
297
+ Returns None if no matching index was found, or if --no-index
298
+ was specified by the user.
299
+ """
300
+ if not url or not self.index_urls:
301
+ return None
302
+
303
+ url = remove_auth_from_url(url).rstrip("/") + "/"
304
+ parsed_url = urllib.parse.urlsplit(url)
305
+
306
+ candidates = []
307
+
308
+ for index in self.index_urls:
309
+ index = index.rstrip("/") + "/"
310
+ parsed_index = urllib.parse.urlsplit(remove_auth_from_url(index))
311
+ if parsed_url == parsed_index:
312
+ return index
313
+
314
+ if parsed_url.netloc != parsed_index.netloc:
315
+ continue
316
+
317
+ candidate = urllib.parse.urlsplit(index)
318
+ candidates.append(candidate)
319
+
320
+ if not candidates:
321
+ return None
322
+
323
+ candidates.sort(
324
+ reverse=True,
325
+ key=lambda candidate: commonprefix(
326
+ [
327
+ parsed_url.path,
328
+ candidate.path,
329
+ ]
330
+ ).rfind("/"),
331
+ )
332
+
333
+ return urllib.parse.urlunsplit(candidates[0])
334
+
335
+ def _get_new_credentials(
336
+ self,
337
+ original_url: str,
338
+ *,
339
+ allow_netrc: bool = True,
340
+ allow_keyring: bool = False,
341
+ ) -> AuthInfo:
342
+ """Find and return credentials for the specified URL."""
343
+ # Split the credentials and netloc from the url.
344
+ url, netloc, url_user_password = split_auth_netloc_from_url(
345
+ original_url,
346
+ )
347
+
348
+ # Start with the credentials embedded in the url
349
+ username, password = url_user_password
350
+ if username is not None and password is not None:
351
+ logger.debug("Found credentials in url for %s", netloc)
352
+ return url_user_password
353
+
354
+ # Find a matching index url for this request
355
+ index_url = self._get_index_url(url)
356
+ if index_url:
357
+ # Split the credentials from the url.
358
+ index_info = split_auth_netloc_from_url(index_url)
359
+ if index_info:
360
+ index_url, _, index_url_user_password = index_info
361
+ logger.debug("Found index url %s", index_url)
362
+
363
+ # If an index URL was found, try its embedded credentials
364
+ if index_url and index_url_user_password[0] is not None:
365
+ username, password = index_url_user_password
366
+ if username is not None and password is not None:
367
+ logger.debug("Found credentials in index url for %s", netloc)
368
+ return index_url_user_password
369
+
370
+ # Get creds from netrc if we still don't have them
371
+ if allow_netrc:
372
+ netrc_auth = get_netrc_auth(original_url)
373
+ if netrc_auth:
374
+ logger.debug("Found credentials in netrc for %s", netloc)
375
+ return netrc_auth
376
+
377
+ # If we don't have a password and keyring is available, use it.
378
+ if allow_keyring:
379
+ # The index url is more specific than the netloc, so try it first
380
+ # fmt: off
381
+ kr_auth = (
382
+ self._get_keyring_auth(index_url, username) or
383
+ self._get_keyring_auth(netloc, username)
384
+ )
385
+ # fmt: on
386
+ if kr_auth:
387
+ logger.debug("Found credentials in keyring for %s", netloc)
388
+ return kr_auth
389
+
390
+ return username, password
391
+
392
+ def _get_url_and_credentials(
393
+ self, original_url: str
394
+ ) -> Tuple[str, Optional[str], Optional[str]]:
395
+ """Return the credentials to use for the provided URL.
396
+
397
+ If allowed, netrc and keyring may be used to obtain the
398
+ correct credentials.
399
+
400
+ Returns (url_without_credentials, username, password). Note
401
+ that even if the original URL contains credentials, this
402
+ function may return a different username and password.
403
+ """
404
+ url, netloc, _ = split_auth_netloc_from_url(original_url)
405
+
406
+ # Try to get credentials from original url
407
+ username, password = self._get_new_credentials(original_url)
408
+
409
+ # If credentials not found, use any stored credentials for this netloc.
410
+ # Do this if either the username or the password is missing.
411
+ # This accounts for the situation in which the user has specified
412
+ # the username in the index url, but the password comes from keyring.
413
+ if (username is None or password is None) and netloc in self.passwords:
414
+ un, pw = self.passwords[netloc]
415
+ # It is possible that the cached credentials are for a different username,
416
+ # in which case the cache should be ignored.
417
+ if username is None or username == un:
418
+ username, password = un, pw
419
+
420
+ if username is not None or password is not None:
421
+ # Convert the username and password if they're None, so that
422
+ # this netloc will show up as "cached" in the conditional above.
423
+ # Further, HTTPBasicAuth doesn't accept None, so it makes sense to
424
+ # cache the value that is going to be used.
425
+ username = username or ""
426
+ password = password or ""
427
+
428
+ # Store any acquired credentials.
429
+ self.passwords[netloc] = (username, password)
430
+
431
+ assert (
432
+ # Credentials were found
433
+ (username is not None and password is not None)
434
+ # Credentials were not found
435
+ or (username is None and password is None)
436
+ ), f"Could not load credentials from url: {original_url}"
437
+
438
+ return url, username, password
439
+
440
+ def __call__(self, req: Request) -> Request:
441
+ # Get credentials for this request
442
+ url, username, password = self._get_url_and_credentials(req.url)
443
+
444
+ # Set the url of the request to the url without any credentials
445
+ req.url = url
446
+
447
+ if username is not None and password is not None:
448
+ # Send the basic auth with this request
449
+ req = HTTPBasicAuth(username, password)(req)
450
+
451
+ # Attach a hook to handle 401 responses
452
+ req.register_hook("response", self.handle_401)
453
+
454
+ return req
455
+
456
+ # Factored out to allow for easy patching in tests
457
+ def _prompt_for_password(
458
+ self, netloc: str
459
+ ) -> Tuple[Optional[str], Optional[str], bool]:
460
+ username = ask_input(f"User for {netloc}: ") if self.prompting else None
461
+ if not username:
462
+ return None, None, False
463
+ if self.use_keyring:
464
+ auth = self._get_keyring_auth(netloc, username)
465
+ if auth and auth[0] is not None and auth[1] is not None:
466
+ return auth[0], auth[1], False
467
+ password = ask_password("Password: ")
468
+ return username, password, True
469
+
470
+ # Factored out to allow for easy patching in tests
471
+ def _should_save_password_to_keyring(self) -> bool:
472
+ if (
473
+ not self.prompting
474
+ or not self.use_keyring
475
+ or not self.keyring_provider.has_keyring
476
+ ):
477
+ return False
478
+ return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
479
+
480
+ def handle_401(self, resp: Response, **kwargs: Any) -> Response:
481
+ # We only care about 401 responses, anything else we want to just
482
+ # pass through the actual response
483
+ if resp.status_code != 401:
484
+ return resp
485
+
486
+ username, password = None, None
487
+
488
+ # Query the keyring for credentials:
489
+ if self.use_keyring:
490
+ username, password = self._get_new_credentials(
491
+ resp.url,
492
+ allow_netrc=False,
493
+ allow_keyring=True,
494
+ )
495
+
496
+ # We are not able to prompt the user so simply return the response
497
+ if not self.prompting and not username and not password:
498
+ return resp
499
+
500
+ parsed = urllib.parse.urlparse(resp.url)
501
+
502
+ # Prompt the user for a new username and password
503
+ save = False
504
+ if not username and not password:
505
+ username, password, save = self._prompt_for_password(parsed.netloc)
506
+
507
+ # Store the new username and password to use for future requests
508
+ self._credentials_to_save = None
509
+ if username is not None and password is not None:
510
+ self.passwords[parsed.netloc] = (username, password)
511
+
512
+ # Prompt to save the password to keyring
513
+ if save and self._should_save_password_to_keyring():
514
+ self._credentials_to_save = Credentials(
515
+ url=parsed.netloc,
516
+ username=username,
517
+ password=password,
518
+ )
519
+
520
+ # Consume content and release the original connection to allow our new
521
+ # request to reuse the same one.
522
+ # The result of the assignment isn't used, it's just needed to consume
523
+ # the content.
524
+ _ = resp.content
525
+ resp.raw.release_conn()
526
+
527
+ # Add our new username and password to the request
528
+ req = HTTPBasicAuth(username or "", password or "")(resp.request)
529
+ req.register_hook("response", self.warn_on_401)
530
+
531
+ # On successful request, save the credentials that were used to
532
+ # keyring. (Note that if the user responded "no" above, this member
533
+ # is not set and nothing will be saved.)
534
+ if self._credentials_to_save:
535
+ req.register_hook("response", self.save_credentials)
536
+
537
+ # Send our new request
538
+ new_resp = resp.connection.send(req, **kwargs)
539
+ new_resp.history.append(resp)
540
+
541
+ return new_resp
542
+
543
+ def warn_on_401(self, resp: Response, **kwargs: Any) -> None:
544
+ """Response callback to warn about incorrect credentials."""
545
+ if resp.status_code == 401:
546
+ logger.warning(
547
+ "401 Error, Credentials not correct for %s",
548
+ resp.request.url,
549
+ )
550
+
551
+ def save_credentials(self, resp: Response, **kwargs: Any) -> None:
552
+ """Response callback to save credentials on success."""
553
+ assert (
554
+ self.keyring_provider.has_keyring
555
+ ), "should never reach here without keyring"
556
+
557
+ creds = self._credentials_to_save
558
+ self._credentials_to_save = None
559
+ if creds and resp.status_code < 400:
560
+ try:
561
+ logger.info("Saving credentials to keyring")
562
+ self.keyring_provider.save_auth_info(
563
+ creds.url, creds.username, creds.password
564
+ )
565
+ except Exception:
566
+ logger.exception("Failed to save credentials")
.mcp-venv/lib/python3.13/site-packages/pip/_internal/network/cache.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HTTP cache implementation."""
2
+
3
+ import os
4
+ from contextlib import contextmanager
5
+ from datetime import datetime
6
+ from typing import BinaryIO, Generator, Optional, Union
7
+
8
+ from pip._vendor.cachecontrol.cache import SeparateBodyBaseCache
9
+ from pip._vendor.cachecontrol.caches import SeparateBodyFileCache
10
+ from pip._vendor.requests.models import Response
11
+
12
+ from pip._internal.utils.filesystem import adjacent_tmp_file, replace
13
+ from pip._internal.utils.misc import ensure_dir
14
+
15
+
16
+ def is_from_cache(response: Response) -> bool:
17
+ return getattr(response, "from_cache", False)
18
+
19
+
20
+ @contextmanager
21
+ def suppressed_cache_errors() -> Generator[None, None, None]:
22
+ """If we can't access the cache then we can just skip caching and process
23
+ requests as if caching wasn't enabled.
24
+ """
25
+ try:
26
+ yield
27
+ except OSError:
28
+ pass
29
+
30
+
31
+ class SafeFileCache(SeparateBodyBaseCache):
32
+ """
33
+ A file based cache which is safe to use even when the target directory may
34
+ not be accessible or writable.
35
+
36
+ There is a race condition when two processes try to write and/or read the
37
+ same entry at the same time, since each entry consists of two separate
38
+ files (https://github.com/psf/cachecontrol/issues/324). We therefore have
39
+ additional logic that makes sure that both files to be present before
40
+ returning an entry; this fixes the read side of the race condition.
41
+
42
+ For the write side, we assume that the server will only ever return the
43
+ same data for the same URL, which ought to be the case for files pip is
44
+ downloading. PyPI does not have a mechanism to swap out a wheel for
45
+ another wheel, for example. If this assumption is not true, the
46
+ CacheControl issue will need to be fixed.
47
+ """
48
+
49
+ def __init__(self, directory: str) -> None:
50
+ assert directory is not None, "Cache directory must not be None."
51
+ super().__init__()
52
+ self.directory = directory
53
+
54
+ def _get_cache_path(self, name: str) -> str:
55
+ # From cachecontrol.caches.file_cache.FileCache._fn, brought into our
56
+ # class for backwards-compatibility and to avoid using a non-public
57
+ # method.
58
+ hashed = SeparateBodyFileCache.encode(name)
59
+ parts = list(hashed[:5]) + [hashed]
60
+ return os.path.join(self.directory, *parts)
61
+
62
+ def get(self, key: str) -> Optional[bytes]:
63
+ # The cache entry is only valid if both metadata and body exist.
64
+ metadata_path = self._get_cache_path(key)
65
+ body_path = metadata_path + ".body"
66
+ if not (os.path.exists(metadata_path) and os.path.exists(body_path)):
67
+ return None
68
+ with suppressed_cache_errors():
69
+ with open(metadata_path, "rb") as f:
70
+ return f.read()
71
+
72
+ def _write(self, path: str, data: bytes) -> None:
73
+ with suppressed_cache_errors():
74
+ ensure_dir(os.path.dirname(path))
75
+
76
+ with adjacent_tmp_file(path) as f:
77
+ f.write(data)
78
+ # Inherit the read/write permissions of the cache directory
79
+ # to enable multi-user cache use-cases.
80
+ mode = (
81
+ os.stat(self.directory).st_mode
82
+ & 0o666 # select read/write permissions of cache directory
83
+ | 0o600 # set owner read/write permissions
84
+ )
85
+ # Change permissions only if there is no risk of following a symlink.
86
+ if os.chmod in os.supports_fd:
87
+ os.chmod(f.fileno(), mode)
88
+ elif os.chmod in os.supports_follow_symlinks:
89
+ os.chmod(f.name, mode, follow_symlinks=False)
90
+
91
+ replace(f.name, path)
92
+
93
+ def set(
94
+ self, key: str, value: bytes, expires: Union[int, datetime, None] = None
95
+ ) -> None:
96
+ path = self._get_cache_path(key)
97
+ self._write(path, value)
98
+
99
+ def delete(self, key: str) -> None:
100
+ path = self._get_cache_path(key)
101
+ with suppressed_cache_errors():
102
+ os.remove(path)
103
+ with suppressed_cache_errors():
104
+ os.remove(path + ".body")
105
+
106
+ def get_body(self, key: str) -> Optional[BinaryIO]:
107
+ # The cache entry is only valid if both metadata and body exist.
108
+ metadata_path = self._get_cache_path(key)
109
+ body_path = metadata_path + ".body"
110
+ if not (os.path.exists(metadata_path) and os.path.exists(body_path)):
111
+ return None
112
+ with suppressed_cache_errors():
113
+ return open(body_path, "rb")
114
+
115
+ def set_body(self, key: str, body: bytes) -> None:
116
+ path = self._get_cache_path(key) + ".body"
117
+ self._write(path, body)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/network/download.py ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Download files with progress indicators."""
2
+
3
+ import email.message
4
+ import logging
5
+ import mimetypes
6
+ import os
7
+ from http import HTTPStatus
8
+ from typing import BinaryIO, Iterable, Optional, Tuple
9
+
10
+ from pip._vendor.requests.models import Response
11
+ from pip._vendor.urllib3.exceptions import ReadTimeoutError
12
+
13
+ from pip._internal.cli.progress_bars import get_download_progress_renderer
14
+ from pip._internal.exceptions import IncompleteDownloadError, NetworkConnectionError
15
+ from pip._internal.models.index import PyPI
16
+ from pip._internal.models.link import Link
17
+ from pip._internal.network.cache import is_from_cache
18
+ from pip._internal.network.session import PipSession
19
+ from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
20
+ from pip._internal.utils.misc import format_size, redact_auth_from_url, splitext
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def _get_http_response_size(resp: Response) -> Optional[int]:
26
+ try:
27
+ return int(resp.headers["content-length"])
28
+ except (ValueError, KeyError, TypeError):
29
+ return None
30
+
31
+
32
+ def _get_http_response_etag_or_last_modified(resp: Response) -> Optional[str]:
33
+ """
34
+ Return either the ETag or Last-Modified header (or None if neither exists).
35
+ The return value can be used in an If-Range header.
36
+ """
37
+ return resp.headers.get("etag", resp.headers.get("last-modified"))
38
+
39
+
40
+ def _prepare_download(
41
+ resp: Response,
42
+ link: Link,
43
+ progress_bar: str,
44
+ total_length: Optional[int],
45
+ range_start: Optional[int] = 0,
46
+ ) -> Iterable[bytes]:
47
+ if link.netloc == PyPI.file_storage_domain:
48
+ url = link.show_url
49
+ else:
50
+ url = link.url_without_fragment
51
+
52
+ logged_url = redact_auth_from_url(url)
53
+
54
+ if total_length:
55
+ if range_start:
56
+ logged_url = (
57
+ f"{logged_url} ({format_size(range_start)}/{format_size(total_length)})"
58
+ )
59
+ else:
60
+ logged_url = f"{logged_url} ({format_size(total_length)})"
61
+
62
+ if is_from_cache(resp):
63
+ logger.info("Using cached %s", logged_url)
64
+ elif range_start:
65
+ logger.info("Resuming download %s", logged_url)
66
+ else:
67
+ logger.info("Downloading %s", logged_url)
68
+
69
+ if logger.getEffectiveLevel() > logging.INFO:
70
+ show_progress = False
71
+ elif is_from_cache(resp):
72
+ show_progress = False
73
+ elif not total_length:
74
+ show_progress = True
75
+ elif total_length > (512 * 1024):
76
+ show_progress = True
77
+ else:
78
+ show_progress = False
79
+
80
+ chunks = response_chunks(resp)
81
+
82
+ if not show_progress:
83
+ return chunks
84
+
85
+ renderer = get_download_progress_renderer(
86
+ bar_type=progress_bar, size=total_length, initial_progress=range_start
87
+ )
88
+ return renderer(chunks)
89
+
90
+
91
+ def sanitize_content_filename(filename: str) -> str:
92
+ """
93
+ Sanitize the "filename" value from a Content-Disposition header.
94
+ """
95
+ return os.path.basename(filename)
96
+
97
+
98
+ def parse_content_disposition(content_disposition: str, default_filename: str) -> str:
99
+ """
100
+ Parse the "filename" value from a Content-Disposition header, and
101
+ return the default filename if the result is empty.
102
+ """
103
+ m = email.message.Message()
104
+ m["content-type"] = content_disposition
105
+ filename = m.get_param("filename")
106
+ if filename:
107
+ # We need to sanitize the filename to prevent directory traversal
108
+ # in case the filename contains ".." path parts.
109
+ filename = sanitize_content_filename(str(filename))
110
+ return filename or default_filename
111
+
112
+
113
+ def _get_http_response_filename(resp: Response, link: Link) -> str:
114
+ """Get an ideal filename from the given HTTP response, falling back to
115
+ the link filename if not provided.
116
+ """
117
+ filename = link.filename # fallback
118
+ # Have a look at the Content-Disposition header for a better guess
119
+ content_disposition = resp.headers.get("content-disposition")
120
+ if content_disposition:
121
+ filename = parse_content_disposition(content_disposition, filename)
122
+ ext: Optional[str] = splitext(filename)[1]
123
+ if not ext:
124
+ ext = mimetypes.guess_extension(resp.headers.get("content-type", ""))
125
+ if ext:
126
+ filename += ext
127
+ if not ext and link.url != resp.url:
128
+ ext = os.path.splitext(resp.url)[1]
129
+ if ext:
130
+ filename += ext
131
+ return filename
132
+
133
+
134
+ def _http_get_download(
135
+ session: PipSession,
136
+ link: Link,
137
+ range_start: Optional[int] = 0,
138
+ if_range: Optional[str] = None,
139
+ ) -> Response:
140
+ target_url = link.url.split("#", 1)[0]
141
+ headers = HEADERS.copy()
142
+ # request a partial download
143
+ if range_start:
144
+ headers["Range"] = f"bytes={range_start}-"
145
+ # make sure the file hasn't changed
146
+ if if_range:
147
+ headers["If-Range"] = if_range
148
+ try:
149
+ resp = session.get(target_url, headers=headers, stream=True)
150
+ raise_for_status(resp)
151
+ except NetworkConnectionError as e:
152
+ assert e.response is not None
153
+ logger.critical("HTTP error %s while getting %s", e.response.status_code, link)
154
+ raise
155
+ return resp
156
+
157
+
158
+ class Downloader:
159
+ def __init__(
160
+ self,
161
+ session: PipSession,
162
+ progress_bar: str,
163
+ resume_retries: int,
164
+ ) -> None:
165
+ assert (
166
+ resume_retries >= 0
167
+ ), "Number of max resume retries must be bigger or equal to zero"
168
+ self._session = session
169
+ self._progress_bar = progress_bar
170
+ self._resume_retries = resume_retries
171
+
172
+ def __call__(self, link: Link, location: str) -> Tuple[str, str]:
173
+ """Download the file given by link into location."""
174
+ resp = _http_get_download(self._session, link)
175
+ # NOTE: The original download size needs to be passed down everywhere
176
+ # so if the download is resumed (with a HTTP Range request) the progress
177
+ # bar will report the right size.
178
+ total_length = _get_http_response_size(resp)
179
+ content_type = resp.headers.get("Content-Type", "")
180
+
181
+ filename = _get_http_response_filename(resp, link)
182
+ filepath = os.path.join(location, filename)
183
+
184
+ with open(filepath, "wb") as content_file:
185
+ bytes_received = self._process_response(
186
+ resp, link, content_file, 0, total_length
187
+ )
188
+ # If possible, check for an incomplete download and attempt resuming.
189
+ if total_length and bytes_received < total_length:
190
+ self._attempt_resume(
191
+ resp, link, content_file, total_length, bytes_received
192
+ )
193
+
194
+ return filepath, content_type
195
+
196
+ def _process_response(
197
+ self,
198
+ resp: Response,
199
+ link: Link,
200
+ content_file: BinaryIO,
201
+ bytes_received: int,
202
+ total_length: Optional[int],
203
+ ) -> int:
204
+ """Process the response and write the chunks to the file."""
205
+ chunks = _prepare_download(
206
+ resp, link, self._progress_bar, total_length, range_start=bytes_received
207
+ )
208
+ return self._write_chunks_to_file(
209
+ chunks, content_file, allow_partial=bool(total_length)
210
+ )
211
+
212
+ def _write_chunks_to_file(
213
+ self, chunks: Iterable[bytes], content_file: BinaryIO, *, allow_partial: bool
214
+ ) -> int:
215
+ """Write the chunks to the file and return the number of bytes received."""
216
+ bytes_received = 0
217
+ try:
218
+ for chunk in chunks:
219
+ bytes_received += len(chunk)
220
+ content_file.write(chunk)
221
+ except ReadTimeoutError as e:
222
+ # If partial downloads are OK (the download will be retried), don't bail.
223
+ if not allow_partial:
224
+ raise e
225
+
226
+ # Ensuring bytes_received is returned to attempt resume
227
+ logger.warning("Connection timed out while downloading.")
228
+
229
+ return bytes_received
230
+
231
+ def _attempt_resume(
232
+ self,
233
+ resp: Response,
234
+ link: Link,
235
+ content_file: BinaryIO,
236
+ total_length: Optional[int],
237
+ bytes_received: int,
238
+ ) -> None:
239
+ """Attempt to resume the download if connection was dropped."""
240
+ etag_or_last_modified = _get_http_response_etag_or_last_modified(resp)
241
+
242
+ attempts_left = self._resume_retries
243
+ while total_length and attempts_left and bytes_received < total_length:
244
+ attempts_left -= 1
245
+
246
+ logger.warning(
247
+ "Attempting to resume incomplete download (%s/%s, attempt %d)",
248
+ format_size(bytes_received),
249
+ format_size(total_length),
250
+ (self._resume_retries - attempts_left),
251
+ )
252
+
253
+ try:
254
+ # Try to resume the download using a HTTP range request.
255
+ resume_resp = _http_get_download(
256
+ self._session,
257
+ link,
258
+ range_start=bytes_received,
259
+ if_range=etag_or_last_modified,
260
+ )
261
+
262
+ # Fallback: if the server responded with 200 (i.e., the file has
263
+ # since been modified or range requests are unsupported) or any
264
+ # other unexpected status, restart the download from the beginning.
265
+ must_restart = resume_resp.status_code != HTTPStatus.PARTIAL_CONTENT
266
+ if must_restart:
267
+ bytes_received, total_length, etag_or_last_modified = (
268
+ self._reset_download_state(resume_resp, content_file)
269
+ )
270
+
271
+ bytes_received += self._process_response(
272
+ resume_resp, link, content_file, bytes_received, total_length
273
+ )
274
+ except (ConnectionError, ReadTimeoutError, OSError):
275
+ continue
276
+
277
+ # No more resume attempts. Raise an error if the download is still incomplete.
278
+ if total_length and bytes_received < total_length:
279
+ os.remove(content_file.name)
280
+ raise IncompleteDownloadError(
281
+ link, bytes_received, total_length, retries=self._resume_retries
282
+ )
283
+
284
+ def _reset_download_state(
285
+ self,
286
+ resp: Response,
287
+ content_file: BinaryIO,
288
+ ) -> Tuple[int, Optional[int], Optional[str]]:
289
+ """Reset the download state to restart downloading from the beginning."""
290
+ content_file.seek(0)
291
+ content_file.truncate()
292
+ bytes_received = 0
293
+ total_length = _get_http_response_size(resp)
294
+ etag_or_last_modified = _get_http_response_etag_or_last_modified(resp)
295
+
296
+ return bytes_received, total_length, etag_or_last_modified
297
+
298
+
299
+ class BatchDownloader:
300
+ def __init__(
301
+ self,
302
+ session: PipSession,
303
+ progress_bar: str,
304
+ resume_retries: int,
305
+ ) -> None:
306
+ self._downloader = Downloader(session, progress_bar, resume_retries)
307
+
308
+ def __call__(
309
+ self, links: Iterable[Link], location: str
310
+ ) -> Iterable[Tuple[Link, Tuple[str, str]]]:
311
+ """Download the files given by links into location."""
312
+ for link in links:
313
+ filepath, content_type = self._downloader(link, location)
314
+ yield link, (filepath, content_type)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/network/lazy_wheel.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Lazy ZIP over HTTP"""
2
+
3
+ __all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"]
4
+
5
+ from bisect import bisect_left, bisect_right
6
+ from contextlib import contextmanager
7
+ from tempfile import NamedTemporaryFile
8
+ from typing import Any, Dict, Generator, List, Optional, Tuple
9
+ from zipfile import BadZipFile, ZipFile
10
+
11
+ from pip._vendor.packaging.utils import canonicalize_name
12
+ from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
13
+
14
+ from pip._internal.metadata import BaseDistribution, MemoryWheel, get_wheel_distribution
15
+ from pip._internal.network.session import PipSession
16
+ from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
17
+
18
+
19
+ class HTTPRangeRequestUnsupported(Exception):
20
+ pass
21
+
22
+
23
+ def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistribution:
24
+ """Return a distribution object from the given wheel URL.
25
+
26
+ This uses HTTP range requests to only fetch the portion of the wheel
27
+ containing metadata, just enough for the object to be constructed.
28
+ If such requests are not supported, HTTPRangeRequestUnsupported
29
+ is raised.
30
+ """
31
+ with LazyZipOverHTTP(url, session) as zf:
32
+ # For read-only ZIP files, ZipFile only needs methods read,
33
+ # seek, seekable and tell, not the whole IO protocol.
34
+ wheel = MemoryWheel(zf.name, zf) # type: ignore
35
+ # After context manager exit, wheel.name
36
+ # is an invalid file by intention.
37
+ return get_wheel_distribution(wheel, canonicalize_name(name))
38
+
39
+
40
+ class LazyZipOverHTTP:
41
+ """File-like object mapped to a ZIP file over HTTP.
42
+
43
+ This uses HTTP range requests to lazily fetch the file's content,
44
+ which is supposed to be fed to ZipFile. If such requests are not
45
+ supported by the server, raise HTTPRangeRequestUnsupported
46
+ during initialization.
47
+ """
48
+
49
+ def __init__(
50
+ self, url: str, session: PipSession, chunk_size: int = CONTENT_CHUNK_SIZE
51
+ ) -> None:
52
+ head = session.head(url, headers=HEADERS)
53
+ raise_for_status(head)
54
+ assert head.status_code == 200
55
+ self._session, self._url, self._chunk_size = session, url, chunk_size
56
+ self._length = int(head.headers["Content-Length"])
57
+ self._file = NamedTemporaryFile()
58
+ self.truncate(self._length)
59
+ self._left: List[int] = []
60
+ self._right: List[int] = []
61
+ if "bytes" not in head.headers.get("Accept-Ranges", "none"):
62
+ raise HTTPRangeRequestUnsupported("range request is not supported")
63
+ self._check_zip()
64
+
65
+ @property
66
+ def mode(self) -> str:
67
+ """Opening mode, which is always rb."""
68
+ return "rb"
69
+
70
+ @property
71
+ def name(self) -> str:
72
+ """Path to the underlying file."""
73
+ return self._file.name
74
+
75
+ def seekable(self) -> bool:
76
+ """Return whether random access is supported, which is True."""
77
+ return True
78
+
79
+ def close(self) -> None:
80
+ """Close the file."""
81
+ self._file.close()
82
+
83
+ @property
84
+ def closed(self) -> bool:
85
+ """Whether the file is closed."""
86
+ return self._file.closed
87
+
88
+ def read(self, size: int = -1) -> bytes:
89
+ """Read up to size bytes from the object and return them.
90
+
91
+ As a convenience, if size is unspecified or -1,
92
+ all bytes until EOF are returned. Fewer than
93
+ size bytes may be returned if EOF is reached.
94
+ """
95
+ download_size = max(size, self._chunk_size)
96
+ start, length = self.tell(), self._length
97
+ stop = length if size < 0 else min(start + download_size, length)
98
+ start = max(0, stop - download_size)
99
+ self._download(start, stop - 1)
100
+ return self._file.read(size)
101
+
102
+ def readable(self) -> bool:
103
+ """Return whether the file is readable, which is True."""
104
+ return True
105
+
106
+ def seek(self, offset: int, whence: int = 0) -> int:
107
+ """Change stream position and return the new absolute position.
108
+
109
+ Seek to offset relative position indicated by whence:
110
+ * 0: Start of stream (the default). pos should be >= 0;
111
+ * 1: Current position - pos may be negative;
112
+ * 2: End of stream - pos usually negative.
113
+ """
114
+ return self._file.seek(offset, whence)
115
+
116
+ def tell(self) -> int:
117
+ """Return the current position."""
118
+ return self._file.tell()
119
+
120
+ def truncate(self, size: Optional[int] = None) -> int:
121
+ """Resize the stream to the given size in bytes.
122
+
123
+ If size is unspecified resize to the current position.
124
+ The current stream position isn't changed.
125
+
126
+ Return the new file size.
127
+ """
128
+ return self._file.truncate(size)
129
+
130
+ def writable(self) -> bool:
131
+ """Return False."""
132
+ return False
133
+
134
+ def __enter__(self) -> "LazyZipOverHTTP":
135
+ self._file.__enter__()
136
+ return self
137
+
138
+ def __exit__(self, *exc: Any) -> None:
139
+ self._file.__exit__(*exc)
140
+
141
+ @contextmanager
142
+ def _stay(self) -> Generator[None, None, None]:
143
+ """Return a context manager keeping the position.
144
+
145
+ At the end of the block, seek back to original position.
146
+ """
147
+ pos = self.tell()
148
+ try:
149
+ yield
150
+ finally:
151
+ self.seek(pos)
152
+
153
+ def _check_zip(self) -> None:
154
+ """Check and download until the file is a valid ZIP."""
155
+ end = self._length - 1
156
+ for start in reversed(range(0, end, self._chunk_size)):
157
+ self._download(start, end)
158
+ with self._stay():
159
+ try:
160
+ # For read-only ZIP files, ZipFile only needs
161
+ # methods read, seek, seekable and tell.
162
+ ZipFile(self)
163
+ except BadZipFile:
164
+ pass
165
+ else:
166
+ break
167
+
168
+ def _stream_response(
169
+ self, start: int, end: int, base_headers: Dict[str, str] = HEADERS
170
+ ) -> Response:
171
+ """Return HTTP response to a range request from start to end."""
172
+ headers = base_headers.copy()
173
+ headers["Range"] = f"bytes={start}-{end}"
174
+ # TODO: Get range requests to be correctly cached
175
+ headers["Cache-Control"] = "no-cache"
176
+ return self._session.get(self._url, headers=headers, stream=True)
177
+
178
+ def _merge(
179
+ self, start: int, end: int, left: int, right: int
180
+ ) -> Generator[Tuple[int, int], None, None]:
181
+ """Return a generator of intervals to be fetched.
182
+
183
+ Args:
184
+ start (int): Start of needed interval
185
+ end (int): End of needed interval
186
+ left (int): Index of first overlapping downloaded data
187
+ right (int): Index after last overlapping downloaded data
188
+ """
189
+ lslice, rslice = self._left[left:right], self._right[left:right]
190
+ i = start = min([start] + lslice[:1])
191
+ end = max([end] + rslice[-1:])
192
+ for j, k in zip(lslice, rslice):
193
+ if j > i:
194
+ yield i, j - 1
195
+ i = k + 1
196
+ if i <= end:
197
+ yield i, end
198
+ self._left[left:right], self._right[left:right] = [start], [end]
199
+
200
+ def _download(self, start: int, end: int) -> None:
201
+ """Download bytes from start to end inclusively."""
202
+ with self._stay():
203
+ left = bisect_left(self._right, start)
204
+ right = bisect_right(self._left, end)
205
+ for start, end in self._merge(start, end, left, right):
206
+ response = self._stream_response(start, end)
207
+ response.raise_for_status()
208
+ self.seek(start)
209
+ for chunk in response_chunks(response, self._chunk_size):
210
+ self._file.write(chunk)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/network/session.py ADDED
@@ -0,0 +1,523 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """PipSession and supporting code, containing all pip-specific
2
+ network request configuration and behavior.
3
+ """
4
+
5
+ import email.utils
6
+ import functools
7
+ import io
8
+ import ipaddress
9
+ import json
10
+ import logging
11
+ import mimetypes
12
+ import os
13
+ import platform
14
+ import shutil
15
+ import subprocess
16
+ import sys
17
+ import urllib.parse
18
+ import warnings
19
+ from typing import (
20
+ TYPE_CHECKING,
21
+ Any,
22
+ Dict,
23
+ Generator,
24
+ List,
25
+ Mapping,
26
+ Optional,
27
+ Sequence,
28
+ Tuple,
29
+ Union,
30
+ )
31
+
32
+ from pip._vendor import requests, urllib3
33
+ from pip._vendor.cachecontrol import CacheControlAdapter as _BaseCacheControlAdapter
34
+ from pip._vendor.requests.adapters import DEFAULT_POOLBLOCK, BaseAdapter
35
+ from pip._vendor.requests.adapters import HTTPAdapter as _BaseHTTPAdapter
36
+ from pip._vendor.requests.models import PreparedRequest, Response
37
+ from pip._vendor.requests.structures import CaseInsensitiveDict
38
+ from pip._vendor.urllib3.connectionpool import ConnectionPool
39
+ from pip._vendor.urllib3.exceptions import InsecureRequestWarning
40
+
41
+ from pip import __version__
42
+ from pip._internal.metadata import get_default_environment
43
+ from pip._internal.models.link import Link
44
+ from pip._internal.network.auth import MultiDomainBasicAuth
45
+ from pip._internal.network.cache import SafeFileCache
46
+
47
+ # Import ssl from compat so the initial import occurs in only one place.
48
+ from pip._internal.utils.compat import has_tls
49
+ from pip._internal.utils.glibc import libc_ver
50
+ from pip._internal.utils.misc import build_url_from_netloc, parse_netloc
51
+ from pip._internal.utils.urls import url_to_path
52
+
53
+ if TYPE_CHECKING:
54
+ from ssl import SSLContext
55
+
56
+ from pip._vendor.urllib3.poolmanager import PoolManager
57
+
58
+
59
+ logger = logging.getLogger(__name__)
60
+
61
+ SecureOrigin = Tuple[str, str, Optional[Union[int, str]]]
62
+
63
+
64
+ # Ignore warning raised when using --trusted-host.
65
+ warnings.filterwarnings("ignore", category=InsecureRequestWarning)
66
+
67
+
68
+ SECURE_ORIGINS: List[SecureOrigin] = [
69
+ # protocol, hostname, port
70
+ # Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC)
71
+ ("https", "*", "*"),
72
+ ("*", "localhost", "*"),
73
+ ("*", "127.0.0.0/8", "*"),
74
+ ("*", "::1/128", "*"),
75
+ ("file", "*", None),
76
+ # ssh is always secure.
77
+ ("ssh", "*", "*"),
78
+ ]
79
+
80
+
81
+ # These are environment variables present when running under various
82
+ # CI systems. For each variable, some CI systems that use the variable
83
+ # are indicated. The collection was chosen so that for each of a number
84
+ # of popular systems, at least one of the environment variables is used.
85
+ # This list is used to provide some indication of and lower bound for
86
+ # CI traffic to PyPI. Thus, it is okay if the list is not comprehensive.
87
+ # For more background, see: https://github.com/pypa/pip/issues/5499
88
+ CI_ENVIRONMENT_VARIABLES = (
89
+ # Azure Pipelines
90
+ "BUILD_BUILDID",
91
+ # Jenkins
92
+ "BUILD_ID",
93
+ # AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI
94
+ "CI",
95
+ # Explicit environment variable.
96
+ "PIP_IS_CI",
97
+ )
98
+
99
+
100
+ def looks_like_ci() -> bool:
101
+ """
102
+ Return whether it looks like pip is running under CI.
103
+ """
104
+ # We don't use the method of checking for a tty (e.g. using isatty())
105
+ # because some CI systems mimic a tty (e.g. Travis CI). Thus that
106
+ # method doesn't provide definitive information in either direction.
107
+ return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES)
108
+
109
+
110
+ @functools.lru_cache(maxsize=1)
111
+ def user_agent() -> str:
112
+ """
113
+ Return a string representing the user agent.
114
+ """
115
+ data: Dict[str, Any] = {
116
+ "installer": {"name": "pip", "version": __version__},
117
+ "python": platform.python_version(),
118
+ "implementation": {
119
+ "name": platform.python_implementation(),
120
+ },
121
+ }
122
+
123
+ if data["implementation"]["name"] == "CPython":
124
+ data["implementation"]["version"] = platform.python_version()
125
+ elif data["implementation"]["name"] == "PyPy":
126
+ pypy_version_info = sys.pypy_version_info # type: ignore
127
+ if pypy_version_info.releaselevel == "final":
128
+ pypy_version_info = pypy_version_info[:3]
129
+ data["implementation"]["version"] = ".".join(
130
+ [str(x) for x in pypy_version_info]
131
+ )
132
+ elif data["implementation"]["name"] == "Jython":
133
+ # Complete Guess
134
+ data["implementation"]["version"] = platform.python_version()
135
+ elif data["implementation"]["name"] == "IronPython":
136
+ # Complete Guess
137
+ data["implementation"]["version"] = platform.python_version()
138
+
139
+ if sys.platform.startswith("linux"):
140
+ from pip._vendor import distro
141
+
142
+ linux_distribution = distro.name(), distro.version(), distro.codename()
143
+ distro_infos: Dict[str, Any] = dict(
144
+ filter(
145
+ lambda x: x[1],
146
+ zip(["name", "version", "id"], linux_distribution),
147
+ )
148
+ )
149
+ libc = dict(
150
+ filter(
151
+ lambda x: x[1],
152
+ zip(["lib", "version"], libc_ver()),
153
+ )
154
+ )
155
+ if libc:
156
+ distro_infos["libc"] = libc
157
+ if distro_infos:
158
+ data["distro"] = distro_infos
159
+
160
+ if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
161
+ data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
162
+
163
+ if platform.system():
164
+ data.setdefault("system", {})["name"] = platform.system()
165
+
166
+ if platform.release():
167
+ data.setdefault("system", {})["release"] = platform.release()
168
+
169
+ if platform.machine():
170
+ data["cpu"] = platform.machine()
171
+
172
+ if has_tls():
173
+ import _ssl as ssl
174
+
175
+ data["openssl_version"] = ssl.OPENSSL_VERSION
176
+
177
+ setuptools_dist = get_default_environment().get_distribution("setuptools")
178
+ if setuptools_dist is not None:
179
+ data["setuptools_version"] = str(setuptools_dist.version)
180
+
181
+ if shutil.which("rustc") is not None:
182
+ # If for any reason `rustc --version` fails, silently ignore it
183
+ try:
184
+ rustc_output = subprocess.check_output(
185
+ ["rustc", "--version"], stderr=subprocess.STDOUT, timeout=0.5
186
+ )
187
+ except Exception:
188
+ pass
189
+ else:
190
+ if rustc_output.startswith(b"rustc "):
191
+ # The format of `rustc --version` is:
192
+ # `b'rustc 1.52.1 (9bc8c42bb 2021-05-09)\n'`
193
+ # We extract just the middle (1.52.1) part
194
+ data["rustc_version"] = rustc_output.split(b" ")[1].decode()
195
+
196
+ # Use None rather than False so as not to give the impression that
197
+ # pip knows it is not being run under CI. Rather, it is a null or
198
+ # inconclusive result. Also, we include some value rather than no
199
+ # value to make it easier to know that the check has been run.
200
+ data["ci"] = True if looks_like_ci() else None
201
+
202
+ user_data = os.environ.get("PIP_USER_AGENT_USER_DATA")
203
+ if user_data is not None:
204
+ data["user_data"] = user_data
205
+
206
+ return "{data[installer][name]}/{data[installer][version]} {json}".format(
207
+ data=data,
208
+ json=json.dumps(data, separators=(",", ":"), sort_keys=True),
209
+ )
210
+
211
+
212
+ class LocalFSAdapter(BaseAdapter):
213
+ def send(
214
+ self,
215
+ request: PreparedRequest,
216
+ stream: bool = False,
217
+ timeout: Optional[Union[float, Tuple[float, float]]] = None,
218
+ verify: Union[bool, str] = True,
219
+ cert: Optional[Union[str, Tuple[str, str]]] = None,
220
+ proxies: Optional[Mapping[str, str]] = None,
221
+ ) -> Response:
222
+ pathname = url_to_path(request.url)
223
+
224
+ resp = Response()
225
+ resp.status_code = 200
226
+ resp.url = request.url
227
+
228
+ try:
229
+ stats = os.stat(pathname)
230
+ except OSError as exc:
231
+ # format the exception raised as a io.BytesIO object,
232
+ # to return a better error message:
233
+ resp.status_code = 404
234
+ resp.reason = type(exc).__name__
235
+ resp.raw = io.BytesIO(f"{resp.reason}: {exc}".encode())
236
+ else:
237
+ modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
238
+ content_type = mimetypes.guess_type(pathname)[0] or "text/plain"
239
+ resp.headers = CaseInsensitiveDict(
240
+ {
241
+ "Content-Type": content_type,
242
+ "Content-Length": stats.st_size,
243
+ "Last-Modified": modified,
244
+ }
245
+ )
246
+
247
+ resp.raw = open(pathname, "rb")
248
+ resp.close = resp.raw.close
249
+
250
+ return resp
251
+
252
+ def close(self) -> None:
253
+ pass
254
+
255
+
256
+ class _SSLContextAdapterMixin:
257
+ """Mixin to add the ``ssl_context`` constructor argument to HTTP adapters.
258
+
259
+ The additional argument is forwarded directly to the pool manager. This allows us
260
+ to dynamically decide what SSL store to use at runtime, which is used to implement
261
+ the optional ``truststore`` backend.
262
+ """
263
+
264
+ def __init__(
265
+ self,
266
+ *,
267
+ ssl_context: Optional["SSLContext"] = None,
268
+ **kwargs: Any,
269
+ ) -> None:
270
+ self._ssl_context = ssl_context
271
+ super().__init__(**kwargs)
272
+
273
+ def init_poolmanager(
274
+ self,
275
+ connections: int,
276
+ maxsize: int,
277
+ block: bool = DEFAULT_POOLBLOCK,
278
+ **pool_kwargs: Any,
279
+ ) -> "PoolManager":
280
+ if self._ssl_context is not None:
281
+ pool_kwargs.setdefault("ssl_context", self._ssl_context)
282
+ return super().init_poolmanager( # type: ignore[misc]
283
+ connections=connections,
284
+ maxsize=maxsize,
285
+ block=block,
286
+ **pool_kwargs,
287
+ )
288
+
289
+
290
+ class HTTPAdapter(_SSLContextAdapterMixin, _BaseHTTPAdapter):
291
+ pass
292
+
293
+
294
+ class CacheControlAdapter(_SSLContextAdapterMixin, _BaseCacheControlAdapter):
295
+ pass
296
+
297
+
298
+ class InsecureHTTPAdapter(HTTPAdapter):
299
+ def cert_verify(
300
+ self,
301
+ conn: ConnectionPool,
302
+ url: str,
303
+ verify: Union[bool, str],
304
+ cert: Optional[Union[str, Tuple[str, str]]],
305
+ ) -> None:
306
+ super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
307
+
308
+
309
+ class InsecureCacheControlAdapter(CacheControlAdapter):
310
+ def cert_verify(
311
+ self,
312
+ conn: ConnectionPool,
313
+ url: str,
314
+ verify: Union[bool, str],
315
+ cert: Optional[Union[str, Tuple[str, str]]],
316
+ ) -> None:
317
+ super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
318
+
319
+
320
+ class PipSession(requests.Session):
321
+ timeout: Optional[int] = None
322
+
323
+ def __init__(
324
+ self,
325
+ *args: Any,
326
+ retries: int = 0,
327
+ cache: Optional[str] = None,
328
+ trusted_hosts: Sequence[str] = (),
329
+ index_urls: Optional[List[str]] = None,
330
+ ssl_context: Optional["SSLContext"] = None,
331
+ **kwargs: Any,
332
+ ) -> None:
333
+ """
334
+ :param trusted_hosts: Domains not to emit warnings for when not using
335
+ HTTPS.
336
+ """
337
+ super().__init__(*args, **kwargs)
338
+
339
+ # Namespace the attribute with "pip_" just in case to prevent
340
+ # possible conflicts with the base class.
341
+ self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = []
342
+ self.pip_proxy = None
343
+
344
+ # Attach our User Agent to the request
345
+ self.headers["User-Agent"] = user_agent()
346
+
347
+ # Attach our Authentication handler to the session
348
+ self.auth = MultiDomainBasicAuth(index_urls=index_urls)
349
+
350
+ # Create our urllib3.Retry instance which will allow us to customize
351
+ # how we handle retries.
352
+ retries = urllib3.Retry(
353
+ # Set the total number of retries that a particular request can
354
+ # have.
355
+ total=retries,
356
+ # A 503 error from PyPI typically means that the Fastly -> Origin
357
+ # connection got interrupted in some way. A 503 error in general
358
+ # is typically considered a transient error so we'll go ahead and
359
+ # retry it.
360
+ # A 500 may indicate transient error in Amazon S3
361
+ # A 502 may be a transient error from a CDN like CloudFlare or CloudFront
362
+ # A 520 or 527 - may indicate transient error in CloudFlare
363
+ status_forcelist=[500, 502, 503, 520, 527],
364
+ # Add a small amount of back off between failed requests in
365
+ # order to prevent hammering the service.
366
+ backoff_factor=0.25,
367
+ ) # type: ignore
368
+
369
+ # Our Insecure HTTPAdapter disables HTTPS validation. It does not
370
+ # support caching so we'll use it for all http:// URLs.
371
+ # If caching is disabled, we will also use it for
372
+ # https:// hosts that we've marked as ignoring
373
+ # TLS errors for (trusted-hosts).
374
+ insecure_adapter = InsecureHTTPAdapter(max_retries=retries)
375
+
376
+ # We want to _only_ cache responses on securely fetched origins or when
377
+ # the host is specified as trusted. We do this because
378
+ # we can't validate the response of an insecurely/untrusted fetched
379
+ # origin, and we don't want someone to be able to poison the cache and
380
+ # require manual eviction from the cache to fix it.
381
+ if cache:
382
+ secure_adapter = CacheControlAdapter(
383
+ cache=SafeFileCache(cache),
384
+ max_retries=retries,
385
+ ssl_context=ssl_context,
386
+ )
387
+ self._trusted_host_adapter = InsecureCacheControlAdapter(
388
+ cache=SafeFileCache(cache),
389
+ max_retries=retries,
390
+ )
391
+ else:
392
+ secure_adapter = HTTPAdapter(max_retries=retries, ssl_context=ssl_context)
393
+ self._trusted_host_adapter = insecure_adapter
394
+
395
+ self.mount("https://", secure_adapter)
396
+ self.mount("http://", insecure_adapter)
397
+
398
+ # Enable file:// urls
399
+ self.mount("file://", LocalFSAdapter())
400
+
401
+ for host in trusted_hosts:
402
+ self.add_trusted_host(host, suppress_logging=True)
403
+
404
+ def update_index_urls(self, new_index_urls: List[str]) -> None:
405
+ """
406
+ :param new_index_urls: New index urls to update the authentication
407
+ handler with.
408
+ """
409
+ self.auth.index_urls = new_index_urls
410
+
411
+ def add_trusted_host(
412
+ self, host: str, source: Optional[str] = None, suppress_logging: bool = False
413
+ ) -> None:
414
+ """
415
+ :param host: It is okay to provide a host that has previously been
416
+ added.
417
+ :param source: An optional source string, for logging where the host
418
+ string came from.
419
+ """
420
+ if not suppress_logging:
421
+ msg = f"adding trusted host: {host!r}"
422
+ if source is not None:
423
+ msg += f" (from {source})"
424
+ logger.info(msg)
425
+
426
+ parsed_host, parsed_port = parse_netloc(host)
427
+ if parsed_host is None:
428
+ raise ValueError(f"Trusted host URL must include a host part: {host!r}")
429
+ if (parsed_host, parsed_port) not in self.pip_trusted_origins:
430
+ self.pip_trusted_origins.append((parsed_host, parsed_port))
431
+
432
+ self.mount(
433
+ build_url_from_netloc(host, scheme="http") + "/", self._trusted_host_adapter
434
+ )
435
+ self.mount(build_url_from_netloc(host) + "/", self._trusted_host_adapter)
436
+ if not parsed_port:
437
+ self.mount(
438
+ build_url_from_netloc(host, scheme="http") + ":",
439
+ self._trusted_host_adapter,
440
+ )
441
+ # Mount wildcard ports for the same host.
442
+ self.mount(build_url_from_netloc(host) + ":", self._trusted_host_adapter)
443
+
444
+ def iter_secure_origins(self) -> Generator[SecureOrigin, None, None]:
445
+ yield from SECURE_ORIGINS
446
+ for host, port in self.pip_trusted_origins:
447
+ yield ("*", host, "*" if port is None else port)
448
+
449
+ def is_secure_origin(self, location: Link) -> bool:
450
+ # Determine if this url used a secure transport mechanism
451
+ parsed = urllib.parse.urlparse(str(location))
452
+ origin_protocol, origin_host, origin_port = (
453
+ parsed.scheme,
454
+ parsed.hostname,
455
+ parsed.port,
456
+ )
457
+
458
+ # The protocol to use to see if the protocol matches.
459
+ # Don't count the repository type as part of the protocol: in
460
+ # cases such as "git+ssh", only use "ssh". (I.e., Only verify against
461
+ # the last scheme.)
462
+ origin_protocol = origin_protocol.rsplit("+", 1)[-1]
463
+
464
+ # Determine if our origin is a secure origin by looking through our
465
+ # hardcoded list of secure origins, as well as any additional ones
466
+ # configured on this PackageFinder instance.
467
+ for secure_origin in self.iter_secure_origins():
468
+ secure_protocol, secure_host, secure_port = secure_origin
469
+ if origin_protocol != secure_protocol and secure_protocol != "*":
470
+ continue
471
+
472
+ try:
473
+ addr = ipaddress.ip_address(origin_host or "")
474
+ network = ipaddress.ip_network(secure_host)
475
+ except ValueError:
476
+ # We don't have both a valid address or a valid network, so
477
+ # we'll check this origin against hostnames.
478
+ if (
479
+ origin_host
480
+ and origin_host.lower() != secure_host.lower()
481
+ and secure_host != "*"
482
+ ):
483
+ continue
484
+ else:
485
+ # We have a valid address and network, so see if the address
486
+ # is contained within the network.
487
+ if addr not in network:
488
+ continue
489
+
490
+ # Check to see if the port matches.
491
+ if (
492
+ origin_port != secure_port
493
+ and secure_port != "*"
494
+ and secure_port is not None
495
+ ):
496
+ continue
497
+
498
+ # If we've gotten here, then this origin matches the current
499
+ # secure origin and we should return True
500
+ return True
501
+
502
+ # If we've gotten to this point, then the origin isn't secure and we
503
+ # will not accept it as a valid location to search. We will however
504
+ # log a warning that we are ignoring it.
505
+ logger.warning(
506
+ "The repository located at %s is not a trusted or secure host and "
507
+ "is being ignored. If this repository is available via HTTPS we "
508
+ "recommend you use HTTPS instead, otherwise you may silence "
509
+ "this warning and allow it anyway with '--trusted-host %s'.",
510
+ origin_host,
511
+ origin_host,
512
+ )
513
+
514
+ return False
515
+
516
+ def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response:
517
+ # Allow setting a default timeout on a session
518
+ kwargs.setdefault("timeout", self.timeout)
519
+ # Allow setting a default proxies on a session
520
+ kwargs.setdefault("proxies", self.proxies)
521
+
522
+ # Dispatch the actual request
523
+ return super().request(method, url, *args, **kwargs)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/network/utils.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Generator
2
+
3
+ from pip._vendor.requests.models import Response
4
+
5
+ from pip._internal.exceptions import NetworkConnectionError
6
+
7
+ # The following comments and HTTP headers were originally added by
8
+ # Donald Stufft in git commit 22c562429a61bb77172039e480873fb239dd8c03.
9
+ #
10
+ # We use Accept-Encoding: identity here because requests defaults to
11
+ # accepting compressed responses. This breaks in a variety of ways
12
+ # depending on how the server is configured.
13
+ # - Some servers will notice that the file isn't a compressible file
14
+ # and will leave the file alone and with an empty Content-Encoding
15
+ # - Some servers will notice that the file is already compressed and
16
+ # will leave the file alone, adding a Content-Encoding: gzip header
17
+ # - Some servers won't notice anything at all and will take a file
18
+ # that's already been compressed and compress it again, and set
19
+ # the Content-Encoding: gzip header
20
+ # By setting this to request only the identity encoding we're hoping
21
+ # to eliminate the third case. Hopefully there does not exist a server
22
+ # which when given a file will notice it is already compressed and that
23
+ # you're not asking for a compressed file and will then decompress it
24
+ # before sending because if that's the case I don't think it'll ever be
25
+ # possible to make this work.
26
+ HEADERS: Dict[str, str] = {"Accept-Encoding": "identity"}
27
+
28
+ DOWNLOAD_CHUNK_SIZE = 256 * 1024
29
+
30
+
31
+ def raise_for_status(resp: Response) -> None:
32
+ http_error_msg = ""
33
+ if isinstance(resp.reason, bytes):
34
+ # We attempt to decode utf-8 first because some servers
35
+ # choose to localize their reason strings. If the string
36
+ # isn't utf-8, we fall back to iso-8859-1 for all other
37
+ # encodings.
38
+ try:
39
+ reason = resp.reason.decode("utf-8")
40
+ except UnicodeDecodeError:
41
+ reason = resp.reason.decode("iso-8859-1")
42
+ else:
43
+ reason = resp.reason
44
+
45
+ if 400 <= resp.status_code < 500:
46
+ http_error_msg = (
47
+ f"{resp.status_code} Client Error: {reason} for url: {resp.url}"
48
+ )
49
+
50
+ elif 500 <= resp.status_code < 600:
51
+ http_error_msg = (
52
+ f"{resp.status_code} Server Error: {reason} for url: {resp.url}"
53
+ )
54
+
55
+ if http_error_msg:
56
+ raise NetworkConnectionError(http_error_msg, response=resp)
57
+
58
+
59
+ def response_chunks(
60
+ response: Response, chunk_size: int = DOWNLOAD_CHUNK_SIZE
61
+ ) -> Generator[bytes, None, None]:
62
+ """Given a requests Response, provide the data chunks."""
63
+ try:
64
+ # Special case for urllib3.
65
+ for chunk in response.raw.stream(
66
+ chunk_size,
67
+ # We use decode_content=False here because we don't
68
+ # want urllib3 to mess with the raw bytes we get
69
+ # from the server. If we decompress inside of
70
+ # urllib3 then we cannot verify the checksum
71
+ # because the checksum will be of the compressed
72
+ # file. This breakage will only occur if the
73
+ # server adds a Content-Encoding header, which
74
+ # depends on how the server was configured:
75
+ # - Some servers will notice that the file isn't a
76
+ # compressible file and will leave the file alone
77
+ # and with an empty Content-Encoding
78
+ # - Some servers will notice that the file is
79
+ # already compressed and will leave the file
80
+ # alone and will add a Content-Encoding: gzip
81
+ # header
82
+ # - Some servers won't notice anything at all and
83
+ # will take a file that's already been compressed
84
+ # and compress it again and set the
85
+ # Content-Encoding: gzip header
86
+ #
87
+ # By setting this not to decode automatically we
88
+ # hope to eliminate problems with the second case.
89
+ decode_content=False,
90
+ ):
91
+ yield chunk
92
+ except AttributeError:
93
+ # Standard file-like object.
94
+ while True:
95
+ chunk = response.raw.read(chunk_size)
96
+ if not chunk:
97
+ break
98
+ yield chunk
.mcp-venv/lib/python3.13/site-packages/pip/_internal/network/xmlrpc.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """xmlrpclib.Transport implementation"""
2
+
3
+ import logging
4
+ import urllib.parse
5
+ import xmlrpc.client
6
+ from typing import TYPE_CHECKING, Tuple
7
+
8
+ from pip._internal.exceptions import NetworkConnectionError
9
+ from pip._internal.network.session import PipSession
10
+ from pip._internal.network.utils import raise_for_status
11
+
12
+ if TYPE_CHECKING:
13
+ from xmlrpc.client import _HostType, _Marshallable
14
+
15
+ from _typeshed import SizedBuffer
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class PipXmlrpcTransport(xmlrpc.client.Transport):
21
+ """Provide a `xmlrpclib.Transport` implementation via a `PipSession`
22
+ object.
23
+ """
24
+
25
+ def __init__(
26
+ self, index_url: str, session: PipSession, use_datetime: bool = False
27
+ ) -> None:
28
+ super().__init__(use_datetime)
29
+ index_parts = urllib.parse.urlparse(index_url)
30
+ self._scheme = index_parts.scheme
31
+ self._session = session
32
+
33
+ def request(
34
+ self,
35
+ host: "_HostType",
36
+ handler: str,
37
+ request_body: "SizedBuffer",
38
+ verbose: bool = False,
39
+ ) -> Tuple["_Marshallable", ...]:
40
+ assert isinstance(host, str)
41
+ parts = (self._scheme, host, handler, None, None, None)
42
+ url = urllib.parse.urlunparse(parts)
43
+ try:
44
+ headers = {"Content-Type": "text/xml"}
45
+ response = self._session.post(
46
+ url,
47
+ data=request_body,
48
+ headers=headers,
49
+ stream=True,
50
+ )
51
+ raise_for_status(response)
52
+ self.verbose = verbose
53
+ return self.parse_response(response.raw)
54
+ except NetworkConnectionError as exc:
55
+ assert exc.response
56
+ logger.critical(
57
+ "HTTP error %s while getting %s",
58
+ exc.response.status_code,
59
+ url,
60
+ )
61
+ raise
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/__init__.py ADDED
File without changes
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/__init__.py ADDED
File without changes
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/build_tracker.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import contextlib
2
+ import hashlib
3
+ import logging
4
+ import os
5
+ from types import TracebackType
6
+ from typing import Dict, Generator, Optional, Type, Union
7
+
8
+ from pip._internal.req.req_install import InstallRequirement
9
+ from pip._internal.utils.temp_dir import TempDirectory
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ @contextlib.contextmanager
15
+ def update_env_context_manager(**changes: str) -> Generator[None, None, None]:
16
+ target = os.environ
17
+
18
+ # Save values from the target and change them.
19
+ non_existent_marker = object()
20
+ saved_values: Dict[str, Union[object, str]] = {}
21
+ for name, new_value in changes.items():
22
+ try:
23
+ saved_values[name] = target[name]
24
+ except KeyError:
25
+ saved_values[name] = non_existent_marker
26
+ target[name] = new_value
27
+
28
+ try:
29
+ yield
30
+ finally:
31
+ # Restore original values in the target.
32
+ for name, original_value in saved_values.items():
33
+ if original_value is non_existent_marker:
34
+ del target[name]
35
+ else:
36
+ assert isinstance(original_value, str) # for mypy
37
+ target[name] = original_value
38
+
39
+
40
+ @contextlib.contextmanager
41
+ def get_build_tracker() -> Generator["BuildTracker", None, None]:
42
+ root = os.environ.get("PIP_BUILD_TRACKER")
43
+ with contextlib.ExitStack() as ctx:
44
+ if root is None:
45
+ root = ctx.enter_context(TempDirectory(kind="build-tracker")).path
46
+ ctx.enter_context(update_env_context_manager(PIP_BUILD_TRACKER=root))
47
+ logger.debug("Initialized build tracking at %s", root)
48
+
49
+ with BuildTracker(root) as tracker:
50
+ yield tracker
51
+
52
+
53
+ class TrackerId(str):
54
+ """Uniquely identifying string provided to the build tracker."""
55
+
56
+
57
+ class BuildTracker:
58
+ """Ensure that an sdist cannot request itself as a setup requirement.
59
+
60
+ When an sdist is prepared, it identifies its setup requirements in the
61
+ context of ``BuildTracker.track()``. If a requirement shows up recursively, this
62
+ raises an exception.
63
+
64
+ This stops fork bombs embedded in malicious packages."""
65
+
66
+ def __init__(self, root: str) -> None:
67
+ self._root = root
68
+ self._entries: Dict[TrackerId, InstallRequirement] = {}
69
+ logger.debug("Created build tracker: %s", self._root)
70
+
71
+ def __enter__(self) -> "BuildTracker":
72
+ logger.debug("Entered build tracker: %s", self._root)
73
+ return self
74
+
75
+ def __exit__(
76
+ self,
77
+ exc_type: Optional[Type[BaseException]],
78
+ exc_val: Optional[BaseException],
79
+ exc_tb: Optional[TracebackType],
80
+ ) -> None:
81
+ self.cleanup()
82
+
83
+ def _entry_path(self, key: TrackerId) -> str:
84
+ hashed = hashlib.sha224(key.encode()).hexdigest()
85
+ return os.path.join(self._root, hashed)
86
+
87
+ def add(self, req: InstallRequirement, key: TrackerId) -> None:
88
+ """Add an InstallRequirement to build tracking."""
89
+
90
+ # Get the file to write information about this requirement.
91
+ entry_path = self._entry_path(key)
92
+
93
+ # Try reading from the file. If it exists and can be read from, a build
94
+ # is already in progress, so a LookupError is raised.
95
+ try:
96
+ with open(entry_path) as fp:
97
+ contents = fp.read()
98
+ except FileNotFoundError:
99
+ pass
100
+ else:
101
+ message = f"{req.link} is already being built: {contents}"
102
+ raise LookupError(message)
103
+
104
+ # If we're here, req should really not be building already.
105
+ assert key not in self._entries
106
+
107
+ # Start tracking this requirement.
108
+ with open(entry_path, "w", encoding="utf-8") as fp:
109
+ fp.write(str(req))
110
+ self._entries[key] = req
111
+
112
+ logger.debug("Added %s to build tracker %r", req, self._root)
113
+
114
+ def remove(self, req: InstallRequirement, key: TrackerId) -> None:
115
+ """Remove an InstallRequirement from build tracking."""
116
+
117
+ # Delete the created file and the corresponding entry.
118
+ os.unlink(self._entry_path(key))
119
+ del self._entries[key]
120
+
121
+ logger.debug("Removed %s from build tracker %r", req, self._root)
122
+
123
+ def cleanup(self) -> None:
124
+ for key, req in list(self._entries.items()):
125
+ self.remove(req, key)
126
+
127
+ logger.debug("Removed build tracker: %r", self._root)
128
+
129
+ @contextlib.contextmanager
130
+ def track(self, req: InstallRequirement, key: str) -> Generator[None, None, None]:
131
+ """Ensure that `key` cannot install itself as a setup requirement.
132
+
133
+ :raises LookupError: If `key` was already provided in a parent invocation of
134
+ the context introduced by this method."""
135
+ tracker_id = TrackerId(key)
136
+ self.add(req, tracker_id)
137
+ yield
138
+ self.remove(req, tracker_id)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/metadata.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Metadata generation logic for source distributions."""
2
+
3
+ import os
4
+
5
+ from pip._vendor.pyproject_hooks import BuildBackendHookCaller
6
+
7
+ from pip._internal.build_env import BuildEnvironment
8
+ from pip._internal.exceptions import (
9
+ InstallationSubprocessError,
10
+ MetadataGenerationFailed,
11
+ )
12
+ from pip._internal.utils.subprocess import runner_with_spinner_message
13
+ from pip._internal.utils.temp_dir import TempDirectory
14
+
15
+
16
+ def generate_metadata(
17
+ build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str
18
+ ) -> str:
19
+ """Generate metadata using mechanisms described in PEP 517.
20
+
21
+ Returns the generated metadata directory.
22
+ """
23
+ metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
24
+
25
+ metadata_dir = metadata_tmpdir.path
26
+
27
+ with build_env:
28
+ # Note that BuildBackendHookCaller implements a fallback for
29
+ # prepare_metadata_for_build_wheel, so we don't have to
30
+ # consider the possibility that this hook doesn't exist.
31
+ runner = runner_with_spinner_message("Preparing metadata (pyproject.toml)")
32
+ with backend.subprocess_runner(runner):
33
+ try:
34
+ distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir)
35
+ except InstallationSubprocessError as error:
36
+ raise MetadataGenerationFailed(package_details=details) from error
37
+
38
+ return os.path.join(metadata_dir, distinfo_dir)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/metadata_editable.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Metadata generation logic for source distributions."""
2
+
3
+ import os
4
+
5
+ from pip._vendor.pyproject_hooks import BuildBackendHookCaller
6
+
7
+ from pip._internal.build_env import BuildEnvironment
8
+ from pip._internal.exceptions import (
9
+ InstallationSubprocessError,
10
+ MetadataGenerationFailed,
11
+ )
12
+ from pip._internal.utils.subprocess import runner_with_spinner_message
13
+ from pip._internal.utils.temp_dir import TempDirectory
14
+
15
+
16
+ def generate_editable_metadata(
17
+ build_env: BuildEnvironment, backend: BuildBackendHookCaller, details: str
18
+ ) -> str:
19
+ """Generate metadata using mechanisms described in PEP 660.
20
+
21
+ Returns the generated metadata directory.
22
+ """
23
+ metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
24
+
25
+ metadata_dir = metadata_tmpdir.path
26
+
27
+ with build_env:
28
+ # Note that BuildBackendHookCaller implements a fallback for
29
+ # prepare_metadata_for_build_wheel/editable, so we don't have to
30
+ # consider the possibility that this hook doesn't exist.
31
+ runner = runner_with_spinner_message(
32
+ "Preparing editable metadata (pyproject.toml)"
33
+ )
34
+ with backend.subprocess_runner(runner):
35
+ try:
36
+ distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir)
37
+ except InstallationSubprocessError as error:
38
+ raise MetadataGenerationFailed(package_details=details) from error
39
+
40
+ assert distinfo_dir is not None
41
+ return os.path.join(metadata_dir, distinfo_dir)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/metadata_legacy.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Metadata generation logic for legacy source distributions."""
2
+
3
+ import logging
4
+ import os
5
+
6
+ from pip._internal.build_env import BuildEnvironment
7
+ from pip._internal.cli.spinners import open_spinner
8
+ from pip._internal.exceptions import (
9
+ InstallationError,
10
+ InstallationSubprocessError,
11
+ MetadataGenerationFailed,
12
+ )
13
+ from pip._internal.utils.setuptools_build import make_setuptools_egg_info_args
14
+ from pip._internal.utils.subprocess import call_subprocess
15
+ from pip._internal.utils.temp_dir import TempDirectory
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def _find_egg_info(directory: str) -> str:
21
+ """Find an .egg-info subdirectory in `directory`."""
22
+ filenames = [f for f in os.listdir(directory) if f.endswith(".egg-info")]
23
+
24
+ if not filenames:
25
+ raise InstallationError(f"No .egg-info directory found in {directory}")
26
+
27
+ if len(filenames) > 1:
28
+ raise InstallationError(
29
+ f"More than one .egg-info directory found in {directory}"
30
+ )
31
+
32
+ return os.path.join(directory, filenames[0])
33
+
34
+
35
+ def generate_metadata(
36
+ build_env: BuildEnvironment,
37
+ setup_py_path: str,
38
+ source_dir: str,
39
+ isolated: bool,
40
+ details: str,
41
+ ) -> str:
42
+ """Generate metadata using setup.py-based defacto mechanisms.
43
+
44
+ Returns the generated metadata directory.
45
+ """
46
+ logger.debug(
47
+ "Running setup.py (path:%s) egg_info for package %s",
48
+ setup_py_path,
49
+ details,
50
+ )
51
+
52
+ egg_info_dir = TempDirectory(kind="pip-egg-info", globally_managed=True).path
53
+
54
+ args = make_setuptools_egg_info_args(
55
+ setup_py_path,
56
+ egg_info_dir=egg_info_dir,
57
+ no_user_config=isolated,
58
+ )
59
+
60
+ with build_env:
61
+ with open_spinner("Preparing metadata (setup.py)") as spinner:
62
+ try:
63
+ call_subprocess(
64
+ args,
65
+ cwd=source_dir,
66
+ command_desc="python setup.py egg_info",
67
+ spinner=spinner,
68
+ )
69
+ except InstallationSubprocessError as error:
70
+ raise MetadataGenerationFailed(package_details=details) from error
71
+
72
+ # Return the .egg-info directory.
73
+ return _find_egg_info(egg_info_dir)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/wheel.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ from typing import Optional
4
+
5
+ from pip._vendor.pyproject_hooks import BuildBackendHookCaller
6
+
7
+ from pip._internal.utils.subprocess import runner_with_spinner_message
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def build_wheel_pep517(
13
+ name: str,
14
+ backend: BuildBackendHookCaller,
15
+ metadata_directory: str,
16
+ tempd: str,
17
+ ) -> Optional[str]:
18
+ """Build one InstallRequirement using the PEP 517 build process.
19
+
20
+ Returns path to wheel if successfully built. Otherwise, returns None.
21
+ """
22
+ assert metadata_directory is not None
23
+ try:
24
+ logger.debug("Destination directory: %s", tempd)
25
+
26
+ runner = runner_with_spinner_message(
27
+ f"Building wheel for {name} (pyproject.toml)"
28
+ )
29
+ with backend.subprocess_runner(runner):
30
+ wheel_name = backend.build_wheel(
31
+ tempd,
32
+ metadata_directory=metadata_directory,
33
+ )
34
+ except Exception:
35
+ logger.error("Failed building wheel for %s", name)
36
+ return None
37
+ return os.path.join(tempd, wheel_name)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/wheel_editable.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ from typing import Optional
4
+
5
+ from pip._vendor.pyproject_hooks import BuildBackendHookCaller, HookMissing
6
+
7
+ from pip._internal.utils.subprocess import runner_with_spinner_message
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def build_wheel_editable(
13
+ name: str,
14
+ backend: BuildBackendHookCaller,
15
+ metadata_directory: str,
16
+ tempd: str,
17
+ ) -> Optional[str]:
18
+ """Build one InstallRequirement using the PEP 660 build process.
19
+
20
+ Returns path to wheel if successfully built. Otherwise, returns None.
21
+ """
22
+ assert metadata_directory is not None
23
+ try:
24
+ logger.debug("Destination directory: %s", tempd)
25
+
26
+ runner = runner_with_spinner_message(
27
+ f"Building editable for {name} (pyproject.toml)"
28
+ )
29
+ with backend.subprocess_runner(runner):
30
+ try:
31
+ wheel_name = backend.build_editable(
32
+ tempd,
33
+ metadata_directory=metadata_directory,
34
+ )
35
+ except HookMissing as e:
36
+ logger.error(
37
+ "Cannot build editable %s because the build "
38
+ "backend does not have the %s hook",
39
+ name,
40
+ e,
41
+ )
42
+ return None
43
+ except Exception:
44
+ logger.error("Failed building editable for %s", name)
45
+ return None
46
+ return os.path.join(tempd, wheel_name)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/build/wheel_legacy.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os.path
3
+ from typing import List, Optional
4
+
5
+ from pip._internal.cli.spinners import open_spinner
6
+ from pip._internal.utils.deprecation import deprecated
7
+ from pip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args
8
+ from pip._internal.utils.subprocess import call_subprocess, format_command_args
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def format_command_result(
14
+ command_args: List[str],
15
+ command_output: str,
16
+ ) -> str:
17
+ """Format command information for logging."""
18
+ command_desc = format_command_args(command_args)
19
+ text = f"Command arguments: {command_desc}\n"
20
+
21
+ if not command_output:
22
+ text += "Command output: None"
23
+ elif logger.getEffectiveLevel() > logging.DEBUG:
24
+ text += "Command output: [use --verbose to show]"
25
+ else:
26
+ if not command_output.endswith("\n"):
27
+ command_output += "\n"
28
+ text += f"Command output:\n{command_output}"
29
+
30
+ return text
31
+
32
+
33
+ def get_legacy_build_wheel_path(
34
+ names: List[str],
35
+ temp_dir: str,
36
+ name: str,
37
+ command_args: List[str],
38
+ command_output: str,
39
+ ) -> Optional[str]:
40
+ """Return the path to the wheel in the temporary build directory."""
41
+ # Sort for determinism.
42
+ names = sorted(names)
43
+ if not names:
44
+ msg = f"Legacy build of wheel for {name!r} created no files.\n"
45
+ msg += format_command_result(command_args, command_output)
46
+ logger.warning(msg)
47
+ return None
48
+
49
+ if len(names) > 1:
50
+ msg = (
51
+ f"Legacy build of wheel for {name!r} created more than one file.\n"
52
+ f"Filenames (choosing first): {names}\n"
53
+ )
54
+ msg += format_command_result(command_args, command_output)
55
+ logger.warning(msg)
56
+
57
+ return os.path.join(temp_dir, names[0])
58
+
59
+
60
+ def build_wheel_legacy(
61
+ name: str,
62
+ setup_py_path: str,
63
+ source_dir: str,
64
+ global_options: List[str],
65
+ build_options: List[str],
66
+ tempd: str,
67
+ ) -> Optional[str]:
68
+ """Build one unpacked package using the "legacy" build process.
69
+
70
+ Returns path to wheel if successfully built. Otherwise, returns None.
71
+ """
72
+ deprecated(
73
+ reason=(
74
+ f"Building {name!r} using the legacy setup.py bdist_wheel mechanism, "
75
+ "which will be removed in a future version."
76
+ ),
77
+ replacement=(
78
+ "to use the standardized build interface by "
79
+ "setting the `--use-pep517` option, "
80
+ "(possibly combined with `--no-build-isolation`), "
81
+ f"or adding a `pyproject.toml` file to the source tree of {name!r}"
82
+ ),
83
+ gone_in="25.3",
84
+ issue=6334,
85
+ )
86
+
87
+ wheel_args = make_setuptools_bdist_wheel_args(
88
+ setup_py_path,
89
+ global_options=global_options,
90
+ build_options=build_options,
91
+ destination_dir=tempd,
92
+ )
93
+
94
+ spin_message = f"Building wheel for {name} (setup.py)"
95
+ with open_spinner(spin_message) as spinner:
96
+ logger.debug("Destination directory: %s", tempd)
97
+
98
+ try:
99
+ output = call_subprocess(
100
+ wheel_args,
101
+ command_desc="python setup.py bdist_wheel",
102
+ cwd=source_dir,
103
+ spinner=spinner,
104
+ )
105
+ except Exception:
106
+ spinner.finish("error")
107
+ logger.error("Failed building wheel for %s", name)
108
+ return None
109
+
110
+ names = os.listdir(tempd)
111
+ wheel_path = get_legacy_build_wheel_path(
112
+ names=names,
113
+ temp_dir=tempd,
114
+ name=name,
115
+ command_args=wheel_args,
116
+ command_output=output,
117
+ )
118
+ return wheel_path
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/check.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Validation of dependencies of packages"""
2
+
3
+ import logging
4
+ from contextlib import suppress
5
+ from email.parser import Parser
6
+ from functools import reduce
7
+ from typing import (
8
+ Callable,
9
+ Dict,
10
+ FrozenSet,
11
+ Generator,
12
+ Iterable,
13
+ List,
14
+ NamedTuple,
15
+ Optional,
16
+ Set,
17
+ Tuple,
18
+ )
19
+
20
+ from pip._vendor.packaging.requirements import Requirement
21
+ from pip._vendor.packaging.tags import Tag, parse_tag
22
+ from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
23
+ from pip._vendor.packaging.version import Version
24
+
25
+ from pip._internal.distributions import make_distribution_for_install_requirement
26
+ from pip._internal.metadata import get_default_environment
27
+ from pip._internal.metadata.base import BaseDistribution
28
+ from pip._internal.req.req_install import InstallRequirement
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class PackageDetails(NamedTuple):
34
+ version: Version
35
+ dependencies: List[Requirement]
36
+
37
+
38
+ # Shorthands
39
+ PackageSet = Dict[NormalizedName, PackageDetails]
40
+ Missing = Tuple[NormalizedName, Requirement]
41
+ Conflicting = Tuple[NormalizedName, Version, Requirement]
42
+
43
+ MissingDict = Dict[NormalizedName, List[Missing]]
44
+ ConflictingDict = Dict[NormalizedName, List[Conflicting]]
45
+ CheckResult = Tuple[MissingDict, ConflictingDict]
46
+ ConflictDetails = Tuple[PackageSet, CheckResult]
47
+
48
+
49
+ def create_package_set_from_installed() -> Tuple[PackageSet, bool]:
50
+ """Converts a list of distributions into a PackageSet."""
51
+ package_set = {}
52
+ problems = False
53
+ env = get_default_environment()
54
+ for dist in env.iter_installed_distributions(local_only=False, skip=()):
55
+ name = dist.canonical_name
56
+ try:
57
+ dependencies = list(dist.iter_dependencies())
58
+ package_set[name] = PackageDetails(dist.version, dependencies)
59
+ except (OSError, ValueError) as e:
60
+ # Don't crash on unreadable or broken metadata.
61
+ logger.warning("Error parsing dependencies of %s: %s", name, e)
62
+ problems = True
63
+ return package_set, problems
64
+
65
+
66
+ def check_package_set(
67
+ package_set: PackageSet, should_ignore: Optional[Callable[[str], bool]] = None
68
+ ) -> CheckResult:
69
+ """Check if a package set is consistent
70
+
71
+ If should_ignore is passed, it should be a callable that takes a
72
+ package name and returns a boolean.
73
+ """
74
+
75
+ missing = {}
76
+ conflicting = {}
77
+
78
+ for package_name, package_detail in package_set.items():
79
+ # Info about dependencies of package_name
80
+ missing_deps: Set[Missing] = set()
81
+ conflicting_deps: Set[Conflicting] = set()
82
+
83
+ if should_ignore and should_ignore(package_name):
84
+ continue
85
+
86
+ for req in package_detail.dependencies:
87
+ name = canonicalize_name(req.name)
88
+
89
+ # Check if it's missing
90
+ if name not in package_set:
91
+ missed = True
92
+ if req.marker is not None:
93
+ missed = req.marker.evaluate({"extra": ""})
94
+ if missed:
95
+ missing_deps.add((name, req))
96
+ continue
97
+
98
+ # Check if there's a conflict
99
+ version = package_set[name].version
100
+ if not req.specifier.contains(version, prereleases=True):
101
+ conflicting_deps.add((name, version, req))
102
+
103
+ if missing_deps:
104
+ missing[package_name] = sorted(missing_deps, key=str)
105
+ if conflicting_deps:
106
+ conflicting[package_name] = sorted(conflicting_deps, key=str)
107
+
108
+ return missing, conflicting
109
+
110
+
111
+ def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDetails:
112
+ """For checking if the dependency graph would be consistent after \
113
+ installing given requirements
114
+ """
115
+ # Start from the current state
116
+ package_set, _ = create_package_set_from_installed()
117
+ # Install packages
118
+ would_be_installed = _simulate_installation_of(to_install, package_set)
119
+
120
+ # Only warn about directly-dependent packages; create a whitelist of them
121
+ whitelist = _create_whitelist(would_be_installed, package_set)
122
+
123
+ return (
124
+ package_set,
125
+ check_package_set(
126
+ package_set, should_ignore=lambda name: name not in whitelist
127
+ ),
128
+ )
129
+
130
+
131
+ def check_unsupported(
132
+ packages: Iterable[BaseDistribution],
133
+ supported_tags: Iterable[Tag],
134
+ ) -> Generator[BaseDistribution, None, None]:
135
+ for p in packages:
136
+ with suppress(FileNotFoundError):
137
+ wheel_file = p.read_text("WHEEL")
138
+ wheel_tags: FrozenSet[Tag] = reduce(
139
+ frozenset.union,
140
+ map(parse_tag, Parser().parsestr(wheel_file).get_all("Tag", [])),
141
+ frozenset(),
142
+ )
143
+ if wheel_tags.isdisjoint(supported_tags):
144
+ yield p
145
+
146
+
147
+ def _simulate_installation_of(
148
+ to_install: List[InstallRequirement], package_set: PackageSet
149
+ ) -> Set[NormalizedName]:
150
+ """Computes the version of packages after installing to_install."""
151
+ # Keep track of packages that were installed
152
+ installed = set()
153
+
154
+ # Modify it as installing requirement_set would (assuming no errors)
155
+ for inst_req in to_install:
156
+ abstract_dist = make_distribution_for_install_requirement(inst_req)
157
+ dist = abstract_dist.get_metadata_distribution()
158
+ name = dist.canonical_name
159
+ package_set[name] = PackageDetails(dist.version, list(dist.iter_dependencies()))
160
+
161
+ installed.add(name)
162
+
163
+ return installed
164
+
165
+
166
+ def _create_whitelist(
167
+ would_be_installed: Set[NormalizedName], package_set: PackageSet
168
+ ) -> Set[NormalizedName]:
169
+ packages_affected = set(would_be_installed)
170
+
171
+ for package_name in package_set:
172
+ if package_name in packages_affected:
173
+ continue
174
+
175
+ for req in package_set[package_name].dependencies:
176
+ if canonicalize_name(req.name) in packages_affected:
177
+ packages_affected.add(package_name)
178
+ break
179
+
180
+ return packages_affected
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/freeze.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections
2
+ import logging
3
+ import os
4
+ from dataclasses import dataclass, field
5
+ from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set
6
+
7
+ from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
8
+ from pip._vendor.packaging.version import InvalidVersion
9
+
10
+ from pip._internal.exceptions import BadCommand, InstallationError
11
+ from pip._internal.metadata import BaseDistribution, get_environment
12
+ from pip._internal.req.constructors import (
13
+ install_req_from_editable,
14
+ install_req_from_line,
15
+ )
16
+ from pip._internal.req.req_file import COMMENT_RE
17
+ from pip._internal.utils.direct_url_helpers import direct_url_as_pep440_direct_reference
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class _EditableInfo(NamedTuple):
23
+ requirement: str
24
+ comments: List[str]
25
+
26
+
27
+ def freeze(
28
+ requirement: Optional[List[str]] = None,
29
+ local_only: bool = False,
30
+ user_only: bool = False,
31
+ paths: Optional[List[str]] = None,
32
+ isolated: bool = False,
33
+ exclude_editable: bool = False,
34
+ skip: Container[str] = (),
35
+ ) -> Generator[str, None, None]:
36
+ installations: Dict[str, FrozenRequirement] = {}
37
+
38
+ dists = get_environment(paths).iter_installed_distributions(
39
+ local_only=local_only,
40
+ skip=(),
41
+ user_only=user_only,
42
+ )
43
+ for dist in dists:
44
+ req = FrozenRequirement.from_dist(dist)
45
+ if exclude_editable and req.editable:
46
+ continue
47
+ installations[req.canonical_name] = req
48
+
49
+ if requirement:
50
+ # the options that don't get turned into an InstallRequirement
51
+ # should only be emitted once, even if the same option is in multiple
52
+ # requirements files, so we need to keep track of what has been emitted
53
+ # so that we don't emit it again if it's seen again
54
+ emitted_options: Set[str] = set()
55
+ # keep track of which files a requirement is in so that we can
56
+ # give an accurate warning if a requirement appears multiple times.
57
+ req_files: Dict[str, List[str]] = collections.defaultdict(list)
58
+ for req_file_path in requirement:
59
+ with open(req_file_path) as req_file:
60
+ for line in req_file:
61
+ if (
62
+ not line.strip()
63
+ or line.strip().startswith("#")
64
+ or line.startswith(
65
+ (
66
+ "-r",
67
+ "--requirement",
68
+ "-f",
69
+ "--find-links",
70
+ "-i",
71
+ "--index-url",
72
+ "--pre",
73
+ "--trusted-host",
74
+ "--process-dependency-links",
75
+ "--extra-index-url",
76
+ "--use-feature",
77
+ )
78
+ )
79
+ ):
80
+ line = line.rstrip()
81
+ if line not in emitted_options:
82
+ emitted_options.add(line)
83
+ yield line
84
+ continue
85
+
86
+ if line.startswith("-e") or line.startswith("--editable"):
87
+ if line.startswith("-e"):
88
+ line = line[2:].strip()
89
+ else:
90
+ line = line[len("--editable") :].strip().lstrip("=")
91
+ line_req = install_req_from_editable(
92
+ line,
93
+ isolated=isolated,
94
+ )
95
+ else:
96
+ line_req = install_req_from_line(
97
+ COMMENT_RE.sub("", line).strip(),
98
+ isolated=isolated,
99
+ )
100
+
101
+ if not line_req.name:
102
+ logger.info(
103
+ "Skipping line in requirement file [%s] because "
104
+ "it's not clear what it would install: %s",
105
+ req_file_path,
106
+ line.strip(),
107
+ )
108
+ logger.info(
109
+ " (add #egg=PackageName to the URL to avoid"
110
+ " this warning)"
111
+ )
112
+ else:
113
+ line_req_canonical_name = canonicalize_name(line_req.name)
114
+ if line_req_canonical_name not in installations:
115
+ # either it's not installed, or it is installed
116
+ # but has been processed already
117
+ if not req_files[line_req.name]:
118
+ logger.warning(
119
+ "Requirement file [%s] contains %s, but "
120
+ "package %r is not installed",
121
+ req_file_path,
122
+ COMMENT_RE.sub("", line).strip(),
123
+ line_req.name,
124
+ )
125
+ else:
126
+ req_files[line_req.name].append(req_file_path)
127
+ else:
128
+ yield str(installations[line_req_canonical_name]).rstrip()
129
+ del installations[line_req_canonical_name]
130
+ req_files[line_req.name].append(req_file_path)
131
+
132
+ # Warn about requirements that were included multiple times (in a
133
+ # single requirements file or in different requirements files).
134
+ for name, files in req_files.items():
135
+ if len(files) > 1:
136
+ logger.warning(
137
+ "Requirement %s included multiple times [%s]",
138
+ name,
139
+ ", ".join(sorted(set(files))),
140
+ )
141
+
142
+ yield ("## The following requirements were added by pip freeze:")
143
+ for installation in sorted(installations.values(), key=lambda x: x.name.lower()):
144
+ if installation.canonical_name not in skip:
145
+ yield str(installation).rstrip()
146
+
147
+
148
+ def _format_as_name_version(dist: BaseDistribution) -> str:
149
+ try:
150
+ dist_version = dist.version
151
+ except InvalidVersion:
152
+ # legacy version
153
+ return f"{dist.raw_name}==={dist.raw_version}"
154
+ else:
155
+ return f"{dist.raw_name}=={dist_version}"
156
+
157
+
158
+ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
159
+ """
160
+ Compute and return values (req, comments) for use in
161
+ FrozenRequirement.from_dist().
162
+ """
163
+ editable_project_location = dist.editable_project_location
164
+ assert editable_project_location
165
+ location = os.path.normcase(os.path.abspath(editable_project_location))
166
+
167
+ from pip._internal.vcs import RemoteNotFoundError, RemoteNotValidError, vcs
168
+
169
+ vcs_backend = vcs.get_backend_for_dir(location)
170
+
171
+ if vcs_backend is None:
172
+ display = _format_as_name_version(dist)
173
+ logger.debug(
174
+ 'No VCS found for editable requirement "%s" in: %r',
175
+ display,
176
+ location,
177
+ )
178
+ return _EditableInfo(
179
+ requirement=location,
180
+ comments=[f"# Editable install with no version control ({display})"],
181
+ )
182
+
183
+ vcs_name = type(vcs_backend).__name__
184
+
185
+ try:
186
+ req = vcs_backend.get_src_requirement(location, dist.raw_name)
187
+ except RemoteNotFoundError:
188
+ display = _format_as_name_version(dist)
189
+ return _EditableInfo(
190
+ requirement=location,
191
+ comments=[f"# Editable {vcs_name} install with no remote ({display})"],
192
+ )
193
+ except RemoteNotValidError as ex:
194
+ display = _format_as_name_version(dist)
195
+ return _EditableInfo(
196
+ requirement=location,
197
+ comments=[
198
+ f"# Editable {vcs_name} install ({display}) with either a deleted "
199
+ f"local remote or invalid URI:",
200
+ f"# '{ex.url}'",
201
+ ],
202
+ )
203
+ except BadCommand:
204
+ logger.warning(
205
+ "cannot determine version of editable source in %s "
206
+ "(%s command not found in path)",
207
+ location,
208
+ vcs_backend.name,
209
+ )
210
+ return _EditableInfo(requirement=location, comments=[])
211
+ except InstallationError as exc:
212
+ logger.warning("Error when trying to get requirement for VCS system %s", exc)
213
+ else:
214
+ return _EditableInfo(requirement=req, comments=[])
215
+
216
+ logger.warning("Could not determine repository location of %s", location)
217
+
218
+ return _EditableInfo(
219
+ requirement=location,
220
+ comments=["## !! Could not determine repository location"],
221
+ )
222
+
223
+
224
+ @dataclass(frozen=True)
225
+ class FrozenRequirement:
226
+ name: str
227
+ req: str
228
+ editable: bool
229
+ comments: Iterable[str] = field(default_factory=tuple)
230
+
231
+ @property
232
+ def canonical_name(self) -> NormalizedName:
233
+ return canonicalize_name(self.name)
234
+
235
+ @classmethod
236
+ def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":
237
+ editable = dist.editable
238
+ if editable:
239
+ req, comments = _get_editable_info(dist)
240
+ else:
241
+ comments = []
242
+ direct_url = dist.direct_url
243
+ if direct_url:
244
+ # if PEP 610 metadata is present, use it
245
+ req = direct_url_as_pep440_direct_reference(direct_url, dist.raw_name)
246
+ else:
247
+ # name==version requirement
248
+ req = _format_as_name_version(dist)
249
+
250
+ return cls(dist.raw_name, req, editable, comments=comments)
251
+
252
+ def __str__(self) -> str:
253
+ req = self.req
254
+ if self.editable:
255
+ req = f"-e {req}"
256
+ return "\n".join(list(self.comments) + [str(req)]) + "\n"
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/install/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """For modules related to installing packages."""
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/install/editable_legacy.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Legacy editable installation process, i.e. `setup.py develop`."""
2
+
3
+ import logging
4
+ from typing import Optional, Sequence
5
+
6
+ from pip._internal.build_env import BuildEnvironment
7
+ from pip._internal.utils.logging import indent_log
8
+ from pip._internal.utils.setuptools_build import make_setuptools_develop_args
9
+ from pip._internal.utils.subprocess import call_subprocess
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def install_editable(
15
+ *,
16
+ global_options: Sequence[str],
17
+ prefix: Optional[str],
18
+ home: Optional[str],
19
+ use_user_site: bool,
20
+ name: str,
21
+ setup_py_path: str,
22
+ isolated: bool,
23
+ build_env: BuildEnvironment,
24
+ unpacked_source_directory: str,
25
+ ) -> None:
26
+ """Install a package in editable mode. Most arguments are pass-through
27
+ to setuptools.
28
+ """
29
+ logger.info("Running setup.py develop for %s", name)
30
+
31
+ args = make_setuptools_develop_args(
32
+ setup_py_path,
33
+ global_options=global_options,
34
+ no_user_config=isolated,
35
+ prefix=prefix,
36
+ home=home,
37
+ use_user_site=use_user_site,
38
+ )
39
+
40
+ with indent_log():
41
+ with build_env:
42
+ call_subprocess(
43
+ args,
44
+ command_desc="python setup.py develop",
45
+ cwd=unpacked_source_directory,
46
+ )
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/install/wheel.py ADDED
@@ -0,0 +1,738 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Support for installing and building the "wheel" binary package format."""
2
+
3
+ import collections
4
+ import compileall
5
+ import contextlib
6
+ import csv
7
+ import importlib
8
+ import logging
9
+ import os.path
10
+ import re
11
+ import shutil
12
+ import sys
13
+ import warnings
14
+ from base64 import urlsafe_b64encode
15
+ from email.message import Message
16
+ from itertools import chain, filterfalse, starmap
17
+ from typing import (
18
+ IO,
19
+ Any,
20
+ BinaryIO,
21
+ Callable,
22
+ Dict,
23
+ Generator,
24
+ Iterable,
25
+ Iterator,
26
+ List,
27
+ NewType,
28
+ Optional,
29
+ Protocol,
30
+ Sequence,
31
+ Set,
32
+ Tuple,
33
+ Union,
34
+ cast,
35
+ )
36
+ from zipfile import ZipFile, ZipInfo
37
+
38
+ from pip._vendor.distlib.scripts import ScriptMaker
39
+ from pip._vendor.distlib.util import get_export_entry
40
+ from pip._vendor.packaging.utils import canonicalize_name
41
+
42
+ from pip._internal.exceptions import InstallationError
43
+ from pip._internal.locations import get_major_minor_version
44
+ from pip._internal.metadata import (
45
+ BaseDistribution,
46
+ FilesystemWheel,
47
+ get_wheel_distribution,
48
+ )
49
+ from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, DirectUrl
50
+ from pip._internal.models.scheme import SCHEME_KEYS, Scheme
51
+ from pip._internal.utils.filesystem import adjacent_tmp_file, replace
52
+ from pip._internal.utils.misc import StreamWrapper, ensure_dir, hash_file, partition
53
+ from pip._internal.utils.unpacking import (
54
+ current_umask,
55
+ is_within_directory,
56
+ set_extracted_file_to_default_mode_plus_executable,
57
+ zip_item_is_executable,
58
+ )
59
+ from pip._internal.utils.wheel import parse_wheel
60
+
61
+
62
+ class File(Protocol):
63
+ src_record_path: "RecordPath"
64
+ dest_path: str
65
+ changed: bool
66
+
67
+ def save(self) -> None:
68
+ pass
69
+
70
+
71
+ logger = logging.getLogger(__name__)
72
+
73
+ RecordPath = NewType("RecordPath", str)
74
+ InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]]
75
+
76
+
77
+ def rehash(path: str, blocksize: int = 1 << 20) -> Tuple[str, str]:
78
+ """Return (encoded_digest, length) for path using hashlib.sha256()"""
79
+ h, length = hash_file(path, blocksize)
80
+ digest = "sha256=" + urlsafe_b64encode(h.digest()).decode("latin1").rstrip("=")
81
+ return (digest, str(length))
82
+
83
+
84
+ def csv_io_kwargs(mode: str) -> Dict[str, Any]:
85
+ """Return keyword arguments to properly open a CSV file
86
+ in the given mode.
87
+ """
88
+ return {"mode": mode, "newline": "", "encoding": "utf-8"}
89
+
90
+
91
+ def fix_script(path: str) -> bool:
92
+ """Replace #!python with #!/path/to/python
93
+ Return True if file was changed.
94
+ """
95
+ # XXX RECORD hashes will need to be updated
96
+ assert os.path.isfile(path)
97
+
98
+ with open(path, "rb") as script:
99
+ firstline = script.readline()
100
+ if not firstline.startswith(b"#!python"):
101
+ return False
102
+ exename = sys.executable.encode(sys.getfilesystemencoding())
103
+ firstline = b"#!" + exename + os.linesep.encode("ascii")
104
+ rest = script.read()
105
+ with open(path, "wb") as script:
106
+ script.write(firstline)
107
+ script.write(rest)
108
+ return True
109
+
110
+
111
+ def wheel_root_is_purelib(metadata: Message) -> bool:
112
+ return metadata.get("Root-Is-Purelib", "").lower() == "true"
113
+
114
+
115
+ def get_entrypoints(dist: BaseDistribution) -> Tuple[Dict[str, str], Dict[str, str]]:
116
+ console_scripts = {}
117
+ gui_scripts = {}
118
+ for entry_point in dist.iter_entry_points():
119
+ if entry_point.group == "console_scripts":
120
+ console_scripts[entry_point.name] = entry_point.value
121
+ elif entry_point.group == "gui_scripts":
122
+ gui_scripts[entry_point.name] = entry_point.value
123
+ return console_scripts, gui_scripts
124
+
125
+
126
+ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
127
+ """Determine if any scripts are not on PATH and format a warning.
128
+ Returns a warning message if one or more scripts are not on PATH,
129
+ otherwise None.
130
+ """
131
+ if not scripts:
132
+ return None
133
+
134
+ # Group scripts by the path they were installed in
135
+ grouped_by_dir: Dict[str, Set[str]] = collections.defaultdict(set)
136
+ for destfile in scripts:
137
+ parent_dir = os.path.dirname(destfile)
138
+ script_name = os.path.basename(destfile)
139
+ grouped_by_dir[parent_dir].add(script_name)
140
+
141
+ # We don't want to warn for directories that are on PATH.
142
+ not_warn_dirs = [
143
+ os.path.normcase(os.path.normpath(i)).rstrip(os.sep)
144
+ for i in os.environ.get("PATH", "").split(os.pathsep)
145
+ ]
146
+ # If an executable sits with sys.executable, we don't warn for it.
147
+ # This covers the case of venv invocations without activating the venv.
148
+ not_warn_dirs.append(
149
+ os.path.normcase(os.path.normpath(os.path.dirname(sys.executable)))
150
+ )
151
+ warn_for: Dict[str, Set[str]] = {
152
+ parent_dir: scripts
153
+ for parent_dir, scripts in grouped_by_dir.items()
154
+ if os.path.normcase(os.path.normpath(parent_dir)) not in not_warn_dirs
155
+ }
156
+ if not warn_for:
157
+ return None
158
+
159
+ # Format a message
160
+ msg_lines = []
161
+ for parent_dir, dir_scripts in warn_for.items():
162
+ sorted_scripts: List[str] = sorted(dir_scripts)
163
+ if len(sorted_scripts) == 1:
164
+ start_text = f"script {sorted_scripts[0]} is"
165
+ else:
166
+ start_text = "scripts {} are".format(
167
+ ", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1]
168
+ )
169
+
170
+ msg_lines.append(
171
+ f"The {start_text} installed in '{parent_dir}' which is not on PATH."
172
+ )
173
+
174
+ last_line_fmt = (
175
+ "Consider adding {} to PATH or, if you prefer "
176
+ "to suppress this warning, use --no-warn-script-location."
177
+ )
178
+ if len(msg_lines) == 1:
179
+ msg_lines.append(last_line_fmt.format("this directory"))
180
+ else:
181
+ msg_lines.append(last_line_fmt.format("these directories"))
182
+
183
+ # Add a note if any directory starts with ~
184
+ warn_for_tilde = any(
185
+ i[0] == "~" for i in os.environ.get("PATH", "").split(os.pathsep) if i
186
+ )
187
+ if warn_for_tilde:
188
+ tilde_warning_msg = (
189
+ "NOTE: The current PATH contains path(s) starting with `~`, "
190
+ "which may not be expanded by all applications."
191
+ )
192
+ msg_lines.append(tilde_warning_msg)
193
+
194
+ # Returns the formatted multiline message
195
+ return "\n".join(msg_lines)
196
+
197
+
198
+ def _normalized_outrows(
199
+ outrows: Iterable[InstalledCSVRow],
200
+ ) -> List[Tuple[str, str, str]]:
201
+ """Normalize the given rows of a RECORD file.
202
+
203
+ Items in each row are converted into str. Rows are then sorted to make
204
+ the value more predictable for tests.
205
+
206
+ Each row is a 3-tuple (path, hash, size) and corresponds to a record of
207
+ a RECORD file (see PEP 376 and PEP 427 for details). For the rows
208
+ passed to this function, the size can be an integer as an int or string,
209
+ or the empty string.
210
+ """
211
+ # Normally, there should only be one row per path, in which case the
212
+ # second and third elements don't come into play when sorting.
213
+ # However, in cases in the wild where a path might happen to occur twice,
214
+ # we don't want the sort operation to trigger an error (but still want
215
+ # determinism). Since the third element can be an int or string, we
216
+ # coerce each element to a string to avoid a TypeError in this case.
217
+ # For additional background, see--
218
+ # https://github.com/pypa/pip/issues/5868
219
+ return sorted(
220
+ (record_path, hash_, str(size)) for record_path, hash_, size in outrows
221
+ )
222
+
223
+
224
+ def _record_to_fs_path(record_path: RecordPath, lib_dir: str) -> str:
225
+ return os.path.join(lib_dir, record_path)
226
+
227
+
228
+ def _fs_to_record_path(path: str, lib_dir: str) -> RecordPath:
229
+ # On Windows, do not handle relative paths if they belong to different
230
+ # logical disks
231
+ if os.path.splitdrive(path)[0].lower() == os.path.splitdrive(lib_dir)[0].lower():
232
+ path = os.path.relpath(path, lib_dir)
233
+
234
+ path = path.replace(os.path.sep, "/")
235
+ return cast("RecordPath", path)
236
+
237
+
238
+ def get_csv_rows_for_installed(
239
+ old_csv_rows: List[List[str]],
240
+ installed: Dict[RecordPath, RecordPath],
241
+ changed: Set[RecordPath],
242
+ generated: List[str],
243
+ lib_dir: str,
244
+ ) -> List[InstalledCSVRow]:
245
+ """
246
+ :param installed: A map from archive RECORD path to installation RECORD
247
+ path.
248
+ """
249
+ installed_rows: List[InstalledCSVRow] = []
250
+ for row in old_csv_rows:
251
+ if len(row) > 3:
252
+ logger.warning("RECORD line has more than three elements: %s", row)
253
+ old_record_path = cast("RecordPath", row[0])
254
+ new_record_path = installed.pop(old_record_path, old_record_path)
255
+ if new_record_path in changed:
256
+ digest, length = rehash(_record_to_fs_path(new_record_path, lib_dir))
257
+ else:
258
+ digest = row[1] if len(row) > 1 else ""
259
+ length = row[2] if len(row) > 2 else ""
260
+ installed_rows.append((new_record_path, digest, length))
261
+ for f in generated:
262
+ path = _fs_to_record_path(f, lib_dir)
263
+ digest, length = rehash(f)
264
+ installed_rows.append((path, digest, length))
265
+ return installed_rows + [
266
+ (installed_record_path, "", "") for installed_record_path in installed.values()
267
+ ]
268
+
269
+
270
+ def get_console_script_specs(console: Dict[str, str]) -> List[str]:
271
+ """
272
+ Given the mapping from entrypoint name to callable, return the relevant
273
+ console script specs.
274
+ """
275
+ # Don't mutate caller's version
276
+ console = console.copy()
277
+
278
+ scripts_to_generate = []
279
+
280
+ # Special case pip and setuptools to generate versioned wrappers
281
+ #
282
+ # The issue is that some projects (specifically, pip and setuptools) use
283
+ # code in setup.py to create "versioned" entry points - pip2.7 on Python
284
+ # 2.7, pip3.3 on Python 3.3, etc. But these entry points are baked into
285
+ # the wheel metadata at build time, and so if the wheel is installed with
286
+ # a *different* version of Python the entry points will be wrong. The
287
+ # correct fix for this is to enhance the metadata to be able to describe
288
+ # such versioned entry points.
289
+ # Currently, projects using versioned entry points will either have
290
+ # incorrect versioned entry points, or they will not be able to distribute
291
+ # "universal" wheels (i.e., they will need a wheel per Python version).
292
+ #
293
+ # Because setuptools and pip are bundled with _ensurepip and virtualenv,
294
+ # we need to use universal wheels. As a workaround, we
295
+ # override the versioned entry points in the wheel and generate the
296
+ # correct ones.
297
+ #
298
+ # To add the level of hack in this section of code, in order to support
299
+ # ensurepip this code will look for an ``ENSUREPIP_OPTIONS`` environment
300
+ # variable which will control which version scripts get installed.
301
+ #
302
+ # ENSUREPIP_OPTIONS=altinstall
303
+ # - Only pipX.Y and easy_install-X.Y will be generated and installed
304
+ # ENSUREPIP_OPTIONS=install
305
+ # - pipX.Y, pipX, easy_install-X.Y will be generated and installed. Note
306
+ # that this option is technically if ENSUREPIP_OPTIONS is set and is
307
+ # not altinstall
308
+ # DEFAULT
309
+ # - The default behavior is to install pip, pipX, pipX.Y, easy_install
310
+ # and easy_install-X.Y.
311
+ pip_script = console.pop("pip", None)
312
+ if pip_script:
313
+ if "ENSUREPIP_OPTIONS" not in os.environ:
314
+ scripts_to_generate.append("pip = " + pip_script)
315
+
316
+ if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall":
317
+ scripts_to_generate.append(f"pip{sys.version_info[0]} = {pip_script}")
318
+
319
+ scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}")
320
+ # Delete any other versioned pip entry points
321
+ pip_ep = [k for k in console if re.match(r"pip(\d+(\.\d+)?)?$", k)]
322
+ for k in pip_ep:
323
+ del console[k]
324
+ easy_install_script = console.pop("easy_install", None)
325
+ if easy_install_script:
326
+ if "ENSUREPIP_OPTIONS" not in os.environ:
327
+ scripts_to_generate.append("easy_install = " + easy_install_script)
328
+
329
+ scripts_to_generate.append(
330
+ f"easy_install-{get_major_minor_version()} = {easy_install_script}"
331
+ )
332
+ # Delete any other versioned easy_install entry points
333
+ easy_install_ep = [
334
+ k for k in console if re.match(r"easy_install(-\d+\.\d+)?$", k)
335
+ ]
336
+ for k in easy_install_ep:
337
+ del console[k]
338
+
339
+ # Generate the console entry points specified in the wheel
340
+ scripts_to_generate.extend(starmap("{} = {}".format, console.items()))
341
+
342
+ return scripts_to_generate
343
+
344
+
345
+ class ZipBackedFile:
346
+ def __init__(
347
+ self, src_record_path: RecordPath, dest_path: str, zip_file: ZipFile
348
+ ) -> None:
349
+ self.src_record_path = src_record_path
350
+ self.dest_path = dest_path
351
+ self._zip_file = zip_file
352
+ self.changed = False
353
+
354
+ def _getinfo(self) -> ZipInfo:
355
+ return self._zip_file.getinfo(self.src_record_path)
356
+
357
+ def save(self) -> None:
358
+ # When we open the output file below, any existing file is truncated
359
+ # before we start writing the new contents. This is fine in most
360
+ # cases, but can cause a segfault if pip has loaded a shared
361
+ # object (e.g. from pyopenssl through its vendored urllib3)
362
+ # Since the shared object is mmap'd an attempt to call a
363
+ # symbol in it will then cause a segfault. Unlinking the file
364
+ # allows writing of new contents while allowing the process to
365
+ # continue to use the old copy.
366
+ if os.path.exists(self.dest_path):
367
+ os.unlink(self.dest_path)
368
+
369
+ zipinfo = self._getinfo()
370
+
371
+ # optimization: the file is created by open(),
372
+ # skip the decompression when there is 0 bytes to decompress.
373
+ with open(self.dest_path, "wb") as dest:
374
+ if zipinfo.file_size > 0:
375
+ with self._zip_file.open(zipinfo) as f:
376
+ blocksize = min(zipinfo.file_size, 1024 * 1024)
377
+ shutil.copyfileobj(f, dest, blocksize)
378
+
379
+ if zip_item_is_executable(zipinfo):
380
+ set_extracted_file_to_default_mode_plus_executable(self.dest_path)
381
+
382
+
383
+ class ScriptFile:
384
+ def __init__(self, file: "File") -> None:
385
+ self._file = file
386
+ self.src_record_path = self._file.src_record_path
387
+ self.dest_path = self._file.dest_path
388
+ self.changed = False
389
+
390
+ def save(self) -> None:
391
+ self._file.save()
392
+ self.changed = fix_script(self.dest_path)
393
+
394
+
395
+ class MissingCallableSuffix(InstallationError):
396
+ def __init__(self, entry_point: str) -> None:
397
+ super().__init__(
398
+ f"Invalid script entry point: {entry_point} - A callable "
399
+ "suffix is required. Cf https://packaging.python.org/"
400
+ "specifications/entry-points/#use-for-scripts for more "
401
+ "information."
402
+ )
403
+
404
+
405
+ def _raise_for_invalid_entrypoint(specification: str) -> None:
406
+ entry = get_export_entry(specification)
407
+ if entry is not None and entry.suffix is None:
408
+ raise MissingCallableSuffix(str(entry))
409
+
410
+
411
+ class PipScriptMaker(ScriptMaker):
412
+ def make(
413
+ self, specification: str, options: Optional[Dict[str, Any]] = None
414
+ ) -> List[str]:
415
+ _raise_for_invalid_entrypoint(specification)
416
+ return super().make(specification, options)
417
+
418
+
419
+ def _install_wheel( # noqa: C901, PLR0915 function is too long
420
+ name: str,
421
+ wheel_zip: ZipFile,
422
+ wheel_path: str,
423
+ scheme: Scheme,
424
+ pycompile: bool = True,
425
+ warn_script_location: bool = True,
426
+ direct_url: Optional[DirectUrl] = None,
427
+ requested: bool = False,
428
+ ) -> None:
429
+ """Install a wheel.
430
+
431
+ :param name: Name of the project to install
432
+ :param wheel_zip: open ZipFile for wheel being installed
433
+ :param scheme: Distutils scheme dictating the install directories
434
+ :param req_description: String used in place of the requirement, for
435
+ logging
436
+ :param pycompile: Whether to byte-compile installed Python files
437
+ :param warn_script_location: Whether to check that scripts are installed
438
+ into a directory on PATH
439
+ :raises UnsupportedWheel:
440
+ * when the directory holds an unpacked wheel with incompatible
441
+ Wheel-Version
442
+ * when the .dist-info dir does not match the wheel
443
+ """
444
+ info_dir, metadata = parse_wheel(wheel_zip, name)
445
+
446
+ if wheel_root_is_purelib(metadata):
447
+ lib_dir = scheme.purelib
448
+ else:
449
+ lib_dir = scheme.platlib
450
+
451
+ # Record details of the files moved
452
+ # installed = files copied from the wheel to the destination
453
+ # changed = files changed while installing (scripts #! line typically)
454
+ # generated = files newly generated during the install (script wrappers)
455
+ installed: Dict[RecordPath, RecordPath] = {}
456
+ changed: Set[RecordPath] = set()
457
+ generated: List[str] = []
458
+
459
+ def record_installed(
460
+ srcfile: RecordPath, destfile: str, modified: bool = False
461
+ ) -> None:
462
+ """Map archive RECORD paths to installation RECORD paths."""
463
+ newpath = _fs_to_record_path(destfile, lib_dir)
464
+ installed[srcfile] = newpath
465
+ if modified:
466
+ changed.add(newpath)
467
+
468
+ def is_dir_path(path: RecordPath) -> bool:
469
+ return path.endswith("/")
470
+
471
+ def assert_no_path_traversal(dest_dir_path: str, target_path: str) -> None:
472
+ if not is_within_directory(dest_dir_path, target_path):
473
+ message = (
474
+ "The wheel {!r} has a file {!r} trying to install"
475
+ " outside the target directory {!r}"
476
+ )
477
+ raise InstallationError(
478
+ message.format(wheel_path, target_path, dest_dir_path)
479
+ )
480
+
481
+ def root_scheme_file_maker(
482
+ zip_file: ZipFile, dest: str
483
+ ) -> Callable[[RecordPath], "File"]:
484
+ def make_root_scheme_file(record_path: RecordPath) -> "File":
485
+ normed_path = os.path.normpath(record_path)
486
+ dest_path = os.path.join(dest, normed_path)
487
+ assert_no_path_traversal(dest, dest_path)
488
+ return ZipBackedFile(record_path, dest_path, zip_file)
489
+
490
+ return make_root_scheme_file
491
+
492
+ def data_scheme_file_maker(
493
+ zip_file: ZipFile, scheme: Scheme
494
+ ) -> Callable[[RecordPath], "File"]:
495
+ scheme_paths = {key: getattr(scheme, key) for key in SCHEME_KEYS}
496
+
497
+ def make_data_scheme_file(record_path: RecordPath) -> "File":
498
+ normed_path = os.path.normpath(record_path)
499
+ try:
500
+ _, scheme_key, dest_subpath = normed_path.split(os.path.sep, 2)
501
+ except ValueError:
502
+ message = (
503
+ f"Unexpected file in {wheel_path}: {record_path!r}. .data directory"
504
+ " contents should be named like: '<scheme key>/<path>'."
505
+ )
506
+ raise InstallationError(message)
507
+
508
+ try:
509
+ scheme_path = scheme_paths[scheme_key]
510
+ except KeyError:
511
+ valid_scheme_keys = ", ".join(sorted(scheme_paths))
512
+ message = (
513
+ f"Unknown scheme key used in {wheel_path}: {scheme_key} "
514
+ f"(for file {record_path!r}). .data directory contents "
515
+ f"should be in subdirectories named with a valid scheme "
516
+ f"key ({valid_scheme_keys})"
517
+ )
518
+ raise InstallationError(message)
519
+
520
+ dest_path = os.path.join(scheme_path, dest_subpath)
521
+ assert_no_path_traversal(scheme_path, dest_path)
522
+ return ZipBackedFile(record_path, dest_path, zip_file)
523
+
524
+ return make_data_scheme_file
525
+
526
+ def is_data_scheme_path(path: RecordPath) -> bool:
527
+ return path.split("/", 1)[0].endswith(".data")
528
+
529
+ paths = cast(List[RecordPath], wheel_zip.namelist())
530
+ file_paths = filterfalse(is_dir_path, paths)
531
+ root_scheme_paths, data_scheme_paths = partition(is_data_scheme_path, file_paths)
532
+
533
+ make_root_scheme_file = root_scheme_file_maker(wheel_zip, lib_dir)
534
+ files: Iterator[File] = map(make_root_scheme_file, root_scheme_paths)
535
+
536
+ def is_script_scheme_path(path: RecordPath) -> bool:
537
+ parts = path.split("/", 2)
538
+ return len(parts) > 2 and parts[0].endswith(".data") and parts[1] == "scripts"
539
+
540
+ other_scheme_paths, script_scheme_paths = partition(
541
+ is_script_scheme_path, data_scheme_paths
542
+ )
543
+
544
+ make_data_scheme_file = data_scheme_file_maker(wheel_zip, scheme)
545
+ other_scheme_files = map(make_data_scheme_file, other_scheme_paths)
546
+ files = chain(files, other_scheme_files)
547
+
548
+ # Get the defined entry points
549
+ distribution = get_wheel_distribution(
550
+ FilesystemWheel(wheel_path),
551
+ canonicalize_name(name),
552
+ )
553
+ console, gui = get_entrypoints(distribution)
554
+
555
+ def is_entrypoint_wrapper(file: "File") -> bool:
556
+ # EP, EP.exe and EP-script.py are scripts generated for
557
+ # entry point EP by setuptools
558
+ path = file.dest_path
559
+ name = os.path.basename(path)
560
+ if name.lower().endswith(".exe"):
561
+ matchname = name[:-4]
562
+ elif name.lower().endswith("-script.py"):
563
+ matchname = name[:-10]
564
+ elif name.lower().endswith(".pya"):
565
+ matchname = name[:-4]
566
+ else:
567
+ matchname = name
568
+ # Ignore setuptools-generated scripts
569
+ return matchname in console or matchname in gui
570
+
571
+ script_scheme_files: Iterator[File] = map(
572
+ make_data_scheme_file, script_scheme_paths
573
+ )
574
+ script_scheme_files = filterfalse(is_entrypoint_wrapper, script_scheme_files)
575
+ script_scheme_files = map(ScriptFile, script_scheme_files)
576
+ files = chain(files, script_scheme_files)
577
+
578
+ existing_parents = set()
579
+ for file in files:
580
+ # directory creation is lazy and after file filtering
581
+ # to ensure we don't install empty dirs; empty dirs can't be
582
+ # uninstalled.
583
+ parent_dir = os.path.dirname(file.dest_path)
584
+ if parent_dir not in existing_parents:
585
+ ensure_dir(parent_dir)
586
+ existing_parents.add(parent_dir)
587
+ file.save()
588
+ record_installed(file.src_record_path, file.dest_path, file.changed)
589
+
590
+ def pyc_source_file_paths() -> Generator[str, None, None]:
591
+ # We de-duplicate installation paths, since there can be overlap (e.g.
592
+ # file in .data maps to same location as file in wheel root).
593
+ # Sorting installation paths makes it easier to reproduce and debug
594
+ # issues related to permissions on existing files.
595
+ for installed_path in sorted(set(installed.values())):
596
+ full_installed_path = os.path.join(lib_dir, installed_path)
597
+ if not os.path.isfile(full_installed_path):
598
+ continue
599
+ if not full_installed_path.endswith(".py"):
600
+ continue
601
+ yield full_installed_path
602
+
603
+ def pyc_output_path(path: str) -> str:
604
+ """Return the path the pyc file would have been written to."""
605
+ return importlib.util.cache_from_source(path)
606
+
607
+ # Compile all of the pyc files for the installed files
608
+ if pycompile:
609
+ with contextlib.redirect_stdout(
610
+ StreamWrapper.from_stream(sys.stdout)
611
+ ) as stdout:
612
+ with warnings.catch_warnings():
613
+ warnings.filterwarnings("ignore")
614
+ for path in pyc_source_file_paths():
615
+ success = compileall.compile_file(path, force=True, quiet=True)
616
+ if success:
617
+ pyc_path = pyc_output_path(path)
618
+ assert os.path.exists(pyc_path)
619
+ pyc_record_path = cast(
620
+ "RecordPath", pyc_path.replace(os.path.sep, "/")
621
+ )
622
+ record_installed(pyc_record_path, pyc_path)
623
+ logger.debug(stdout.getvalue())
624
+
625
+ maker = PipScriptMaker(None, scheme.scripts)
626
+
627
+ # Ensure old scripts are overwritten.
628
+ # See https://github.com/pypa/pip/issues/1800
629
+ maker.clobber = True
630
+
631
+ # Ensure we don't generate any variants for scripts because this is almost
632
+ # never what somebody wants.
633
+ # See https://bitbucket.org/pypa/distlib/issue/35/
634
+ maker.variants = {""}
635
+
636
+ # This is required because otherwise distlib creates scripts that are not
637
+ # executable.
638
+ # See https://bitbucket.org/pypa/distlib/issue/32/
639
+ maker.set_mode = True
640
+
641
+ # Generate the console and GUI entry points specified in the wheel
642
+ scripts_to_generate = get_console_script_specs(console)
643
+
644
+ gui_scripts_to_generate = list(starmap("{} = {}".format, gui.items()))
645
+
646
+ generated_console_scripts = maker.make_multiple(scripts_to_generate)
647
+ generated.extend(generated_console_scripts)
648
+
649
+ generated.extend(maker.make_multiple(gui_scripts_to_generate, {"gui": True}))
650
+
651
+ if warn_script_location:
652
+ msg = message_about_scripts_not_on_PATH(generated_console_scripts)
653
+ if msg is not None:
654
+ logger.warning(msg)
655
+
656
+ generated_file_mode = 0o666 & ~current_umask()
657
+
658
+ @contextlib.contextmanager
659
+ def _generate_file(path: str, **kwargs: Any) -> Generator[BinaryIO, None, None]:
660
+ with adjacent_tmp_file(path, **kwargs) as f:
661
+ yield f
662
+ os.chmod(f.name, generated_file_mode)
663
+ replace(f.name, path)
664
+
665
+ dest_info_dir = os.path.join(lib_dir, info_dir)
666
+
667
+ # Record pip as the installer
668
+ installer_path = os.path.join(dest_info_dir, "INSTALLER")
669
+ with _generate_file(installer_path) as installer_file:
670
+ installer_file.write(b"pip\n")
671
+ generated.append(installer_path)
672
+
673
+ # Record the PEP 610 direct URL reference
674
+ if direct_url is not None:
675
+ direct_url_path = os.path.join(dest_info_dir, DIRECT_URL_METADATA_NAME)
676
+ with _generate_file(direct_url_path) as direct_url_file:
677
+ direct_url_file.write(direct_url.to_json().encode("utf-8"))
678
+ generated.append(direct_url_path)
679
+
680
+ # Record the REQUESTED file
681
+ if requested:
682
+ requested_path = os.path.join(dest_info_dir, "REQUESTED")
683
+ with open(requested_path, "wb"):
684
+ pass
685
+ generated.append(requested_path)
686
+
687
+ record_text = distribution.read_text("RECORD")
688
+ record_rows = list(csv.reader(record_text.splitlines()))
689
+
690
+ rows = get_csv_rows_for_installed(
691
+ record_rows,
692
+ installed=installed,
693
+ changed=changed,
694
+ generated=generated,
695
+ lib_dir=lib_dir,
696
+ )
697
+
698
+ # Record details of all files installed
699
+ record_path = os.path.join(dest_info_dir, "RECORD")
700
+
701
+ with _generate_file(record_path, **csv_io_kwargs("w")) as record_file:
702
+ # Explicitly cast to typing.IO[str] as a workaround for the mypy error:
703
+ # "writer" has incompatible type "BinaryIO"; expected "_Writer"
704
+ writer = csv.writer(cast("IO[str]", record_file))
705
+ writer.writerows(_normalized_outrows(rows))
706
+
707
+
708
+ @contextlib.contextmanager
709
+ def req_error_context(req_description: str) -> Generator[None, None, None]:
710
+ try:
711
+ yield
712
+ except InstallationError as e:
713
+ message = f"For req: {req_description}. {e.args[0]}"
714
+ raise InstallationError(message) from e
715
+
716
+
717
+ def install_wheel(
718
+ name: str,
719
+ wheel_path: str,
720
+ scheme: Scheme,
721
+ req_description: str,
722
+ pycompile: bool = True,
723
+ warn_script_location: bool = True,
724
+ direct_url: Optional[DirectUrl] = None,
725
+ requested: bool = False,
726
+ ) -> None:
727
+ with ZipFile(wheel_path, allowZip64=True) as z:
728
+ with req_error_context(req_description):
729
+ _install_wheel(
730
+ name=name,
731
+ wheel_zip=z,
732
+ wheel_path=wheel_path,
733
+ scheme=scheme,
734
+ pycompile=pycompile,
735
+ warn_script_location=warn_script_location,
736
+ direct_url=direct_url,
737
+ requested=requested,
738
+ )
.mcp-venv/lib/python3.13/site-packages/pip/_internal/operations/prepare.py ADDED
@@ -0,0 +1,737 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Prepares a distribution for installation"""
2
+
3
+ # The following comment should be removed at some point in the future.
4
+ # mypy: strict-optional=False
5
+
6
+ import mimetypes
7
+ import os
8
+ import shutil
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Dict, Iterable, List, Optional
12
+
13
+ from pip._vendor.packaging.utils import canonicalize_name
14
+
15
+ from pip._internal.distributions import make_distribution_for_install_requirement
16
+ from pip._internal.distributions.installed import InstalledDistribution
17
+ from pip._internal.exceptions import (
18
+ DirectoryUrlHashUnsupported,
19
+ HashMismatch,
20
+ HashUnpinned,
21
+ InstallationError,
22
+ MetadataInconsistent,
23
+ NetworkConnectionError,
24
+ VcsHashUnsupported,
25
+ )
26
+ from pip._internal.index.package_finder import PackageFinder
27
+ from pip._internal.metadata import BaseDistribution, get_metadata_distribution
28
+ from pip._internal.models.direct_url import ArchiveInfo
29
+ from pip._internal.models.link import Link
30
+ from pip._internal.models.wheel import Wheel
31
+ from pip._internal.network.download import BatchDownloader, Downloader
32
+ from pip._internal.network.lazy_wheel import (
33
+ HTTPRangeRequestUnsupported,
34
+ dist_from_wheel_url,
35
+ )
36
+ from pip._internal.network.session import PipSession
37
+ from pip._internal.operations.build.build_tracker import BuildTracker
38
+ from pip._internal.req.req_install import InstallRequirement
39
+ from pip._internal.utils._log import getLogger
40
+ from pip._internal.utils.direct_url_helpers import (
41
+ direct_url_for_editable,
42
+ direct_url_from_link,
43
+ )
44
+ from pip._internal.utils.hashes import Hashes, MissingHashes
45
+ from pip._internal.utils.logging import indent_log
46
+ from pip._internal.utils.misc import (
47
+ display_path,
48
+ hash_file,
49
+ hide_url,
50
+ redact_auth_from_requirement,
51
+ )
52
+ from pip._internal.utils.temp_dir import TempDirectory
53
+ from pip._internal.utils.unpacking import unpack_file
54
+ from pip._internal.vcs import vcs
55
+
56
+ logger = getLogger(__name__)
57
+
58
+
59
+ def _get_prepared_distribution(
60
+ req: InstallRequirement,
61
+ build_tracker: BuildTracker,
62
+ finder: PackageFinder,
63
+ build_isolation: bool,
64
+ check_build_deps: bool,
65
+ ) -> BaseDistribution:
66
+ """Prepare a distribution for installation."""
67
+ abstract_dist = make_distribution_for_install_requirement(req)
68
+ tracker_id = abstract_dist.build_tracker_id
69
+ if tracker_id is not None:
70
+ with build_tracker.track(req, tracker_id):
71
+ abstract_dist.prepare_distribution_metadata(
72
+ finder, build_isolation, check_build_deps
73
+ )
74
+ return abstract_dist.get_metadata_distribution()
75
+
76
+
77
+ def unpack_vcs_link(link: Link, location: str, verbosity: int) -> None:
78
+ vcs_backend = vcs.get_backend_for_scheme(link.scheme)
79
+ assert vcs_backend is not None
80
+ vcs_backend.unpack(location, url=hide_url(link.url), verbosity=verbosity)
81
+
82
+
83
+ @dataclass
84
+ class File:
85
+ path: str
86
+ content_type: Optional[str] = None
87
+
88
+ def __post_init__(self) -> None:
89
+ if self.content_type is None:
90
+ # Try to guess the file's MIME type. If the system MIME tables
91
+ # can't be loaded, give up.
92
+ try:
93
+ self.content_type = mimetypes.guess_type(self.path)[0]
94
+ except OSError:
95
+ pass
96
+
97
+
98
+ def get_http_url(
99
+ link: Link,
100
+ download: Downloader,
101
+ download_dir: Optional[str] = None,
102
+ hashes: Optional[Hashes] = None,
103
+ ) -> File:
104
+ temp_dir = TempDirectory(kind="unpack", globally_managed=True)
105
+ # If a download dir is specified, is the file already downloaded there?
106
+ already_downloaded_path = None
107
+ if download_dir:
108
+ already_downloaded_path = _check_download_dir(link, download_dir, hashes)
109
+
110
+ if already_downloaded_path:
111
+ from_path = already_downloaded_path
112
+ content_type = None
113
+ else:
114
+ # let's download to a tmp dir
115
+ from_path, content_type = download(link, temp_dir.path)
116
+ if hashes:
117
+ hashes.check_against_path(from_path)
118
+
119
+ return File(from_path, content_type)
120
+
121
+
122
+ def get_file_url(
123
+ link: Link, download_dir: Optional[str] = None, hashes: Optional[Hashes] = None
124
+ ) -> File:
125
+ """Get file and optionally check its hash."""
126
+ # If a download dir is specified, is the file already there and valid?
127
+ already_downloaded_path = None
128
+ if download_dir:
129
+ already_downloaded_path = _check_download_dir(link, download_dir, hashes)
130
+
131
+ if already_downloaded_path:
132
+ from_path = already_downloaded_path
133
+ else:
134
+ from_path = link.file_path
135
+
136
+ # If --require-hashes is off, `hashes` is either empty, the
137
+ # link's embedded hash, or MissingHashes; it is required to
138
+ # match. If --require-hashes is on, we are satisfied by any
139
+ # hash in `hashes` matching: a URL-based or an option-based
140
+ # one; no internet-sourced hash will be in `hashes`.
141
+ if hashes:
142
+ hashes.check_against_path(from_path)
143
+ return File(from_path, None)
144
+
145
+
146
+ def unpack_url(
147
+ link: Link,
148
+ location: str,
149
+ download: Downloader,
150
+ verbosity: int,
151
+ download_dir: Optional[str] = None,
152
+ hashes: Optional[Hashes] = None,
153
+ ) -> Optional[File]:
154
+ """Unpack link into location, downloading if required.
155
+
156
+ :param hashes: A Hashes object, one of whose embedded hashes must match,
157
+ or HashMismatch will be raised. If the Hashes is empty, no matches are
158
+ required, and unhashable types of requirements (like VCS ones, which
159
+ would ordinarily raise HashUnsupported) are allowed.
160
+ """
161
+ # non-editable vcs urls
162
+ if link.is_vcs:
163
+ unpack_vcs_link(link, location, verbosity=verbosity)
164
+ return None
165
+
166
+ assert not link.is_existing_dir()
167
+
168
+ # file urls
169
+ if link.is_file:
170
+ file = get_file_url(link, download_dir, hashes=hashes)
171
+
172
+ # http urls
173
+ else:
174
+ file = get_http_url(
175
+ link,
176
+ download,
177
+ download_dir,
178
+ hashes=hashes,
179
+ )
180
+
181
+ # unpack the archive to the build dir location. even when only downloading
182
+ # archives, they have to be unpacked to parse dependencies, except wheels
183
+ if not link.is_wheel:
184
+ unpack_file(file.path, location, file.content_type)
185
+
186
+ return file
187
+
188
+
189
+ def _check_download_dir(
190
+ link: Link,
191
+ download_dir: str,
192
+ hashes: Optional[Hashes],
193
+ warn_on_hash_mismatch: bool = True,
194
+ ) -> Optional[str]:
195
+ """Check download_dir for previously downloaded file with correct hash
196
+ If a correct file is found return its path else None
197
+ """
198
+ download_path = os.path.join(download_dir, link.filename)
199
+
200
+ if not os.path.exists(download_path):
201
+ return None
202
+
203
+ # If already downloaded, does its hash match?
204
+ logger.info("File was already downloaded %s", download_path)
205
+ if hashes:
206
+ try:
207
+ hashes.check_against_path(download_path)
208
+ except HashMismatch:
209
+ if warn_on_hash_mismatch:
210
+ logger.warning(
211
+ "Previously-downloaded file %s has bad hash. Re-downloading.",
212
+ download_path,
213
+ )
214
+ os.unlink(download_path)
215
+ return None
216
+ return download_path
217
+
218
+
219
+ class RequirementPreparer:
220
+ """Prepares a Requirement"""
221
+
222
+ def __init__(
223
+ self,
224
+ build_dir: str,
225
+ download_dir: Optional[str],
226
+ src_dir: str,
227
+ build_isolation: bool,
228
+ check_build_deps: bool,
229
+ build_tracker: BuildTracker,
230
+ session: PipSession,
231
+ progress_bar: str,
232
+ finder: PackageFinder,
233
+ require_hashes: bool,
234
+ use_user_site: bool,
235
+ lazy_wheel: bool,
236
+ verbosity: int,
237
+ legacy_resolver: bool,
238
+ resume_retries: int,
239
+ ) -> None:
240
+ super().__init__()
241
+
242
+ self.src_dir = src_dir
243
+ self.build_dir = build_dir
244
+ self.build_tracker = build_tracker
245
+ self._session = session
246
+ self._download = Downloader(session, progress_bar, resume_retries)
247
+ self._batch_download = BatchDownloader(session, progress_bar, resume_retries)
248
+ self.finder = finder
249
+
250
+ # Where still-packed archives should be written to. If None, they are
251
+ # not saved, and are deleted immediately after unpacking.
252
+ self.download_dir = download_dir
253
+
254
+ # Is build isolation allowed?
255
+ self.build_isolation = build_isolation
256
+
257
+ # Should check build dependencies?
258
+ self.check_build_deps = check_build_deps
259
+
260
+ # Should hash-checking be required?
261
+ self.require_hashes = require_hashes
262
+
263
+ # Should install in user site-packages?
264
+ self.use_user_site = use_user_site
265
+
266
+ # Should wheels be downloaded lazily?
267
+ self.use_lazy_wheel = lazy_wheel
268
+
269
+ # How verbose should underlying tooling be?
270
+ self.verbosity = verbosity
271
+
272
+ # Are we using the legacy resolver?
273
+ self.legacy_resolver = legacy_resolver
274
+
275
+ # Memoized downloaded files, as mapping of url: path.
276
+ self._downloaded: Dict[str, str] = {}
277
+
278
+ # Previous "header" printed for a link-based InstallRequirement
279
+ self._previous_requirement_header = ("", "")
280
+
281
+ def _log_preparing_link(self, req: InstallRequirement) -> None:
282
+ """Provide context for the requirement being prepared."""
283
+ if req.link.is_file and not req.is_wheel_from_cache:
284
+ message = "Processing %s"
285
+ information = str(display_path(req.link.file_path))
286
+ else:
287
+ message = "Collecting %s"
288
+ information = redact_auth_from_requirement(req.req) if req.req else str(req)
289
+
290
+ # If we used req.req, inject requirement source if available (this
291
+ # would already be included if we used req directly)
292
+ if req.req and req.comes_from:
293
+ if isinstance(req.comes_from, str):
294
+ comes_from: Optional[str] = req.comes_from
295
+ else:
296
+ comes_from = req.comes_from.from_path()
297
+ if comes_from:
298
+ information += f" (from {comes_from})"
299
+
300
+ if (message, information) != self._previous_requirement_header:
301
+ self._previous_requirement_header = (message, information)
302
+ logger.info(message, information)
303
+
304
+ if req.is_wheel_from_cache:
305
+ with indent_log():
306
+ logger.info("Using cached %s", req.link.filename)
307
+
308
+ def _ensure_link_req_src_dir(
309
+ self, req: InstallRequirement, parallel_builds: bool
310
+ ) -> None:
311
+ """Ensure source_dir of a linked InstallRequirement."""
312
+ # Since source_dir is only set for editable requirements.
313
+ if req.link.is_wheel:
314
+ # We don't need to unpack wheels, so no need for a source
315
+ # directory.
316
+ return
317
+ assert req.source_dir is None
318
+ if req.link.is_existing_dir():
319
+ # build local directories in-tree
320
+ req.source_dir = req.link.file_path
321
+ return
322
+
323
+ # We always delete unpacked sdists after pip runs.
324
+ req.ensure_has_source_dir(
325
+ self.build_dir,
326
+ autodelete=True,
327
+ parallel_builds=parallel_builds,
328
+ )
329
+ req.ensure_pristine_source_checkout()
330
+
331
+ def _get_linked_req_hashes(self, req: InstallRequirement) -> Hashes:
332
+ # By the time this is called, the requirement's link should have
333
+ # been checked so we can tell what kind of requirements req is
334
+ # and raise some more informative errors than otherwise.
335
+ # (For example, we can raise VcsHashUnsupported for a VCS URL
336
+ # rather than HashMissing.)
337
+ if not self.require_hashes:
338
+ return req.hashes(trust_internet=True)
339
+
340
+ # We could check these first 2 conditions inside unpack_url
341
+ # and save repetition of conditions, but then we would
342
+ # report less-useful error messages for unhashable
343
+ # requirements, complaining that there's no hash provided.
344
+ if req.link.is_vcs:
345
+ raise VcsHashUnsupported()
346
+ if req.link.is_existing_dir():
347
+ raise DirectoryUrlHashUnsupported()
348
+
349
+ # Unpinned packages are asking for trouble when a new version
350
+ # is uploaded. This isn't a security check, but it saves users
351
+ # a surprising hash mismatch in the future.
352
+ # file:/// URLs aren't pinnable, so don't complain about them
353
+ # not being pinned.
354
+ if not req.is_direct and not req.is_pinned:
355
+ raise HashUnpinned()
356
+
357
+ # If known-good hashes are missing for this requirement,
358
+ # shim it with a facade object that will provoke hash
359
+ # computation and then raise a HashMissing exception
360
+ # showing the user what the hash should be.
361
+ return req.hashes(trust_internet=False) or MissingHashes()
362
+
363
+ def _fetch_metadata_only(
364
+ self,
365
+ req: InstallRequirement,
366
+ ) -> Optional[BaseDistribution]:
367
+ if self.legacy_resolver:
368
+ logger.debug(
369
+ "Metadata-only fetching is not used in the legacy resolver",
370
+ )
371
+ return None
372
+ if self.require_hashes:
373
+ logger.debug(
374
+ "Metadata-only fetching is not used as hash checking is required",
375
+ )
376
+ return None
377
+ # Try PEP 658 metadata first, then fall back to lazy wheel if unavailable.
378
+ return self._fetch_metadata_using_link_data_attr(
379
+ req
380
+ ) or self._fetch_metadata_using_lazy_wheel(req.link)
381
+
382
+ def _fetch_metadata_using_link_data_attr(
383
+ self,
384
+ req: InstallRequirement,
385
+ ) -> Optional[BaseDistribution]:
386
+ """Fetch metadata from the data-dist-info-metadata attribute, if possible."""
387
+ # (1) Get the link to the metadata file, if provided by the backend.
388
+ metadata_link = req.link.metadata_link()
389
+ if metadata_link is None:
390
+ return None
391
+ assert req.req is not None
392
+ logger.verbose(
393
+ "Obtaining dependency information for %s from %s",
394
+ req.req,
395
+ metadata_link,
396
+ )
397
+ # (2) Download the contents of the METADATA file, separate from the dist itself.
398
+ metadata_file = get_http_url(
399
+ metadata_link,
400
+ self._download,
401
+ hashes=metadata_link.as_hashes(),
402
+ )
403
+ with open(metadata_file.path, "rb") as f:
404
+ metadata_contents = f.read()
405
+ # (3) Generate a dist just from those file contents.
406
+ metadata_dist = get_metadata_distribution(
407
+ metadata_contents,
408
+ req.link.filename,
409
+ req.req.name,
410
+ )
411
+ # (4) Ensure the Name: field from the METADATA file matches the name from the
412
+ # install requirement.
413
+ #
414
+ # NB: raw_name will fall back to the name from the install requirement if
415
+ # the Name: field is not present, but it's noted in the raw_name docstring
416
+ # that that should NEVER happen anyway.
417
+ if canonicalize_name(metadata_dist.raw_name) != canonicalize_name(req.req.name):
418
+ raise MetadataInconsistent(
419
+ req, "Name", req.req.name, metadata_dist.raw_name
420
+ )
421
+ return metadata_dist
422
+
423
+ def _fetch_metadata_using_lazy_wheel(
424
+ self,
425
+ link: Link,
426
+ ) -> Optional[BaseDistribution]:
427
+ """Fetch metadata using lazy wheel, if possible."""
428
+ # --use-feature=fast-deps must be provided.
429
+ if not self.use_lazy_wheel:
430
+ return None
431
+ if link.is_file or not link.is_wheel:
432
+ logger.debug(
433
+ "Lazy wheel is not used as %r does not point to a remote wheel",
434
+ link,
435
+ )
436
+ return None
437
+
438
+ wheel = Wheel(link.filename)
439
+ name = canonicalize_name(wheel.name)
440
+ logger.info(
441
+ "Obtaining dependency information from %s %s",
442
+ name,
443
+ wheel.version,
444
+ )
445
+ url = link.url.split("#", 1)[0]
446
+ try:
447
+ return dist_from_wheel_url(name, url, self._session)
448
+ except HTTPRangeRequestUnsupported:
449
+ logger.debug("%s does not support range requests", url)
450
+ return None
451
+
452
+ def _complete_partial_requirements(
453
+ self,
454
+ partially_downloaded_reqs: Iterable[InstallRequirement],
455
+ parallel_builds: bool = False,
456
+ ) -> None:
457
+ """Download any requirements which were only fetched by metadata."""
458
+ # Download to a temporary directory. These will be copied over as
459
+ # needed for downstream 'download', 'wheel', and 'install' commands.
460
+ temp_dir = TempDirectory(kind="unpack", globally_managed=True).path
461
+
462
+ # Map each link to the requirement that owns it. This allows us to set
463
+ # `req.local_file_path` on the appropriate requirement after passing
464
+ # all the links at once into BatchDownloader.
465
+ links_to_fully_download: Dict[Link, InstallRequirement] = {}
466
+ for req in partially_downloaded_reqs:
467
+ assert req.link
468
+ links_to_fully_download[req.link] = req
469
+
470
+ batch_download = self._batch_download(
471
+ links_to_fully_download.keys(),
472
+ temp_dir,
473
+ )
474
+ for link, (filepath, _) in batch_download:
475
+ logger.debug("Downloading link %s to %s", link, filepath)
476
+ req = links_to_fully_download[link]
477
+ # Record the downloaded file path so wheel reqs can extract a Distribution
478
+ # in .get_dist().
479
+ req.local_file_path = filepath
480
+ # Record that the file is downloaded so we don't do it again in
481
+ # _prepare_linked_requirement().
482
+ self._downloaded[req.link.url] = filepath
483
+
484
+ # If this is an sdist, we need to unpack it after downloading, but the
485
+ # .source_dir won't be set up until we are in _prepare_linked_requirement().
486
+ # Add the downloaded archive to the install requirement to unpack after
487
+ # preparing the source dir.
488
+ if not req.is_wheel:
489
+ req.needs_unpacked_archive(Path(filepath))
490
+
491
+ # This step is necessary to ensure all lazy wheels are processed
492
+ # successfully by the 'download', 'wheel', and 'install' commands.
493
+ for req in partially_downloaded_reqs:
494
+ self._prepare_linked_requirement(req, parallel_builds)
495
+
496
+ def prepare_linked_requirement(
497
+ self, req: InstallRequirement, parallel_builds: bool = False
498
+ ) -> BaseDistribution:
499
+ """Prepare a requirement to be obtained from req.link."""
500
+ assert req.link
501
+ self._log_preparing_link(req)
502
+ with indent_log():
503
+ # Check if the relevant file is already available
504
+ # in the download directory
505
+ file_path = None
506
+ if self.download_dir is not None and req.link.is_wheel:
507
+ hashes = self._get_linked_req_hashes(req)
508
+ file_path = _check_download_dir(
509
+ req.link,
510
+ self.download_dir,
511
+ hashes,
512
+ # When a locally built wheel has been found in cache, we don't warn
513
+ # about re-downloading when the already downloaded wheel hash does
514
+ # not match. This is because the hash must be checked against the
515
+ # original link, not the cached link. It that case the already
516
+ # downloaded file will be removed and re-fetched from cache (which
517
+ # implies a hash check against the cache entry's origin.json).
518
+ warn_on_hash_mismatch=not req.is_wheel_from_cache,
519
+ )
520
+
521
+ if file_path is not None:
522
+ # The file is already available, so mark it as downloaded
523
+ self._downloaded[req.link.url] = file_path
524
+ else:
525
+ # The file is not available, attempt to fetch only metadata
526
+ metadata_dist = self._fetch_metadata_only(req)
527
+ if metadata_dist is not None:
528
+ req.needs_more_preparation = True
529
+ return metadata_dist
530
+
531
+ # None of the optimizations worked, fully prepare the requirement
532
+ return self._prepare_linked_requirement(req, parallel_builds)
533
+
534
+ def prepare_linked_requirements_more(
535
+ self, reqs: Iterable[InstallRequirement], parallel_builds: bool = False
536
+ ) -> None:
537
+ """Prepare linked requirements more, if needed."""
538
+ reqs = [req for req in reqs if req.needs_more_preparation]
539
+ for req in reqs:
540
+ # Determine if any of these requirements were already downloaded.
541
+ if self.download_dir is not None and req.link.is_wheel:
542
+ hashes = self._get_linked_req_hashes(req)
543
+ file_path = _check_download_dir(req.link, self.download_dir, hashes)
544
+ if file_path is not None:
545
+ self._downloaded[req.link.url] = file_path
546
+ req.needs_more_preparation = False
547
+
548
+ # Prepare requirements we found were already downloaded for some
549
+ # reason. The other downloads will be completed separately.
550
+ partially_downloaded_reqs: List[InstallRequirement] = []
551
+ for req in reqs:
552
+ if req.needs_more_preparation:
553
+ partially_downloaded_reqs.append(req)
554
+ else:
555
+ self._prepare_linked_requirement(req, parallel_builds)
556
+
557
+ # TODO: separate this part out from RequirementPreparer when the v1
558
+ # resolver can be removed!
559
+ self._complete_partial_requirements(
560
+ partially_downloaded_reqs,
561
+ parallel_builds=parallel_builds,
562
+ )
563
+
564
+ def _prepare_linked_requirement(
565
+ self, req: InstallRequirement, parallel_builds: bool
566
+ ) -> BaseDistribution:
567
+ assert req.link
568
+ link = req.link
569
+
570
+ hashes = self._get_linked_req_hashes(req)
571
+
572
+ if hashes and req.is_wheel_from_cache:
573
+ assert req.download_info is not None
574
+ assert link.is_wheel
575
+ assert link.is_file
576
+ # We need to verify hashes, and we have found the requirement in the cache
577
+ # of locally built wheels.
578
+ if (
579
+ isinstance(req.download_info.info, ArchiveInfo)
580
+ and req.download_info.info.hashes
581
+ and hashes.has_one_of(req.download_info.info.hashes)
582
+ ):
583
+ # At this point we know the requirement was built from a hashable source
584
+ # artifact, and we verified that the cache entry's hash of the original
585
+ # artifact matches one of the hashes we expect. We don't verify hashes
586
+ # against the cached wheel, because the wheel is not the original.
587
+ hashes = None
588
+ else:
589
+ logger.warning(
590
+ "The hashes of the source archive found in cache entry "
591
+ "don't match, ignoring cached built wheel "
592
+ "and re-downloading source."
593
+ )
594
+ req.link = req.cached_wheel_source_link
595
+ link = req.link
596
+
597
+ self._ensure_link_req_src_dir(req, parallel_builds)
598
+
599
+ if link.is_existing_dir():
600
+ local_file = None
601
+ elif link.url not in self._downloaded:
602
+ try:
603
+ local_file = unpack_url(
604
+ link,
605
+ req.source_dir,
606
+ self._download,
607
+ self.verbosity,
608
+ self.download_dir,
609
+ hashes,
610
+ )
611
+ except NetworkConnectionError as exc:
612
+ raise InstallationError(
613
+ f"Could not install requirement {req} because of HTTP "
614
+ f"error {exc} for URL {link}"
615
+ )
616
+ else:
617
+ file_path = self._downloaded[link.url]
618
+ if hashes:
619
+ hashes.check_against_path(file_path)
620
+ local_file = File(file_path, content_type=None)
621
+
622
+ # If download_info is set, we got it from the wheel cache.
623
+ if req.download_info is None:
624
+ # Editables don't go through this function (see
625
+ # prepare_editable_requirement).
626
+ assert not req.editable
627
+ req.download_info = direct_url_from_link(link, req.source_dir)
628
+ # Make sure we have a hash in download_info. If we got it as part of the
629
+ # URL, it will have been verified and we can rely on it. Otherwise we
630
+ # compute it from the downloaded file.
631
+ # FIXME: https://github.com/pypa/pip/issues/11943
632
+ if (
633
+ isinstance(req.download_info.info, ArchiveInfo)
634
+ and not req.download_info.info.hashes
635
+ and local_file
636
+ ):
637
+ hash = hash_file(local_file.path)[0].hexdigest()
638
+ # We populate info.hash for backward compatibility.
639
+ # This will automatically populate info.hashes.
640
+ req.download_info.info.hash = f"sha256={hash}"
641
+
642
+ # For use in later processing,
643
+ # preserve the file path on the requirement.
644
+ if local_file:
645
+ req.local_file_path = local_file.path
646
+
647
+ dist = _get_prepared_distribution(
648
+ req,
649
+ self.build_tracker,
650
+ self.finder,
651
+ self.build_isolation,
652
+ self.check_build_deps,
653
+ )
654
+ return dist
655
+
656
+ def save_linked_requirement(self, req: InstallRequirement) -> None:
657
+ assert self.download_dir is not None
658
+ assert req.link is not None
659
+ link = req.link
660
+ if link.is_vcs or (link.is_existing_dir() and req.editable):
661
+ # Make a .zip of the source_dir we already created.
662
+ req.archive(self.download_dir)
663
+ return
664
+
665
+ if link.is_existing_dir():
666
+ logger.debug(
667
+ "Not copying link to destination directory "
668
+ "since it is a directory: %s",
669
+ link,
670
+ )
671
+ return
672
+ if req.local_file_path is None:
673
+ # No distribution was downloaded for this requirement.
674
+ return
675
+
676
+ download_location = os.path.join(self.download_dir, link.filename)
677
+ if not os.path.exists(download_location):
678
+ shutil.copy(req.local_file_path, download_location)
679
+ download_path = display_path(download_location)
680
+ logger.info("Saved %s", download_path)
681
+
682
+ def prepare_editable_requirement(
683
+ self,
684
+ req: InstallRequirement,
685
+ ) -> BaseDistribution:
686
+ """Prepare an editable requirement."""
687
+ assert req.editable, "cannot prepare a non-editable req as editable"
688
+
689
+ logger.info("Obtaining %s", req)
690
+
691
+ with indent_log():
692
+ if self.require_hashes:
693
+ raise InstallationError(
694
+ f"The editable requirement {req} cannot be installed when "
695
+ "requiring hashes, because there is no single file to "
696
+ "hash."
697
+ )
698
+ req.ensure_has_source_dir(self.src_dir)
699
+ req.update_editable()
700
+ assert req.source_dir
701
+ req.download_info = direct_url_for_editable(req.unpacked_source_directory)
702
+
703
+ dist = _get_prepared_distribution(
704
+ req,
705
+ self.build_tracker,
706
+ self.finder,
707
+ self.build_isolation,
708
+ self.check_build_deps,
709
+ )
710
+
711
+ req.check_if_exists(self.use_user_site)
712
+
713
+ return dist
714
+
715
+ def prepare_installed_requirement(
716
+ self,
717
+ req: InstallRequirement,
718
+ skip_reason: str,
719
+ ) -> BaseDistribution:
720
+ """Prepare an already-installed requirement."""
721
+ assert req.satisfied_by, "req should have been satisfied but isn't"
722
+ assert skip_reason is not None, (
723
+ "did not get skip reason skipped but req.satisfied_by "
724
+ f"is set to {req.satisfied_by}"
725
+ )
726
+ logger.info(
727
+ "Requirement %s: %s (%s)", skip_reason, req, req.satisfied_by.version
728
+ )
729
+ with indent_log():
730
+ if self.require_hashes:
731
+ logger.debug(
732
+ "Since it is already installed, we are trusting this "
733
+ "package without checking its hash. To ensure a "
734
+ "completely repeatable environment, install into an "
735
+ "empty virtualenv."
736
+ )
737
+ return InstalledDistribution(req).get_metadata_distribution()
.mcp-venv/lib/python3.13/site-packages/pip/_internal/pyproject.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib.util
2
+ import os
3
+ import sys
4
+ from collections import namedtuple
5
+ from typing import Any, List, Optional
6
+
7
+ if sys.version_info >= (3, 11):
8
+ import tomllib
9
+ else:
10
+ from pip._vendor import tomli as tomllib
11
+
12
+ from pip._vendor.packaging.requirements import InvalidRequirement
13
+
14
+ from pip._internal.exceptions import (
15
+ InstallationError,
16
+ InvalidPyProjectBuildRequires,
17
+ MissingPyProjectBuildRequires,
18
+ )
19
+ from pip._internal.utils.packaging import get_requirement
20
+
21
+
22
+ def _is_list_of_str(obj: Any) -> bool:
23
+ return isinstance(obj, list) and all(isinstance(item, str) for item in obj)
24
+
25
+
26
+ def make_pyproject_path(unpacked_source_directory: str) -> str:
27
+ return os.path.join(unpacked_source_directory, "pyproject.toml")
28
+
29
+
30
+ BuildSystemDetails = namedtuple(
31
+ "BuildSystemDetails", ["requires", "backend", "check", "backend_path"]
32
+ )
33
+
34
+
35
+ def load_pyproject_toml(
36
+ use_pep517: Optional[bool], pyproject_toml: str, setup_py: str, req_name: str
37
+ ) -> Optional[BuildSystemDetails]:
38
+ """Load the pyproject.toml file.
39
+
40
+ Parameters:
41
+ use_pep517 - Has the user requested PEP 517 processing? None
42
+ means the user hasn't explicitly specified.
43
+ pyproject_toml - Location of the project's pyproject.toml file
44
+ setup_py - Location of the project's setup.py file
45
+ req_name - The name of the requirement we're processing (for
46
+ error reporting)
47
+
48
+ Returns:
49
+ None if we should use the legacy code path, otherwise a tuple
50
+ (
51
+ requirements from pyproject.toml,
52
+ name of PEP 517 backend,
53
+ requirements we should check are installed after setting
54
+ up the build environment
55
+ directory paths to import the backend from (backend-path),
56
+ relative to the project root.
57
+ )
58
+ """
59
+ has_pyproject = os.path.isfile(pyproject_toml)
60
+ has_setup = os.path.isfile(setup_py)
61
+
62
+ if not has_pyproject and not has_setup:
63
+ raise InstallationError(
64
+ f"{req_name} does not appear to be a Python project: "
65
+ f"neither 'setup.py' nor 'pyproject.toml' found."
66
+ )
67
+
68
+ if has_pyproject:
69
+ with open(pyproject_toml, encoding="utf-8") as f:
70
+ pp_toml = tomllib.loads(f.read())
71
+ build_system = pp_toml.get("build-system")
72
+ else:
73
+ build_system = None
74
+
75
+ # The following cases must use PEP 517
76
+ # We check for use_pep517 being non-None and falsy because that means
77
+ # the user explicitly requested --no-use-pep517. The value 0 as
78
+ # opposed to False can occur when the value is provided via an
79
+ # environment variable or config file option (due to the quirk of
80
+ # strtobool() returning an integer in pip's configuration code).
81
+ if has_pyproject and not has_setup:
82
+ if use_pep517 is not None and not use_pep517:
83
+ raise InstallationError(
84
+ "Disabling PEP 517 processing is invalid: "
85
+ "project does not have a setup.py"
86
+ )
87
+ use_pep517 = True
88
+ elif build_system and "build-backend" in build_system:
89
+ if use_pep517 is not None and not use_pep517:
90
+ raise InstallationError(
91
+ "Disabling PEP 517 processing is invalid: "
92
+ "project specifies a build backend of {} "
93
+ "in pyproject.toml".format(build_system["build-backend"])
94
+ )
95
+ use_pep517 = True
96
+
97
+ # If we haven't worked out whether to use PEP 517 yet,
98
+ # and the user hasn't explicitly stated a preference,
99
+ # we do so if the project has a pyproject.toml file
100
+ # or if we cannot import setuptools or wheels.
101
+
102
+ # We fallback to PEP 517 when without setuptools or without the wheel package,
103
+ # so setuptools can be installed as a default build backend.
104
+ # For more info see:
105
+ # https://discuss.python.org/t/pip-without-setuptools-could-the-experience-be-improved/11810/9
106
+ # https://github.com/pypa/pip/issues/8559
107
+ elif use_pep517 is None:
108
+ use_pep517 = (
109
+ has_pyproject
110
+ or not importlib.util.find_spec("setuptools")
111
+ or not importlib.util.find_spec("wheel")
112
+ )
113
+
114
+ # At this point, we know whether we're going to use PEP 517.
115
+ assert use_pep517 is not None
116
+
117
+ # If we're using the legacy code path, there is nothing further
118
+ # for us to do here.
119
+ if not use_pep517:
120
+ return None
121
+
122
+ if build_system is None:
123
+ # Either the user has a pyproject.toml with no build-system
124
+ # section, or the user has no pyproject.toml, but has opted in
125
+ # explicitly via --use-pep517.
126
+ # In the absence of any explicit backend specification, we
127
+ # assume the setuptools backend that most closely emulates the
128
+ # traditional direct setup.py execution, and require wheel and
129
+ # a version of setuptools that supports that backend.
130
+
131
+ build_system = {
132
+ "requires": ["setuptools>=40.8.0"],
133
+ "build-backend": "setuptools.build_meta:__legacy__",
134
+ }
135
+
136
+ # If we're using PEP 517, we have build system information (either
137
+ # from pyproject.toml, or defaulted by the code above).
138
+ # Note that at this point, we do not know if the user has actually
139
+ # specified a backend, though.
140
+ assert build_system is not None
141
+
142
+ # Ensure that the build-system section in pyproject.toml conforms
143
+ # to PEP 518.
144
+
145
+ # Specifying the build-system table but not the requires key is invalid
146
+ if "requires" not in build_system:
147
+ raise MissingPyProjectBuildRequires(package=req_name)
148
+
149
+ # Error out if requires is not a list of strings
150
+ requires = build_system["requires"]
151
+ if not _is_list_of_str(requires):
152
+ raise InvalidPyProjectBuildRequires(
153
+ package=req_name,
154
+ reason="It is not a list of strings.",
155
+ )
156
+
157
+ # Each requirement must be valid as per PEP 508
158
+ for requirement in requires:
159
+ try:
160
+ get_requirement(requirement)
161
+ except InvalidRequirement as error:
162
+ raise InvalidPyProjectBuildRequires(
163
+ package=req_name,
164
+ reason=f"It contains an invalid requirement: {requirement!r}",
165
+ ) from error
166
+
167
+ backend = build_system.get("build-backend")
168
+ backend_path = build_system.get("backend-path", [])
169
+ check: List[str] = []
170
+ if backend is None:
171
+ # If the user didn't specify a backend, we assume they want to use
172
+ # the setuptools backend. But we can't be sure they have included
173
+ # a version of setuptools which supplies the backend. So we
174
+ # make a note to check that this requirement is present once
175
+ # we have set up the environment.
176
+ # This is quite a lot of work to check for a very specific case. But
177
+ # the problem is, that case is potentially quite common - projects that
178
+ # adopted PEP 518 early for the ability to specify requirements to
179
+ # execute setup.py, but never considered needing to mention the build
180
+ # tools themselves. The original PEP 518 code had a similar check (but
181
+ # implemented in a different way).
182
+ backend = "setuptools.build_meta:__legacy__"
183
+ check = ["setuptools>=40.8.0"]
184
+
185
+ return BuildSystemDetails(requires, backend, check, backend_path)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/req/__init__.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections
2
+ import logging
3
+ from dataclasses import dataclass
4
+ from typing import Generator, List, Optional, Sequence, Tuple
5
+
6
+ from pip._internal.cli.progress_bars import get_install_progress_renderer
7
+ from pip._internal.utils.logging import indent_log
8
+
9
+ from .req_file import parse_requirements
10
+ from .req_install import InstallRequirement
11
+ from .req_set import RequirementSet
12
+
13
+ __all__ = [
14
+ "RequirementSet",
15
+ "InstallRequirement",
16
+ "parse_requirements",
17
+ "install_given_reqs",
18
+ ]
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class InstallationResult:
25
+ name: str
26
+
27
+
28
+ def _validate_requirements(
29
+ requirements: List[InstallRequirement],
30
+ ) -> Generator[Tuple[str, InstallRequirement], None, None]:
31
+ for req in requirements:
32
+ assert req.name, f"invalid to-be-installed requirement: {req}"
33
+ yield req.name, req
34
+
35
+
36
+ def install_given_reqs(
37
+ requirements: List[InstallRequirement],
38
+ global_options: Sequence[str],
39
+ root: Optional[str],
40
+ home: Optional[str],
41
+ prefix: Optional[str],
42
+ warn_script_location: bool,
43
+ use_user_site: bool,
44
+ pycompile: bool,
45
+ progress_bar: str,
46
+ ) -> List[InstallationResult]:
47
+ """
48
+ Install everything in the given list.
49
+
50
+ (to be called after having downloaded and unpacked the packages)
51
+ """
52
+ to_install = collections.OrderedDict(_validate_requirements(requirements))
53
+
54
+ if to_install:
55
+ logger.info(
56
+ "Installing collected packages: %s",
57
+ ", ".join(to_install.keys()),
58
+ )
59
+
60
+ installed = []
61
+
62
+ show_progress = logger.isEnabledFor(logging.INFO) and len(to_install) > 1
63
+
64
+ items = iter(to_install.values())
65
+ if show_progress:
66
+ renderer = get_install_progress_renderer(
67
+ bar_type=progress_bar, total=len(to_install)
68
+ )
69
+ items = renderer(items)
70
+
71
+ with indent_log():
72
+ for requirement in items:
73
+ req_name = requirement.name
74
+ assert req_name is not None
75
+ if requirement.should_reinstall:
76
+ logger.info("Attempting uninstall: %s", req_name)
77
+ with indent_log():
78
+ uninstalled_pathset = requirement.uninstall(auto_confirm=True)
79
+ else:
80
+ uninstalled_pathset = None
81
+
82
+ try:
83
+ requirement.install(
84
+ global_options,
85
+ root=root,
86
+ home=home,
87
+ prefix=prefix,
88
+ warn_script_location=warn_script_location,
89
+ use_user_site=use_user_site,
90
+ pycompile=pycompile,
91
+ )
92
+ except Exception:
93
+ # if install did not succeed, rollback previous uninstall
94
+ if uninstalled_pathset and not requirement.install_succeeded:
95
+ uninstalled_pathset.rollback()
96
+ raise
97
+ else:
98
+ if uninstalled_pathset and requirement.install_succeeded:
99
+ uninstalled_pathset.commit()
100
+
101
+ installed.append(InstallationResult(req_name))
102
+
103
+ return installed
.mcp-venv/lib/python3.13/site-packages/pip/_internal/req/constructors.py ADDED
@@ -0,0 +1,560 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Backing implementation for InstallRequirement's various constructors
2
+
3
+ The idea here is that these formed a major chunk of InstallRequirement's size
4
+ so, moving them and support code dedicated to them outside of that class
5
+ helps creates for better understandability for the rest of the code.
6
+
7
+ These are meant to be used elsewhere within pip to create instances of
8
+ InstallRequirement.
9
+ """
10
+
11
+ import copy
12
+ import logging
13
+ import os
14
+ import re
15
+ from dataclasses import dataclass
16
+ from typing import Collection, Dict, List, Optional, Set, Tuple, Union
17
+
18
+ from pip._vendor.packaging.markers import Marker
19
+ from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
20
+ from pip._vendor.packaging.specifiers import Specifier
21
+
22
+ from pip._internal.exceptions import InstallationError
23
+ from pip._internal.models.index import PyPI, TestPyPI
24
+ from pip._internal.models.link import Link
25
+ from pip._internal.models.wheel import Wheel
26
+ from pip._internal.req.req_file import ParsedRequirement
27
+ from pip._internal.req.req_install import InstallRequirement
28
+ from pip._internal.utils.filetypes import is_archive_file
29
+ from pip._internal.utils.misc import is_installable_dir
30
+ from pip._internal.utils.packaging import get_requirement
31
+ from pip._internal.utils.urls import path_to_url
32
+ from pip._internal.vcs import is_url, vcs
33
+
34
+ __all__ = [
35
+ "install_req_from_editable",
36
+ "install_req_from_line",
37
+ "parse_editable",
38
+ ]
39
+
40
+ logger = logging.getLogger(__name__)
41
+ operators = Specifier._operators.keys()
42
+
43
+
44
+ def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
45
+ m = re.match(r"^(.+)(\[[^\]]+\])$", path)
46
+ extras = None
47
+ if m:
48
+ path_no_extras = m.group(1)
49
+ extras = m.group(2)
50
+ else:
51
+ path_no_extras = path
52
+
53
+ return path_no_extras, extras
54
+
55
+
56
+ def convert_extras(extras: Optional[str]) -> Set[str]:
57
+ if not extras:
58
+ return set()
59
+ return get_requirement("placeholder" + extras.lower()).extras
60
+
61
+
62
+ def _set_requirement_extras(req: Requirement, new_extras: Set[str]) -> Requirement:
63
+ """
64
+ Returns a new requirement based on the given one, with the supplied extras. If the
65
+ given requirement already has extras those are replaced (or dropped if no new extras
66
+ are given).
67
+ """
68
+ match: Optional[re.Match[str]] = re.fullmatch(
69
+ # see https://peps.python.org/pep-0508/#complete-grammar
70
+ r"([\w\t .-]+)(\[[^\]]*\])?(.*)",
71
+ str(req),
72
+ flags=re.ASCII,
73
+ )
74
+ # ireq.req is a valid requirement so the regex should always match
75
+ assert (
76
+ match is not None
77
+ ), f"regex match on requirement {req} failed, this should never happen"
78
+ pre: Optional[str] = match.group(1)
79
+ post: Optional[str] = match.group(3)
80
+ assert (
81
+ pre is not None and post is not None
82
+ ), f"regex group selection for requirement {req} failed, this should never happen"
83
+ extras: str = "[{}]".format(",".join(sorted(new_extras)) if new_extras else "")
84
+ return get_requirement(f"{pre}{extras}{post}")
85
+
86
+
87
+ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
88
+ """Parses an editable requirement into:
89
+ - a requirement name
90
+ - an URL
91
+ - extras
92
+ - editable options
93
+ Accepted requirements:
94
+ svn+http://blahblah@rev#egg=Foobar[baz]&subdirectory=version_subdir
95
+ .[some_extra]
96
+ """
97
+
98
+ url = editable_req
99
+
100
+ # If a file path is specified with extras, strip off the extras.
101
+ url_no_extras, extras = _strip_extras(url)
102
+
103
+ if os.path.isdir(url_no_extras):
104
+ # Treating it as code that has already been checked out
105
+ url_no_extras = path_to_url(url_no_extras)
106
+
107
+ if url_no_extras.lower().startswith("file:"):
108
+ package_name = Link(url_no_extras).egg_fragment
109
+ if extras:
110
+ return (
111
+ package_name,
112
+ url_no_extras,
113
+ get_requirement("placeholder" + extras.lower()).extras,
114
+ )
115
+ else:
116
+ return package_name, url_no_extras, set()
117
+
118
+ for version_control in vcs:
119
+ if url.lower().startswith(f"{version_control}:"):
120
+ url = f"{version_control}+{url}"
121
+ break
122
+
123
+ link = Link(url)
124
+
125
+ if not link.is_vcs:
126
+ backends = ", ".join(vcs.all_schemes)
127
+ raise InstallationError(
128
+ f"{editable_req} is not a valid editable requirement. "
129
+ f"It should either be a path to a local project or a VCS URL "
130
+ f"(beginning with {backends})."
131
+ )
132
+
133
+ package_name = link.egg_fragment
134
+ if not package_name:
135
+ raise InstallationError(
136
+ f"Could not detect requirement name for '{editable_req}', "
137
+ "please specify one with #egg=your_package_name"
138
+ )
139
+ return package_name, url, set()
140
+
141
+
142
+ def check_first_requirement_in_file(filename: str) -> None:
143
+ """Check if file is parsable as a requirements file.
144
+
145
+ This is heavily based on ``pkg_resources.parse_requirements``, but
146
+ simplified to just check the first meaningful line.
147
+
148
+ :raises InvalidRequirement: If the first meaningful line cannot be parsed
149
+ as an requirement.
150
+ """
151
+ with open(filename, encoding="utf-8", errors="ignore") as f:
152
+ # Create a steppable iterator, so we can handle \-continuations.
153
+ lines = (
154
+ line
155
+ for line in (line.strip() for line in f)
156
+ if line and not line.startswith("#") # Skip blank lines/comments.
157
+ )
158
+
159
+ for line in lines:
160
+ # Drop comments -- a hash without a space may be in a URL.
161
+ if " #" in line:
162
+ line = line[: line.find(" #")]
163
+ # If there is a line continuation, drop it, and append the next line.
164
+ if line.endswith("\\"):
165
+ line = line[:-2].strip() + next(lines, "")
166
+ get_requirement(line)
167
+ return
168
+
169
+
170
+ def deduce_helpful_msg(req: str) -> str:
171
+ """Returns helpful msg in case requirements file does not exist,
172
+ or cannot be parsed.
173
+
174
+ :params req: Requirements file path
175
+ """
176
+ if not os.path.exists(req):
177
+ return f" File '{req}' does not exist."
178
+ msg = " The path does exist. "
179
+ # Try to parse and check if it is a requirements file.
180
+ try:
181
+ check_first_requirement_in_file(req)
182
+ except InvalidRequirement:
183
+ logger.debug("Cannot parse '%s' as requirements file", req)
184
+ else:
185
+ msg += (
186
+ f"The argument you provided "
187
+ f"({req}) appears to be a"
188
+ f" requirements file. If that is the"
189
+ f" case, use the '-r' flag to install"
190
+ f" the packages specified within it."
191
+ )
192
+ return msg
193
+
194
+
195
+ @dataclass(frozen=True)
196
+ class RequirementParts:
197
+ requirement: Optional[Requirement]
198
+ link: Optional[Link]
199
+ markers: Optional[Marker]
200
+ extras: Set[str]
201
+
202
+
203
+ def parse_req_from_editable(editable_req: str) -> RequirementParts:
204
+ name, url, extras_override = parse_editable(editable_req)
205
+
206
+ if name is not None:
207
+ try:
208
+ req: Optional[Requirement] = get_requirement(name)
209
+ except InvalidRequirement as exc:
210
+ raise InstallationError(f"Invalid requirement: {name!r}: {exc}")
211
+ else:
212
+ req = None
213
+
214
+ link = Link(url)
215
+
216
+ return RequirementParts(req, link, None, extras_override)
217
+
218
+
219
+ # ---- The actual constructors follow ----
220
+
221
+
222
+ def install_req_from_editable(
223
+ editable_req: str,
224
+ comes_from: Optional[Union[InstallRequirement, str]] = None,
225
+ *,
226
+ use_pep517: Optional[bool] = None,
227
+ isolated: bool = False,
228
+ global_options: Optional[List[str]] = None,
229
+ hash_options: Optional[Dict[str, List[str]]] = None,
230
+ constraint: bool = False,
231
+ user_supplied: bool = False,
232
+ permit_editable_wheels: bool = False,
233
+ config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
234
+ ) -> InstallRequirement:
235
+ parts = parse_req_from_editable(editable_req)
236
+
237
+ return InstallRequirement(
238
+ parts.requirement,
239
+ comes_from=comes_from,
240
+ user_supplied=user_supplied,
241
+ editable=True,
242
+ permit_editable_wheels=permit_editable_wheels,
243
+ link=parts.link,
244
+ constraint=constraint,
245
+ use_pep517=use_pep517,
246
+ isolated=isolated,
247
+ global_options=global_options,
248
+ hash_options=hash_options,
249
+ config_settings=config_settings,
250
+ extras=parts.extras,
251
+ )
252
+
253
+
254
+ def _looks_like_path(name: str) -> bool:
255
+ """Checks whether the string "looks like" a path on the filesystem.
256
+
257
+ This does not check whether the target actually exists, only judge from the
258
+ appearance.
259
+
260
+ Returns true if any of the following conditions is true:
261
+ * a path separator is found (either os.path.sep or os.path.altsep);
262
+ * a dot is found (which represents the current directory).
263
+ """
264
+ if os.path.sep in name:
265
+ return True
266
+ if os.path.altsep is not None and os.path.altsep in name:
267
+ return True
268
+ if name.startswith("."):
269
+ return True
270
+ return False
271
+
272
+
273
+ def _get_url_from_path(path: str, name: str) -> Optional[str]:
274
+ """
275
+ First, it checks whether a provided path is an installable directory. If it
276
+ is, returns the path.
277
+
278
+ If false, check if the path is an archive file (such as a .whl).
279
+ The function checks if the path is a file. If false, if the path has
280
+ an @, it will treat it as a PEP 440 URL requirement and return the path.
281
+ """
282
+ if _looks_like_path(name) and os.path.isdir(path):
283
+ if is_installable_dir(path):
284
+ return path_to_url(path)
285
+ # TODO: The is_installable_dir test here might not be necessary
286
+ # now that it is done in load_pyproject_toml too.
287
+ raise InstallationError(
288
+ f"Directory {name!r} is not installable. Neither 'setup.py' "
289
+ "nor 'pyproject.toml' found."
290
+ )
291
+ if not is_archive_file(path):
292
+ return None
293
+ if os.path.isfile(path):
294
+ return path_to_url(path)
295
+ urlreq_parts = name.split("@", 1)
296
+ if len(urlreq_parts) >= 2 and not _looks_like_path(urlreq_parts[0]):
297
+ # If the path contains '@' and the part before it does not look
298
+ # like a path, try to treat it as a PEP 440 URL req instead.
299
+ return None
300
+ logger.warning(
301
+ "Requirement %r looks like a filename, but the file does not exist",
302
+ name,
303
+ )
304
+ return path_to_url(path)
305
+
306
+
307
+ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementParts:
308
+ if is_url(name):
309
+ marker_sep = "; "
310
+ else:
311
+ marker_sep = ";"
312
+ if marker_sep in name:
313
+ name, markers_as_string = name.split(marker_sep, 1)
314
+ markers_as_string = markers_as_string.strip()
315
+ if not markers_as_string:
316
+ markers = None
317
+ else:
318
+ markers = Marker(markers_as_string)
319
+ else:
320
+ markers = None
321
+ name = name.strip()
322
+ req_as_string = None
323
+ path = os.path.normpath(os.path.abspath(name))
324
+ link = None
325
+ extras_as_string = None
326
+
327
+ if is_url(name):
328
+ link = Link(name)
329
+ else:
330
+ p, extras_as_string = _strip_extras(path)
331
+ url = _get_url_from_path(p, name)
332
+ if url is not None:
333
+ link = Link(url)
334
+
335
+ # it's a local file, dir, or url
336
+ if link:
337
+ # Handle relative file URLs
338
+ if link.scheme == "file" and re.search(r"\.\./", link.url):
339
+ link = Link(path_to_url(os.path.normpath(os.path.abspath(link.path))))
340
+ # wheel file
341
+ if link.is_wheel:
342
+ wheel = Wheel(link.filename) # can raise InvalidWheelFilename
343
+ req_as_string = f"{wheel.name}=={wheel.version}"
344
+ else:
345
+ # set the req to the egg fragment. when it's not there, this
346
+ # will become an 'unnamed' requirement
347
+ req_as_string = link.egg_fragment
348
+
349
+ # a requirement specifier
350
+ else:
351
+ req_as_string = name
352
+
353
+ extras = convert_extras(extras_as_string)
354
+
355
+ def with_source(text: str) -> str:
356
+ if not line_source:
357
+ return text
358
+ return f"{text} (from {line_source})"
359
+
360
+ def _parse_req_string(req_as_string: str) -> Requirement:
361
+ try:
362
+ return get_requirement(req_as_string)
363
+ except InvalidRequirement as exc:
364
+ if os.path.sep in req_as_string:
365
+ add_msg = "It looks like a path."
366
+ add_msg += deduce_helpful_msg(req_as_string)
367
+ elif "=" in req_as_string and not any(
368
+ op in req_as_string for op in operators
369
+ ):
370
+ add_msg = "= is not a valid operator. Did you mean == ?"
371
+ else:
372
+ add_msg = ""
373
+ msg = with_source(f"Invalid requirement: {req_as_string!r}: {exc}")
374
+ if add_msg:
375
+ msg += f"\nHint: {add_msg}"
376
+ raise InstallationError(msg)
377
+
378
+ if req_as_string is not None:
379
+ req: Optional[Requirement] = _parse_req_string(req_as_string)
380
+ else:
381
+ req = None
382
+
383
+ return RequirementParts(req, link, markers, extras)
384
+
385
+
386
+ def install_req_from_line(
387
+ name: str,
388
+ comes_from: Optional[Union[str, InstallRequirement]] = None,
389
+ *,
390
+ use_pep517: Optional[bool] = None,
391
+ isolated: bool = False,
392
+ global_options: Optional[List[str]] = None,
393
+ hash_options: Optional[Dict[str, List[str]]] = None,
394
+ constraint: bool = False,
395
+ line_source: Optional[str] = None,
396
+ user_supplied: bool = False,
397
+ config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
398
+ ) -> InstallRequirement:
399
+ """Creates an InstallRequirement from a name, which might be a
400
+ requirement, directory containing 'setup.py', filename, or URL.
401
+
402
+ :param line_source: An optional string describing where the line is from,
403
+ for logging purposes in case of an error.
404
+ """
405
+ parts = parse_req_from_line(name, line_source)
406
+
407
+ return InstallRequirement(
408
+ parts.requirement,
409
+ comes_from,
410
+ link=parts.link,
411
+ markers=parts.markers,
412
+ use_pep517=use_pep517,
413
+ isolated=isolated,
414
+ global_options=global_options,
415
+ hash_options=hash_options,
416
+ config_settings=config_settings,
417
+ constraint=constraint,
418
+ extras=parts.extras,
419
+ user_supplied=user_supplied,
420
+ )
421
+
422
+
423
+ def install_req_from_req_string(
424
+ req_string: str,
425
+ comes_from: Optional[InstallRequirement] = None,
426
+ isolated: bool = False,
427
+ use_pep517: Optional[bool] = None,
428
+ user_supplied: bool = False,
429
+ ) -> InstallRequirement:
430
+ try:
431
+ req = get_requirement(req_string)
432
+ except InvalidRequirement as exc:
433
+ raise InstallationError(f"Invalid requirement: {req_string!r}: {exc}")
434
+
435
+ domains_not_allowed = [
436
+ PyPI.file_storage_domain,
437
+ TestPyPI.file_storage_domain,
438
+ ]
439
+ if (
440
+ req.url
441
+ and comes_from
442
+ and comes_from.link
443
+ and comes_from.link.netloc in domains_not_allowed
444
+ ):
445
+ # Explicitly disallow pypi packages that depend on external urls
446
+ raise InstallationError(
447
+ "Packages installed from PyPI cannot depend on packages "
448
+ "which are not also hosted on PyPI.\n"
449
+ f"{comes_from.name} depends on {req} "
450
+ )
451
+
452
+ return InstallRequirement(
453
+ req,
454
+ comes_from,
455
+ isolated=isolated,
456
+ use_pep517=use_pep517,
457
+ user_supplied=user_supplied,
458
+ )
459
+
460
+
461
+ def install_req_from_parsed_requirement(
462
+ parsed_req: ParsedRequirement,
463
+ isolated: bool = False,
464
+ use_pep517: Optional[bool] = None,
465
+ user_supplied: bool = False,
466
+ config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
467
+ ) -> InstallRequirement:
468
+ if parsed_req.is_editable:
469
+ req = install_req_from_editable(
470
+ parsed_req.requirement,
471
+ comes_from=parsed_req.comes_from,
472
+ use_pep517=use_pep517,
473
+ constraint=parsed_req.constraint,
474
+ isolated=isolated,
475
+ user_supplied=user_supplied,
476
+ config_settings=config_settings,
477
+ )
478
+
479
+ else:
480
+ req = install_req_from_line(
481
+ parsed_req.requirement,
482
+ comes_from=parsed_req.comes_from,
483
+ use_pep517=use_pep517,
484
+ isolated=isolated,
485
+ global_options=(
486
+ parsed_req.options.get("global_options", [])
487
+ if parsed_req.options
488
+ else []
489
+ ),
490
+ hash_options=(
491
+ parsed_req.options.get("hashes", {}) if parsed_req.options else {}
492
+ ),
493
+ constraint=parsed_req.constraint,
494
+ line_source=parsed_req.line_source,
495
+ user_supplied=user_supplied,
496
+ config_settings=config_settings,
497
+ )
498
+ return req
499
+
500
+
501
+ def install_req_from_link_and_ireq(
502
+ link: Link, ireq: InstallRequirement
503
+ ) -> InstallRequirement:
504
+ return InstallRequirement(
505
+ req=ireq.req,
506
+ comes_from=ireq.comes_from,
507
+ editable=ireq.editable,
508
+ link=link,
509
+ markers=ireq.markers,
510
+ use_pep517=ireq.use_pep517,
511
+ isolated=ireq.isolated,
512
+ global_options=ireq.global_options,
513
+ hash_options=ireq.hash_options,
514
+ config_settings=ireq.config_settings,
515
+ user_supplied=ireq.user_supplied,
516
+ )
517
+
518
+
519
+ def install_req_drop_extras(ireq: InstallRequirement) -> InstallRequirement:
520
+ """
521
+ Creates a new InstallationRequirement using the given template but without
522
+ any extras. Sets the original requirement as the new one's parent
523
+ (comes_from).
524
+ """
525
+ return InstallRequirement(
526
+ req=(
527
+ _set_requirement_extras(ireq.req, set()) if ireq.req is not None else None
528
+ ),
529
+ comes_from=ireq,
530
+ editable=ireq.editable,
531
+ link=ireq.link,
532
+ markers=ireq.markers,
533
+ use_pep517=ireq.use_pep517,
534
+ isolated=ireq.isolated,
535
+ global_options=ireq.global_options,
536
+ hash_options=ireq.hash_options,
537
+ constraint=ireq.constraint,
538
+ extras=[],
539
+ config_settings=ireq.config_settings,
540
+ user_supplied=ireq.user_supplied,
541
+ permit_editable_wheels=ireq.permit_editable_wheels,
542
+ )
543
+
544
+
545
+ def install_req_extend_extras(
546
+ ireq: InstallRequirement,
547
+ extras: Collection[str],
548
+ ) -> InstallRequirement:
549
+ """
550
+ Returns a copy of an installation requirement with some additional extras.
551
+ Makes a shallow copy of the ireq object.
552
+ """
553
+ result = copy.copy(ireq)
554
+ result.extras = {*ireq.extras, *extras}
555
+ result.req = (
556
+ _set_requirement_extras(ireq.req, result.extras)
557
+ if ireq.req is not None
558
+ else None
559
+ )
560
+ return result
.mcp-venv/lib/python3.13/site-packages/pip/_internal/req/req_dependency_group.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from typing import Any, Dict, Iterable, Iterator, List, Tuple
3
+
4
+ if sys.version_info >= (3, 11):
5
+ import tomllib
6
+ else:
7
+ from pip._vendor import tomli as tomllib
8
+
9
+ from pip._vendor.dependency_groups import DependencyGroupResolver
10
+
11
+ from pip._internal.exceptions import InstallationError
12
+
13
+
14
+ def parse_dependency_groups(groups: List[Tuple[str, str]]) -> List[str]:
15
+ """
16
+ Parse dependency groups data as provided via the CLI, in a `[path:]group` syntax.
17
+
18
+ Raises InstallationErrors if anything goes wrong.
19
+ """
20
+ resolvers = _build_resolvers(path for (path, _) in groups)
21
+ return list(_resolve_all_groups(resolvers, groups))
22
+
23
+
24
+ def _resolve_all_groups(
25
+ resolvers: Dict[str, DependencyGroupResolver], groups: List[Tuple[str, str]]
26
+ ) -> Iterator[str]:
27
+ """
28
+ Run all resolution, converting any error from `DependencyGroupResolver` into
29
+ an InstallationError.
30
+ """
31
+ for path, groupname in groups:
32
+ resolver = resolvers[path]
33
+ try:
34
+ yield from (str(req) for req in resolver.resolve(groupname))
35
+ except (ValueError, TypeError, LookupError) as e:
36
+ raise InstallationError(
37
+ f"[dependency-groups] resolution failed for '{groupname}' "
38
+ f"from '{path}': {e}"
39
+ ) from e
40
+
41
+
42
+ def _build_resolvers(paths: Iterable[str]) -> Dict[str, Any]:
43
+ resolvers = {}
44
+ for path in paths:
45
+ if path in resolvers:
46
+ continue
47
+
48
+ pyproject = _load_pyproject(path)
49
+ if "dependency-groups" not in pyproject:
50
+ raise InstallationError(
51
+ f"[dependency-groups] table was missing from '{path}'. "
52
+ "Cannot resolve '--group' option."
53
+ )
54
+ raw_dependency_groups = pyproject["dependency-groups"]
55
+ if not isinstance(raw_dependency_groups, dict):
56
+ raise InstallationError(
57
+ f"[dependency-groups] table was malformed in {path}. "
58
+ "Cannot resolve '--group' option."
59
+ )
60
+
61
+ resolvers[path] = DependencyGroupResolver(raw_dependency_groups)
62
+ return resolvers
63
+
64
+
65
+ def _load_pyproject(path: str) -> Dict[str, Any]:
66
+ """
67
+ This helper loads a pyproject.toml as TOML.
68
+
69
+ It raises an InstallationError if the operation fails.
70
+ """
71
+ try:
72
+ with open(path, "rb") as fp:
73
+ return tomllib.load(fp)
74
+ except FileNotFoundError:
75
+ raise InstallationError(f"{path} not found. Cannot resolve '--group' option.")
76
+ except tomllib.TOMLDecodeError as e:
77
+ raise InstallationError(f"Error parsing {path}: {e}") from e
78
+ except OSError as e:
79
+ raise InstallationError(f"Error reading {path}: {e}") from e
.mcp-venv/lib/python3.13/site-packages/pip/_internal/req/req_file.py ADDED
@@ -0,0 +1,623 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Requirements file parsing
3
+ """
4
+
5
+ import codecs
6
+ import locale
7
+ import logging
8
+ import optparse
9
+ import os
10
+ import re
11
+ import shlex
12
+ import sys
13
+ import urllib.parse
14
+ from dataclasses import dataclass
15
+ from optparse import Values
16
+ from typing import (
17
+ TYPE_CHECKING,
18
+ Any,
19
+ Callable,
20
+ Dict,
21
+ Generator,
22
+ Iterable,
23
+ List,
24
+ NoReturn,
25
+ Optional,
26
+ Tuple,
27
+ )
28
+
29
+ from pip._internal.cli import cmdoptions
30
+ from pip._internal.exceptions import InstallationError, RequirementsFileParseError
31
+ from pip._internal.models.search_scope import SearchScope
32
+
33
+ if TYPE_CHECKING:
34
+ from pip._internal.index.package_finder import PackageFinder
35
+ from pip._internal.network.session import PipSession
36
+
37
+ __all__ = ["parse_requirements"]
38
+
39
+ ReqFileLines = Iterable[Tuple[int, str]]
40
+
41
+ LineParser = Callable[[str], Tuple[str, Values]]
42
+
43
+ SCHEME_RE = re.compile(r"^(http|https|file):", re.I)
44
+ COMMENT_RE = re.compile(r"(^|\s+)#.*$")
45
+
46
+ # Matches environment variable-style values in '${MY_VARIABLE_1}' with the
47
+ # variable name consisting of only uppercase letters, digits or the '_'
48
+ # (underscore). This follows the POSIX standard defined in IEEE Std 1003.1,
49
+ # 2013 Edition.
50
+ ENV_VAR_RE = re.compile(r"(?P<var>\$\{(?P<name>[A-Z0-9_]+)\})")
51
+
52
+ SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [
53
+ cmdoptions.index_url,
54
+ cmdoptions.extra_index_url,
55
+ cmdoptions.no_index,
56
+ cmdoptions.constraints,
57
+ cmdoptions.requirements,
58
+ cmdoptions.editable,
59
+ cmdoptions.find_links,
60
+ cmdoptions.no_binary,
61
+ cmdoptions.only_binary,
62
+ cmdoptions.prefer_binary,
63
+ cmdoptions.require_hashes,
64
+ cmdoptions.pre,
65
+ cmdoptions.trusted_host,
66
+ cmdoptions.use_new_feature,
67
+ ]
68
+
69
+ # options to be passed to requirements
70
+ SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [
71
+ cmdoptions.global_options,
72
+ cmdoptions.hash,
73
+ cmdoptions.config_settings,
74
+ ]
75
+
76
+ SUPPORTED_OPTIONS_EDITABLE_REQ: List[Callable[..., optparse.Option]] = [
77
+ cmdoptions.config_settings,
78
+ ]
79
+
80
+
81
+ # the 'dest' string values
82
+ SUPPORTED_OPTIONS_REQ_DEST = [str(o().dest) for o in SUPPORTED_OPTIONS_REQ]
83
+ SUPPORTED_OPTIONS_EDITABLE_REQ_DEST = [
84
+ str(o().dest) for o in SUPPORTED_OPTIONS_EDITABLE_REQ
85
+ ]
86
+
87
+ # order of BOMS is important: codecs.BOM_UTF16_LE is a prefix of codecs.BOM_UTF32_LE
88
+ # so data.startswith(BOM_UTF16_LE) would be true for UTF32_LE data
89
+ BOMS: List[Tuple[bytes, str]] = [
90
+ (codecs.BOM_UTF8, "utf-8"),
91
+ (codecs.BOM_UTF32, "utf-32"),
92
+ (codecs.BOM_UTF32_BE, "utf-32-be"),
93
+ (codecs.BOM_UTF32_LE, "utf-32-le"),
94
+ (codecs.BOM_UTF16, "utf-16"),
95
+ (codecs.BOM_UTF16_BE, "utf-16-be"),
96
+ (codecs.BOM_UTF16_LE, "utf-16-le"),
97
+ ]
98
+
99
+ PEP263_ENCODING_RE = re.compile(rb"coding[:=]\s*([-\w.]+)")
100
+ DEFAULT_ENCODING = "utf-8"
101
+
102
+ logger = logging.getLogger(__name__)
103
+
104
+
105
+ @dataclass(frozen=True)
106
+ class ParsedRequirement:
107
+ # TODO: replace this with slots=True when dropping Python 3.9 support.
108
+ __slots__ = (
109
+ "requirement",
110
+ "is_editable",
111
+ "comes_from",
112
+ "constraint",
113
+ "options",
114
+ "line_source",
115
+ )
116
+
117
+ requirement: str
118
+ is_editable: bool
119
+ comes_from: str
120
+ constraint: bool
121
+ options: Optional[Dict[str, Any]]
122
+ line_source: Optional[str]
123
+
124
+
125
+ @dataclass(frozen=True)
126
+ class ParsedLine:
127
+ __slots__ = ("filename", "lineno", "args", "opts", "constraint")
128
+
129
+ filename: str
130
+ lineno: int
131
+ args: str
132
+ opts: Values
133
+ constraint: bool
134
+
135
+ @property
136
+ def is_editable(self) -> bool:
137
+ return bool(self.opts.editables)
138
+
139
+ @property
140
+ def requirement(self) -> Optional[str]:
141
+ if self.args:
142
+ return self.args
143
+ elif self.is_editable:
144
+ # We don't support multiple -e on one line
145
+ return self.opts.editables[0]
146
+ return None
147
+
148
+
149
+ def parse_requirements(
150
+ filename: str,
151
+ session: "PipSession",
152
+ finder: Optional["PackageFinder"] = None,
153
+ options: Optional[optparse.Values] = None,
154
+ constraint: bool = False,
155
+ ) -> Generator[ParsedRequirement, None, None]:
156
+ """Parse a requirements file and yield ParsedRequirement instances.
157
+
158
+ :param filename: Path or url of requirements file.
159
+ :param session: PipSession instance.
160
+ :param finder: Instance of pip.index.PackageFinder.
161
+ :param options: cli options.
162
+ :param constraint: If true, parsing a constraint file rather than
163
+ requirements file.
164
+ """
165
+ line_parser = get_line_parser(finder)
166
+ parser = RequirementsFileParser(session, line_parser)
167
+
168
+ for parsed_line in parser.parse(filename, constraint):
169
+ parsed_req = handle_line(
170
+ parsed_line, options=options, finder=finder, session=session
171
+ )
172
+ if parsed_req is not None:
173
+ yield parsed_req
174
+
175
+
176
+ def preprocess(content: str) -> ReqFileLines:
177
+ """Split, filter, and join lines, and return a line iterator
178
+
179
+ :param content: the content of the requirements file
180
+ """
181
+ lines_enum: ReqFileLines = enumerate(content.splitlines(), start=1)
182
+ lines_enum = join_lines(lines_enum)
183
+ lines_enum = ignore_comments(lines_enum)
184
+ lines_enum = expand_env_variables(lines_enum)
185
+ return lines_enum
186
+
187
+
188
+ def handle_requirement_line(
189
+ line: ParsedLine,
190
+ options: Optional[optparse.Values] = None,
191
+ ) -> ParsedRequirement:
192
+ # preserve for the nested code path
193
+ line_comes_from = "{} {} (line {})".format(
194
+ "-c" if line.constraint else "-r",
195
+ line.filename,
196
+ line.lineno,
197
+ )
198
+
199
+ assert line.requirement is not None
200
+
201
+ # get the options that apply to requirements
202
+ if line.is_editable:
203
+ supported_dest = SUPPORTED_OPTIONS_EDITABLE_REQ_DEST
204
+ else:
205
+ supported_dest = SUPPORTED_OPTIONS_REQ_DEST
206
+ req_options = {}
207
+ for dest in supported_dest:
208
+ if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
209
+ req_options[dest] = line.opts.__dict__[dest]
210
+
211
+ line_source = f"line {line.lineno} of {line.filename}"
212
+ return ParsedRequirement(
213
+ requirement=line.requirement,
214
+ is_editable=line.is_editable,
215
+ comes_from=line_comes_from,
216
+ constraint=line.constraint,
217
+ options=req_options,
218
+ line_source=line_source,
219
+ )
220
+
221
+
222
+ def handle_option_line(
223
+ opts: Values,
224
+ filename: str,
225
+ lineno: int,
226
+ finder: Optional["PackageFinder"] = None,
227
+ options: Optional[optparse.Values] = None,
228
+ session: Optional["PipSession"] = None,
229
+ ) -> None:
230
+ if opts.hashes:
231
+ logger.warning(
232
+ "%s line %s has --hash but no requirement, and will be ignored.",
233
+ filename,
234
+ lineno,
235
+ )
236
+
237
+ if options:
238
+ # percolate options upward
239
+ if opts.require_hashes:
240
+ options.require_hashes = opts.require_hashes
241
+ if opts.features_enabled:
242
+ options.features_enabled.extend(
243
+ f for f in opts.features_enabled if f not in options.features_enabled
244
+ )
245
+
246
+ # set finder options
247
+ if finder:
248
+ find_links = finder.find_links
249
+ index_urls = finder.index_urls
250
+ no_index = finder.search_scope.no_index
251
+ if opts.no_index is True:
252
+ no_index = True
253
+ index_urls = []
254
+ if opts.index_url and not no_index:
255
+ index_urls = [opts.index_url]
256
+ if opts.extra_index_urls and not no_index:
257
+ index_urls.extend(opts.extra_index_urls)
258
+ if opts.find_links:
259
+ # FIXME: it would be nice to keep track of the source
260
+ # of the find_links: support a find-links local path
261
+ # relative to a requirements file.
262
+ value = opts.find_links[0]
263
+ req_dir = os.path.dirname(os.path.abspath(filename))
264
+ relative_to_reqs_file = os.path.join(req_dir, value)
265
+ if os.path.exists(relative_to_reqs_file):
266
+ value = relative_to_reqs_file
267
+ find_links.append(value)
268
+
269
+ if session:
270
+ # We need to update the auth urls in session
271
+ session.update_index_urls(index_urls)
272
+
273
+ search_scope = SearchScope(
274
+ find_links=find_links,
275
+ index_urls=index_urls,
276
+ no_index=no_index,
277
+ )
278
+ finder.search_scope = search_scope
279
+
280
+ if opts.pre:
281
+ finder.set_allow_all_prereleases()
282
+
283
+ if opts.prefer_binary:
284
+ finder.set_prefer_binary()
285
+
286
+ if session:
287
+ for host in opts.trusted_hosts or []:
288
+ source = f"line {lineno} of {filename}"
289
+ session.add_trusted_host(host, source=source)
290
+
291
+
292
+ def handle_line(
293
+ line: ParsedLine,
294
+ options: Optional[optparse.Values] = None,
295
+ finder: Optional["PackageFinder"] = None,
296
+ session: Optional["PipSession"] = None,
297
+ ) -> Optional[ParsedRequirement]:
298
+ """Handle a single parsed requirements line; This can result in
299
+ creating/yielding requirements, or updating the finder.
300
+
301
+ :param line: The parsed line to be processed.
302
+ :param options: CLI options.
303
+ :param finder: The finder - updated by non-requirement lines.
304
+ :param session: The session - updated by non-requirement lines.
305
+
306
+ Returns a ParsedRequirement object if the line is a requirement line,
307
+ otherwise returns None.
308
+
309
+ For lines that contain requirements, the only options that have an effect
310
+ are from SUPPORTED_OPTIONS_REQ, and they are scoped to the
311
+ requirement. Other options from SUPPORTED_OPTIONS may be present, but are
312
+ ignored.
313
+
314
+ For lines that do not contain requirements, the only options that have an
315
+ effect are from SUPPORTED_OPTIONS. Options from SUPPORTED_OPTIONS_REQ may
316
+ be present, but are ignored. These lines may contain multiple options
317
+ (although our docs imply only one is supported), and all our parsed and
318
+ affect the finder.
319
+ """
320
+
321
+ if line.requirement is not None:
322
+ parsed_req = handle_requirement_line(line, options)
323
+ return parsed_req
324
+ else:
325
+ handle_option_line(
326
+ line.opts,
327
+ line.filename,
328
+ line.lineno,
329
+ finder,
330
+ options,
331
+ session,
332
+ )
333
+ return None
334
+
335
+
336
+ class RequirementsFileParser:
337
+ def __init__(
338
+ self,
339
+ session: "PipSession",
340
+ line_parser: LineParser,
341
+ ) -> None:
342
+ self._session = session
343
+ self._line_parser = line_parser
344
+
345
+ def parse(
346
+ self, filename: str, constraint: bool
347
+ ) -> Generator[ParsedLine, None, None]:
348
+ """Parse a given file, yielding parsed lines."""
349
+ yield from self._parse_and_recurse(
350
+ filename, constraint, [{os.path.abspath(filename): None}]
351
+ )
352
+
353
+ def _parse_and_recurse(
354
+ self,
355
+ filename: str,
356
+ constraint: bool,
357
+ parsed_files_stack: List[Dict[str, Optional[str]]],
358
+ ) -> Generator[ParsedLine, None, None]:
359
+ for line in self._parse_file(filename, constraint):
360
+ if line.requirement is None and (
361
+ line.opts.requirements or line.opts.constraints
362
+ ):
363
+ # parse a nested requirements file
364
+ if line.opts.requirements:
365
+ req_path = line.opts.requirements[0]
366
+ nested_constraint = False
367
+ else:
368
+ req_path = line.opts.constraints[0]
369
+ nested_constraint = True
370
+
371
+ # original file is over http
372
+ if SCHEME_RE.search(filename):
373
+ # do a url join so relative paths work
374
+ req_path = urllib.parse.urljoin(filename, req_path)
375
+ # original file and nested file are paths
376
+ elif not SCHEME_RE.search(req_path):
377
+ # do a join so relative paths work
378
+ # and then abspath so that we can identify recursive references
379
+ req_path = os.path.abspath(
380
+ os.path.join(
381
+ os.path.dirname(filename),
382
+ req_path,
383
+ )
384
+ )
385
+ parsed_files = parsed_files_stack[0]
386
+ if req_path in parsed_files:
387
+ initial_file = parsed_files[req_path]
388
+ tail = (
389
+ f" and again in {initial_file}"
390
+ if initial_file is not None
391
+ else ""
392
+ )
393
+ raise RequirementsFileParseError(
394
+ f"{req_path} recursively references itself in {filename}{tail}"
395
+ )
396
+ # Keeping a track where was each file first included in
397
+ new_parsed_files = parsed_files.copy()
398
+ new_parsed_files[req_path] = filename
399
+ yield from self._parse_and_recurse(
400
+ req_path, nested_constraint, [new_parsed_files, *parsed_files_stack]
401
+ )
402
+ else:
403
+ yield line
404
+
405
+ def _parse_file(
406
+ self, filename: str, constraint: bool
407
+ ) -> Generator[ParsedLine, None, None]:
408
+ _, content = get_file_content(filename, self._session)
409
+
410
+ lines_enum = preprocess(content)
411
+
412
+ for line_number, line in lines_enum:
413
+ try:
414
+ args_str, opts = self._line_parser(line)
415
+ except OptionParsingError as e:
416
+ # add offending line
417
+ msg = f"Invalid requirement: {line}\n{e.msg}"
418
+ raise RequirementsFileParseError(msg)
419
+
420
+ yield ParsedLine(
421
+ filename,
422
+ line_number,
423
+ args_str,
424
+ opts,
425
+ constraint,
426
+ )
427
+
428
+
429
+ def get_line_parser(finder: Optional["PackageFinder"]) -> LineParser:
430
+ def parse_line(line: str) -> Tuple[str, Values]:
431
+ # Build new parser for each line since it accumulates appendable
432
+ # options.
433
+ parser = build_parser()
434
+ defaults = parser.get_default_values()
435
+ defaults.index_url = None
436
+ if finder:
437
+ defaults.format_control = finder.format_control
438
+
439
+ args_str, options_str = break_args_options(line)
440
+
441
+ try:
442
+ options = shlex.split(options_str)
443
+ except ValueError as e:
444
+ raise OptionParsingError(f"Could not split options: {options_str}") from e
445
+
446
+ opts, _ = parser.parse_args(options, defaults)
447
+
448
+ return args_str, opts
449
+
450
+ return parse_line
451
+
452
+
453
+ def break_args_options(line: str) -> Tuple[str, str]:
454
+ """Break up the line into an args and options string. We only want to shlex
455
+ (and then optparse) the options, not the args. args can contain markers
456
+ which are corrupted by shlex.
457
+ """
458
+ tokens = line.split(" ")
459
+ args = []
460
+ options = tokens[:]
461
+ for token in tokens:
462
+ if token.startswith("-") or token.startswith("--"):
463
+ break
464
+ else:
465
+ args.append(token)
466
+ options.pop(0)
467
+ return " ".join(args), " ".join(options)
468
+
469
+
470
+ class OptionParsingError(Exception):
471
+ def __init__(self, msg: str) -> None:
472
+ self.msg = msg
473
+
474
+
475
+ def build_parser() -> optparse.OptionParser:
476
+ """
477
+ Return a parser for parsing requirement lines
478
+ """
479
+ parser = optparse.OptionParser(add_help_option=False)
480
+
481
+ option_factories = SUPPORTED_OPTIONS + SUPPORTED_OPTIONS_REQ
482
+ for option_factory in option_factories:
483
+ option = option_factory()
484
+ parser.add_option(option)
485
+
486
+ # By default optparse sys.exits on parsing errors. We want to wrap
487
+ # that in our own exception.
488
+ def parser_exit(self: Any, msg: str) -> "NoReturn":
489
+ raise OptionParsingError(msg)
490
+
491
+ # NOTE: mypy disallows assigning to a method
492
+ # https://github.com/python/mypy/issues/2427
493
+ parser.exit = parser_exit # type: ignore
494
+
495
+ return parser
496
+
497
+
498
+ def join_lines(lines_enum: ReqFileLines) -> ReqFileLines:
499
+ """Joins a line ending in '\' with the previous line (except when following
500
+ comments). The joined line takes on the index of the first line.
501
+ """
502
+ primary_line_number = None
503
+ new_line: List[str] = []
504
+ for line_number, line in lines_enum:
505
+ if not line.endswith("\\") or COMMENT_RE.match(line):
506
+ if COMMENT_RE.match(line):
507
+ # this ensures comments are always matched later
508
+ line = " " + line
509
+ if new_line:
510
+ new_line.append(line)
511
+ assert primary_line_number is not None
512
+ yield primary_line_number, "".join(new_line)
513
+ new_line = []
514
+ else:
515
+ yield line_number, line
516
+ else:
517
+ if not new_line:
518
+ primary_line_number = line_number
519
+ new_line.append(line.strip("\\"))
520
+
521
+ # last line contains \
522
+ if new_line:
523
+ assert primary_line_number is not None
524
+ yield primary_line_number, "".join(new_line)
525
+
526
+ # TODO: handle space after '\'.
527
+
528
+
529
+ def ignore_comments(lines_enum: ReqFileLines) -> ReqFileLines:
530
+ """
531
+ Strips comments and filter empty lines.
532
+ """
533
+ for line_number, line in lines_enum:
534
+ line = COMMENT_RE.sub("", line)
535
+ line = line.strip()
536
+ if line:
537
+ yield line_number, line
538
+
539
+
540
+ def expand_env_variables(lines_enum: ReqFileLines) -> ReqFileLines:
541
+ """Replace all environment variables that can be retrieved via `os.getenv`.
542
+
543
+ The only allowed format for environment variables defined in the
544
+ requirement file is `${MY_VARIABLE_1}` to ensure two things:
545
+
546
+ 1. Strings that contain a `$` aren't accidentally (partially) expanded.
547
+ 2. Ensure consistency across platforms for requirement files.
548
+
549
+ These points are the result of a discussion on the `github pull
550
+ request #3514 <https://github.com/pypa/pip/pull/3514>`_.
551
+
552
+ Valid characters in variable names follow the `POSIX standard
553
+ <http://pubs.opengroup.org/onlinepubs/9699919799/>`_ and are limited
554
+ to uppercase letter, digits and the `_` (underscore).
555
+ """
556
+ for line_number, line in lines_enum:
557
+ for env_var, var_name in ENV_VAR_RE.findall(line):
558
+ value = os.getenv(var_name)
559
+ if not value:
560
+ continue
561
+
562
+ line = line.replace(env_var, value)
563
+
564
+ yield line_number, line
565
+
566
+
567
+ def get_file_content(url: str, session: "PipSession") -> Tuple[str, str]:
568
+ """Gets the content of a file; it may be a filename, file: URL, or
569
+ http: URL. Returns (location, content). Content is unicode.
570
+ Respects # -*- coding: declarations on the retrieved files.
571
+
572
+ :param url: File path or url.
573
+ :param session: PipSession instance.
574
+ """
575
+ scheme = urllib.parse.urlsplit(url).scheme
576
+ # Pip has special support for file:// URLs (LocalFSAdapter).
577
+ if scheme in ["http", "https", "file"]:
578
+ # Delay importing heavy network modules until absolutely necessary.
579
+ from pip._internal.network.utils import raise_for_status
580
+
581
+ resp = session.get(url)
582
+ raise_for_status(resp)
583
+ return resp.url, resp.text
584
+
585
+ # Assume this is a bare path.
586
+ try:
587
+ with open(url, "rb") as f:
588
+ raw_content = f.read()
589
+ except OSError as exc:
590
+ raise InstallationError(f"Could not open requirements file: {exc}")
591
+
592
+ content = _decode_req_file(raw_content, url)
593
+
594
+ return url, content
595
+
596
+
597
+ def _decode_req_file(data: bytes, url: str) -> str:
598
+ for bom, encoding in BOMS:
599
+ if data.startswith(bom):
600
+ return data[len(bom) :].decode(encoding)
601
+
602
+ for line in data.split(b"\n")[:2]:
603
+ if line[0:1] == b"#":
604
+ result = PEP263_ENCODING_RE.search(line)
605
+ if result is not None:
606
+ encoding = result.groups()[0].decode("ascii")
607
+ return data.decode(encoding)
608
+
609
+ try:
610
+ return data.decode(DEFAULT_ENCODING)
611
+ except UnicodeDecodeError:
612
+ locale_encoding = locale.getpreferredencoding(False) or sys.getdefaultencoding()
613
+ logging.warning(
614
+ "unable to decode data from %s with default encoding %s, "
615
+ "falling back to encoding from locale: %s. "
616
+ "If this is intentional you should specify the encoding with a "
617
+ "PEP-263 style comment, e.g. '# -*- coding: %s -*-'",
618
+ url,
619
+ DEFAULT_ENCODING,
620
+ locale_encoding,
621
+ locale_encoding,
622
+ )
623
+ return data.decode(locale_encoding)
.mcp-venv/lib/python3.13/site-packages/pip/_internal/req/req_install.py ADDED
@@ -0,0 +1,934 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import logging
3
+ import os
4
+ import shutil
5
+ import sys
6
+ import uuid
7
+ import zipfile
8
+ from optparse import Values
9
+ from pathlib import Path
10
+ from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union
11
+
12
+ from pip._vendor.packaging.markers import Marker
13
+ from pip._vendor.packaging.requirements import Requirement
14
+ from pip._vendor.packaging.specifiers import SpecifierSet
15
+ from pip._vendor.packaging.utils import canonicalize_name
16
+ from pip._vendor.packaging.version import Version
17
+ from pip._vendor.packaging.version import parse as parse_version
18
+ from pip._vendor.pyproject_hooks import BuildBackendHookCaller
19
+
20
+ from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
21
+ from pip._internal.exceptions import InstallationError, PreviousBuildDirError
22
+ from pip._internal.locations import get_scheme
23
+ from pip._internal.metadata import (
24
+ BaseDistribution,
25
+ get_default_environment,
26
+ get_directory_distribution,
27
+ get_wheel_distribution,
28
+ )
29
+ from pip._internal.metadata.base import FilesystemWheel
30
+ from pip._internal.models.direct_url import DirectUrl
31
+ from pip._internal.models.link import Link
32
+ from pip._internal.operations.build.metadata import generate_metadata
33
+ from pip._internal.operations.build.metadata_editable import generate_editable_metadata
34
+ from pip._internal.operations.build.metadata_legacy import (
35
+ generate_metadata as generate_metadata_legacy,
36
+ )
37
+ from pip._internal.operations.install.editable_legacy import (
38
+ install_editable as install_editable_legacy,
39
+ )
40
+ from pip._internal.operations.install.wheel import install_wheel
41
+ from pip._internal.pyproject import load_pyproject_toml, make_pyproject_path
42
+ from pip._internal.req.req_uninstall import UninstallPathSet
43
+ from pip._internal.utils.deprecation import deprecated
44
+ from pip._internal.utils.hashes import Hashes
45
+ from pip._internal.utils.misc import (
46
+ ConfiguredBuildBackendHookCaller,
47
+ ask_path_exists,
48
+ backup_dir,
49
+ display_path,
50
+ hide_url,
51
+ is_installable_dir,
52
+ redact_auth_from_requirement,
53
+ redact_auth_from_url,
54
+ )
55
+ from pip._internal.utils.packaging import get_requirement
56
+ from pip._internal.utils.subprocess import runner_with_spinner_message
57
+ from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
58
+ from pip._internal.utils.unpacking import unpack_file
59
+ from pip._internal.utils.virtualenv import running_under_virtualenv
60
+ from pip._internal.vcs import vcs
61
+
62
+ logger = logging.getLogger(__name__)
63
+
64
+
65
+ class InstallRequirement:
66
+ """
67
+ Represents something that may be installed later on, may have information
68
+ about where to fetch the relevant requirement and also contains logic for
69
+ installing the said requirement.
70
+ """
71
+
72
+ def __init__(
73
+ self,
74
+ req: Optional[Requirement],
75
+ comes_from: Optional[Union[str, "InstallRequirement"]],
76
+ editable: bool = False,
77
+ link: Optional[Link] = None,
78
+ markers: Optional[Marker] = None,
79
+ use_pep517: Optional[bool] = None,
80
+ isolated: bool = False,
81
+ *,
82
+ global_options: Optional[List[str]] = None,
83
+ hash_options: Optional[Dict[str, List[str]]] = None,
84
+ config_settings: Optional[Dict[str, Union[str, List[str]]]] = None,
85
+ constraint: bool = False,
86
+ extras: Collection[str] = (),
87
+ user_supplied: bool = False,
88
+ permit_editable_wheels: bool = False,
89
+ ) -> None:
90
+ assert req is None or isinstance(req, Requirement), req
91
+ self.req = req
92
+ self.comes_from = comes_from
93
+ self.constraint = constraint
94
+ self.editable = editable
95
+ self.permit_editable_wheels = permit_editable_wheels
96
+
97
+ # source_dir is the local directory where the linked requirement is
98
+ # located, or unpacked. In case unpacking is needed, creating and
99
+ # populating source_dir is done by the RequirementPreparer. Note this
100
+ # is not necessarily the directory where pyproject.toml or setup.py is
101
+ # located - that one is obtained via unpacked_source_directory.
102
+ self.source_dir: Optional[str] = None
103
+ if self.editable:
104
+ assert link
105
+ if link.is_file:
106
+ self.source_dir = os.path.normpath(os.path.abspath(link.file_path))
107
+
108
+ # original_link is the direct URL that was provided by the user for the
109
+ # requirement, either directly or via a constraints file.
110
+ if link is None and req and req.url:
111
+ # PEP 508 URL requirement
112
+ link = Link(req.url)
113
+ self.link = self.original_link = link
114
+
115
+ # When this InstallRequirement is a wheel obtained from the cache of locally
116
+ # built wheels, this is the source link corresponding to the cache entry, which
117
+ # was used to download and build the cached wheel.
118
+ self.cached_wheel_source_link: Optional[Link] = None
119
+
120
+ # Information about the location of the artifact that was downloaded . This
121
+ # property is guaranteed to be set in resolver results.
122
+ self.download_info: Optional[DirectUrl] = None
123
+
124
+ # Path to any downloaded or already-existing package.
125
+ self.local_file_path: Optional[str] = None
126
+ if self.link and self.link.is_file:
127
+ self.local_file_path = self.link.file_path
128
+
129
+ if extras:
130
+ self.extras = extras
131
+ elif req:
132
+ self.extras = req.extras
133
+ else:
134
+ self.extras = set()
135
+ if markers is None and req:
136
+ markers = req.marker
137
+ self.markers = markers
138
+
139
+ # This holds the Distribution object if this requirement is already installed.
140
+ self.satisfied_by: Optional[BaseDistribution] = None
141
+ # Whether the installation process should try to uninstall an existing
142
+ # distribution before installing this requirement.
143
+ self.should_reinstall = False
144
+ # Temporary build location
145
+ self._temp_build_dir: Optional[TempDirectory] = None
146
+ # Set to True after successful installation
147
+ self.install_succeeded: Optional[bool] = None
148
+ # Supplied options
149
+ self.global_options = global_options if global_options else []
150
+ self.hash_options = hash_options if hash_options else {}
151
+ self.config_settings = config_settings
152
+ # Set to True after successful preparation of this requirement
153
+ self.prepared = False
154
+ # User supplied requirement are explicitly requested for installation
155
+ # by the user via CLI arguments or requirements files, as opposed to,
156
+ # e.g. dependencies, extras or constraints.
157
+ self.user_supplied = user_supplied
158
+
159
+ self.isolated = isolated
160
+ self.build_env: BuildEnvironment = NoOpBuildEnvironment()
161
+
162
+ # For PEP 517, the directory where we request the project metadata
163
+ # gets stored. We need this to pass to build_wheel, so the backend
164
+ # can ensure that the wheel matches the metadata (see the PEP for
165
+ # details).
166
+ self.metadata_directory: Optional[str] = None
167
+
168
+ # The static build requirements (from pyproject.toml)
169
+ self.pyproject_requires: Optional[List[str]] = None
170
+
171
+ # Build requirements that we will check are available
172
+ self.requirements_to_check: List[str] = []
173
+
174
+ # The PEP 517 backend we should use to build the project
175
+ self.pep517_backend: Optional[BuildBackendHookCaller] = None
176
+
177
+ # Are we using PEP 517 for this requirement?
178
+ # After pyproject.toml has been loaded, the only valid values are True
179
+ # and False. Before loading, None is valid (meaning "use the default").
180
+ # Setting an explicit value before loading pyproject.toml is supported,
181
+ # but after loading this flag should be treated as read only.
182
+ self.use_pep517 = use_pep517
183
+
184
+ # If config settings are provided, enforce PEP 517.
185
+ if self.config_settings:
186
+ if self.use_pep517 is False:
187
+ logger.warning(
188
+ "--no-use-pep517 ignored for %s "
189
+ "because --config-settings are specified.",
190
+ self,
191
+ )
192
+ self.use_pep517 = True
193
+
194
+ # This requirement needs more preparation before it can be built
195
+ self.needs_more_preparation = False
196
+
197
+ # This requirement needs to be unpacked before it can be installed.
198
+ self._archive_source: Optional[Path] = None
199
+
200
+ def __str__(self) -> str:
201
+ if self.req:
202
+ s = redact_auth_from_requirement(self.req)
203
+ if self.link:
204
+ s += f" from {redact_auth_from_url(self.link.url)}"
205
+ elif self.link:
206
+ s = redact_auth_from_url(self.link.url)
207
+ else:
208
+ s = "<InstallRequirement>"
209
+ if self.satisfied_by is not None:
210
+ if self.satisfied_by.location is not None:
211
+ location = display_path(self.satisfied_by.location)
212
+ else:
213
+ location = "<memory>"
214
+ s += f" in {location}"
215
+ if self.comes_from:
216
+ if isinstance(self.comes_from, str):
217
+ comes_from: Optional[str] = self.comes_from
218
+ else:
219
+ comes_from = self.comes_from.from_path()
220
+ if comes_from:
221
+ s += f" (from {comes_from})"
222
+ return s
223
+
224
+ def __repr__(self) -> str:
225
+ return (
226
+ f"<{self.__class__.__name__} object: "
227
+ f"{str(self)} editable={self.editable!r}>"
228
+ )
229
+
230
+ def format_debug(self) -> str:
231
+ """An un-tested helper for getting state, for debugging."""
232
+ attributes = vars(self)
233
+ names = sorted(attributes)
234
+
235
+ state = (f"{attr}={attributes[attr]!r}" for attr in sorted(names))
236
+ return "<{name} object: {{{state}}}>".format(
237
+ name=self.__class__.__name__,
238
+ state=", ".join(state),
239
+ )
240
+
241
+ # Things that are valid for all kinds of requirements?
242
+ @property
243
+ def name(self) -> Optional[str]:
244
+ if self.req is None:
245
+ return None
246
+ return self.req.name
247
+
248
+ @functools.cached_property
249
+ def supports_pyproject_editable(self) -> bool:
250
+ if not self.use_pep517:
251
+ return False
252
+ assert self.pep517_backend
253
+ with self.build_env:
254
+ runner = runner_with_spinner_message(
255
+ "Checking if build backend supports build_editable"
256
+ )
257
+ with self.pep517_backend.subprocess_runner(runner):
258
+ return "build_editable" in self.pep517_backend._supported_features()
259
+
260
+ @property
261
+ def specifier(self) -> SpecifierSet:
262
+ assert self.req is not None
263
+ return self.req.specifier
264
+
265
+ @property
266
+ def is_direct(self) -> bool:
267
+ """Whether this requirement was specified as a direct URL."""
268
+ return self.original_link is not None
269
+
270
+ @property
271
+ def is_pinned(self) -> bool:
272
+ """Return whether I am pinned to an exact version.
273
+
274
+ For example, some-package==1.2 is pinned; some-package>1.2 is not.
275
+ """
276
+ assert self.req is not None
277
+ specifiers = self.req.specifier
278
+ return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="}
279
+
280
+ def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool:
281
+ if not extras_requested:
282
+ # Provide an extra to safely evaluate the markers
283
+ # without matching any extra
284
+ extras_requested = ("",)
285
+ if self.markers is not None:
286
+ return any(
287
+ self.markers.evaluate({"extra": extra}) for extra in extras_requested
288
+ )
289
+ else:
290
+ return True
291
+
292
+ @property
293
+ def has_hash_options(self) -> bool:
294
+ """Return whether any known-good hashes are specified as options.
295
+
296
+ These activate --require-hashes mode; hashes specified as part of a
297
+ URL do not.
298
+
299
+ """
300
+ return bool(self.hash_options)
301
+
302
+ def hashes(self, trust_internet: bool = True) -> Hashes:
303
+ """Return a hash-comparer that considers my option- and URL-based
304
+ hashes to be known-good.
305
+
306
+ Hashes in URLs--ones embedded in the requirements file, not ones
307
+ downloaded from an index server--are almost peers with ones from
308
+ flags. They satisfy --require-hashes (whether it was implicitly or
309
+ explicitly activated) but do not activate it. md5 and sha224 are not
310
+ allowed in flags, which should nudge people toward good algos. We
311
+ always OR all hashes together, even ones from URLs.
312
+
313
+ :param trust_internet: Whether to trust URL-based (#md5=...) hashes
314
+ downloaded from the internet, as by populate_link()
315
+
316
+ """
317
+ good_hashes = self.hash_options.copy()
318
+ if trust_internet:
319
+ link = self.link
320
+ elif self.is_direct and self.user_supplied:
321
+ link = self.original_link
322
+ else:
323
+ link = None
324
+ if link and link.hash:
325
+ assert link.hash_name is not None
326
+ good_hashes.setdefault(link.hash_name, []).append(link.hash)
327
+ return Hashes(good_hashes)
328
+
329
+ def from_path(self) -> Optional[str]:
330
+ """Format a nice indicator to show where this "comes from" """
331
+ if self.req is None:
332
+ return None
333
+ s = str(self.req)
334
+ if self.comes_from:
335
+ comes_from: Optional[str]
336
+ if isinstance(self.comes_from, str):
337
+ comes_from = self.comes_from
338
+ else:
339
+ comes_from = self.comes_from.from_path()
340
+ if comes_from:
341
+ s += "->" + comes_from
342
+ return s
343
+
344
+ def ensure_build_location(
345
+ self, build_dir: str, autodelete: bool, parallel_builds: bool
346
+ ) -> str:
347
+ assert build_dir is not None
348
+ if self._temp_build_dir is not None:
349
+ assert self._temp_build_dir.path
350
+ return self._temp_build_dir.path
351
+ if self.req is None:
352
+ # Some systems have /tmp as a symlink which confuses custom
353
+ # builds (such as numpy). Thus, we ensure that the real path
354
+ # is returned.
355
+ self._temp_build_dir = TempDirectory(
356
+ kind=tempdir_kinds.REQ_BUILD, globally_managed=True
357
+ )
358
+
359
+ return self._temp_build_dir.path
360
+
361
+ # This is the only remaining place where we manually determine the path
362
+ # for the temporary directory. It is only needed for editables where
363
+ # it is the value of the --src option.
364
+
365
+ # When parallel builds are enabled, add a UUID to the build directory
366
+ # name so multiple builds do not interfere with each other.
367
+ dir_name: str = canonicalize_name(self.req.name)
368
+ if parallel_builds:
369
+ dir_name = f"{dir_name}_{uuid.uuid4().hex}"
370
+
371
+ # FIXME: Is there a better place to create the build_dir? (hg and bzr
372
+ # need this)
373
+ if not os.path.exists(build_dir):
374
+ logger.debug("Creating directory %s", build_dir)
375
+ os.makedirs(build_dir)
376
+ actual_build_dir = os.path.join(build_dir, dir_name)
377
+ # `None` indicates that we respect the globally-configured deletion
378
+ # settings, which is what we actually want when auto-deleting.
379
+ delete_arg = None if autodelete else False
380
+ return TempDirectory(
381
+ path=actual_build_dir,
382
+ delete=delete_arg,
383
+ kind=tempdir_kinds.REQ_BUILD,
384
+ globally_managed=True,
385
+ ).path
386
+
387
+ def _set_requirement(self) -> None:
388
+ """Set requirement after generating metadata."""
389
+ assert self.req is None
390
+ assert self.metadata is not None
391
+ assert self.source_dir is not None
392
+
393
+ # Construct a Requirement object from the generated metadata
394
+ if isinstance(parse_version(self.metadata["Version"]), Version):
395
+ op = "=="
396
+ else:
397
+ op = "==="
398
+
399
+ self.req = get_requirement(
400
+ "".join(
401
+ [
402
+ self.metadata["Name"],
403
+ op,
404
+ self.metadata["Version"],
405
+ ]
406
+ )
407
+ )
408
+
409
+ def warn_on_mismatching_name(self) -> None:
410
+ assert self.req is not None
411
+ metadata_name = canonicalize_name(self.metadata["Name"])
412
+ if canonicalize_name(self.req.name) == metadata_name:
413
+ # Everything is fine.
414
+ return
415
+
416
+ # If we're here, there's a mismatch. Log a warning about it.
417
+ logger.warning(
418
+ "Generating metadata for package %s "
419
+ "produced metadata for project name %s. Fix your "
420
+ "#egg=%s fragments.",
421
+ self.name,
422
+ metadata_name,
423
+ self.name,
424
+ )
425
+ self.req = get_requirement(metadata_name)
426
+
427
+ def check_if_exists(self, use_user_site: bool) -> None:
428
+ """Find an installed distribution that satisfies or conflicts
429
+ with this requirement, and set self.satisfied_by or
430
+ self.should_reinstall appropriately.
431
+ """
432
+ if self.req is None:
433
+ return
434
+ existing_dist = get_default_environment().get_distribution(self.req.name)
435
+ if not existing_dist:
436
+ return
437
+
438
+ version_compatible = self.req.specifier.contains(
439
+ existing_dist.version,
440
+ prereleases=True,
441
+ )
442
+ if not version_compatible:
443
+ self.satisfied_by = None
444
+ if use_user_site:
445
+ if existing_dist.in_usersite:
446
+ self.should_reinstall = True
447
+ elif running_under_virtualenv() and existing_dist.in_site_packages:
448
+ raise InstallationError(
449
+ f"Will not install to the user site because it will "
450
+ f"lack sys.path precedence to {existing_dist.raw_name} "
451
+ f"in {existing_dist.location}"
452
+ )
453
+ else:
454
+ self.should_reinstall = True
455
+ else:
456
+ if self.editable:
457
+ self.should_reinstall = True
458
+ # when installing editables, nothing pre-existing should ever
459
+ # satisfy
460
+ self.satisfied_by = None
461
+ else:
462
+ self.satisfied_by = existing_dist
463
+
464
+ # Things valid for wheels
465
+ @property
466
+ def is_wheel(self) -> bool:
467
+ if not self.link:
468
+ return False
469
+ return self.link.is_wheel
470
+
471
+ @property
472
+ def is_wheel_from_cache(self) -> bool:
473
+ # When True, it means that this InstallRequirement is a local wheel file in the
474
+ # cache of locally built wheels.
475
+ return self.cached_wheel_source_link is not None
476
+
477
+ # Things valid for sdists
478
+ @property
479
+ def unpacked_source_directory(self) -> str:
480
+ assert self.source_dir, f"No source dir for {self}"
481
+ return os.path.join(
482
+ self.source_dir, self.link and self.link.subdirectory_fragment or ""
483
+ )
484
+
485
+ @property
486
+ def setup_py_path(self) -> str:
487
+ assert self.source_dir, f"No source dir for {self}"
488
+ setup_py = os.path.join(self.unpacked_source_directory, "setup.py")
489
+
490
+ return setup_py
491
+
492
+ @property
493
+ def setup_cfg_path(self) -> str:
494
+ assert self.source_dir, f"No source dir for {self}"
495
+ setup_cfg = os.path.join(self.unpacked_source_directory, "setup.cfg")
496
+
497
+ return setup_cfg
498
+
499
+ @property
500
+ def pyproject_toml_path(self) -> str:
501
+ assert self.source_dir, f"No source dir for {self}"
502
+ return make_pyproject_path(self.unpacked_source_directory)
503
+
504
+ def load_pyproject_toml(self) -> None:
505
+ """Load the pyproject.toml file.
506
+
507
+ After calling this routine, all of the attributes related to PEP 517
508
+ processing for this requirement have been set. In particular, the
509
+ use_pep517 attribute can be used to determine whether we should
510
+ follow the PEP 517 or legacy (setup.py) code path.
511
+ """
512
+ pyproject_toml_data = load_pyproject_toml(
513
+ self.use_pep517, self.pyproject_toml_path, self.setup_py_path, str(self)
514
+ )
515
+
516
+ if pyproject_toml_data is None:
517
+ assert not self.config_settings
518
+ self.use_pep517 = False
519
+ return
520
+
521
+ self.use_pep517 = True
522
+ requires, backend, check, backend_path = pyproject_toml_data
523
+ self.requirements_to_check = check
524
+ self.pyproject_requires = requires
525
+ self.pep517_backend = ConfiguredBuildBackendHookCaller(
526
+ self,
527
+ self.unpacked_source_directory,
528
+ backend,
529
+ backend_path=backend_path,
530
+ )
531
+
532
+ def isolated_editable_sanity_check(self) -> None:
533
+ """Check that an editable requirement if valid for use with PEP 517/518.
534
+
535
+ This verifies that an editable that has a pyproject.toml either supports PEP 660
536
+ or as a setup.py or a setup.cfg
537
+ """
538
+ if (
539
+ self.editable
540
+ and self.use_pep517
541
+ and not self.supports_pyproject_editable
542
+ and not os.path.isfile(self.setup_py_path)
543
+ and not os.path.isfile(self.setup_cfg_path)
544
+ ):
545
+ raise InstallationError(
546
+ f"Project {self} has a 'pyproject.toml' and its build "
547
+ f"backend is missing the 'build_editable' hook. Since it does not "
548
+ f"have a 'setup.py' nor a 'setup.cfg', "
549
+ f"it cannot be installed in editable mode. "
550
+ f"Consider using a build backend that supports PEP 660."
551
+ )
552
+
553
+ def prepare_metadata(self) -> None:
554
+ """Ensure that project metadata is available.
555
+
556
+ Under PEP 517 and PEP 660, call the backend hook to prepare the metadata.
557
+ Under legacy processing, call setup.py egg-info.
558
+ """
559
+ assert self.source_dir, f"No source dir for {self}"
560
+ details = self.name or f"from {self.link}"
561
+
562
+ if self.use_pep517:
563
+ assert self.pep517_backend is not None
564
+ if (
565
+ self.editable
566
+ and self.permit_editable_wheels
567
+ and self.supports_pyproject_editable
568
+ ):
569
+ self.metadata_directory = generate_editable_metadata(
570
+ build_env=self.build_env,
571
+ backend=self.pep517_backend,
572
+ details=details,
573
+ )
574
+ else:
575
+ self.metadata_directory = generate_metadata(
576
+ build_env=self.build_env,
577
+ backend=self.pep517_backend,
578
+ details=details,
579
+ )
580
+ else:
581
+ self.metadata_directory = generate_metadata_legacy(
582
+ build_env=self.build_env,
583
+ setup_py_path=self.setup_py_path,
584
+ source_dir=self.unpacked_source_directory,
585
+ isolated=self.isolated,
586
+ details=details,
587
+ )
588
+
589
+ # Act on the newly generated metadata, based on the name and version.
590
+ if not self.name:
591
+ self._set_requirement()
592
+ else:
593
+ self.warn_on_mismatching_name()
594
+
595
+ self.assert_source_matches_version()
596
+
597
+ @property
598
+ def metadata(self) -> Any:
599
+ if not hasattr(self, "_metadata"):
600
+ self._metadata = self.get_dist().metadata
601
+
602
+ return self._metadata
603
+
604
+ def get_dist(self) -> BaseDistribution:
605
+ if self.metadata_directory:
606
+ return get_directory_distribution(self.metadata_directory)
607
+ elif self.local_file_path and self.is_wheel:
608
+ assert self.req is not None
609
+ return get_wheel_distribution(
610
+ FilesystemWheel(self.local_file_path),
611
+ canonicalize_name(self.req.name),
612
+ )
613
+ raise AssertionError(
614
+ f"InstallRequirement {self} has no metadata directory and no wheel: "
615
+ f"can't make a distribution."
616
+ )
617
+
618
+ def assert_source_matches_version(self) -> None:
619
+ assert self.source_dir, f"No source dir for {self}"
620
+ version = self.metadata["version"]
621
+ if self.req and self.req.specifier and version not in self.req.specifier:
622
+ logger.warning(
623
+ "Requested %s, but installing version %s",
624
+ self,
625
+ version,
626
+ )
627
+ else:
628
+ logger.debug(
629
+ "Source in %s has version %s, which satisfies requirement %s",
630
+ display_path(self.source_dir),
631
+ version,
632
+ self,
633
+ )
634
+
635
+ # For both source distributions and editables
636
+ def ensure_has_source_dir(
637
+ self,
638
+ parent_dir: str,
639
+ autodelete: bool = False,
640
+ parallel_builds: bool = False,
641
+ ) -> None:
642
+ """Ensure that a source_dir is set.
643
+
644
+ This will create a temporary build dir if the name of the requirement
645
+ isn't known yet.
646
+
647
+ :param parent_dir: The ideal pip parent_dir for the source_dir.
648
+ Generally src_dir for editables and build_dir for sdists.
649
+ :return: self.source_dir
650
+ """
651
+ if self.source_dir is None:
652
+ self.source_dir = self.ensure_build_location(
653
+ parent_dir,
654
+ autodelete=autodelete,
655
+ parallel_builds=parallel_builds,
656
+ )
657
+
658
+ def needs_unpacked_archive(self, archive_source: Path) -> None:
659
+ assert self._archive_source is None
660
+ self._archive_source = archive_source
661
+
662
+ def ensure_pristine_source_checkout(self) -> None:
663
+ """Ensure the source directory has not yet been built in."""
664
+ assert self.source_dir is not None
665
+ if self._archive_source is not None:
666
+ unpack_file(str(self._archive_source), self.source_dir)
667
+ elif is_installable_dir(self.source_dir):
668
+ # If a checkout exists, it's unwise to keep going.
669
+ # version inconsistencies are logged later, but do not fail
670
+ # the installation.
671
+ raise PreviousBuildDirError(
672
+ f"pip can't proceed with requirements '{self}' due to a "
673
+ f"pre-existing build directory ({self.source_dir}). This is likely "
674
+ "due to a previous installation that failed . pip is "
675
+ "being responsible and not assuming it can delete this. "
676
+ "Please delete it and try again."
677
+ )
678
+
679
+ # For editable installations
680
+ def update_editable(self) -> None:
681
+ if not self.link:
682
+ logger.debug(
683
+ "Cannot update repository at %s; repository location is unknown",
684
+ self.source_dir,
685
+ )
686
+ return
687
+ assert self.editable
688
+ assert self.source_dir
689
+ if self.link.scheme == "file":
690
+ # Static paths don't get updated
691
+ return
692
+ vcs_backend = vcs.get_backend_for_scheme(self.link.scheme)
693
+ # Editable requirements are validated in Requirement constructors.
694
+ # So here, if it's neither a path nor a valid VCS URL, it's a bug.
695
+ assert vcs_backend, f"Unsupported VCS URL {self.link.url}"
696
+ hidden_url = hide_url(self.link.url)
697
+ vcs_backend.obtain(self.source_dir, url=hidden_url, verbosity=0)
698
+
699
+ # Top-level Actions
700
+ def uninstall(
701
+ self, auto_confirm: bool = False, verbose: bool = False
702
+ ) -> Optional[UninstallPathSet]:
703
+ """
704
+ Uninstall the distribution currently satisfying this requirement.
705
+
706
+ Prompts before removing or modifying files unless
707
+ ``auto_confirm`` is True.
708
+
709
+ Refuses to delete or modify files outside of ``sys.prefix`` -
710
+ thus uninstallation within a virtual environment can only
711
+ modify that virtual environment, even if the virtualenv is
712
+ linked to global site-packages.
713
+
714
+ """
715
+ assert self.req
716
+ dist = get_default_environment().get_distribution(self.req.name)
717
+ if not dist:
718
+ logger.warning("Skipping %s as it is not installed.", self.name)
719
+ return None
720
+ logger.info("Found existing installation: %s", dist)
721
+
722
+ uninstalled_pathset = UninstallPathSet.from_dist(dist)
723
+ uninstalled_pathset.remove(auto_confirm, verbose)
724
+ return uninstalled_pathset
725
+
726
+ def _get_archive_name(self, path: str, parentdir: str, rootdir: str) -> str:
727
+ def _clean_zip_name(name: str, prefix: str) -> str:
728
+ assert name.startswith(
729
+ prefix + os.path.sep
730
+ ), f"name {name!r} doesn't start with prefix {prefix!r}"
731
+ name = name[len(prefix) + 1 :]
732
+ name = name.replace(os.path.sep, "/")
733
+ return name
734
+
735
+ assert self.req is not None
736
+ path = os.path.join(parentdir, path)
737
+ name = _clean_zip_name(path, rootdir)
738
+ return self.req.name + "/" + name
739
+
740
+ def archive(self, build_dir: Optional[str]) -> None:
741
+ """Saves archive to provided build_dir.
742
+
743
+ Used for saving downloaded VCS requirements as part of `pip download`.
744
+ """
745
+ assert self.source_dir
746
+ if build_dir is None:
747
+ return
748
+
749
+ create_archive = True
750
+ archive_name = "{}-{}.zip".format(self.name, self.metadata["version"])
751
+ archive_path = os.path.join(build_dir, archive_name)
752
+
753
+ if os.path.exists(archive_path):
754
+ response = ask_path_exists(
755
+ f"The file {display_path(archive_path)} exists. (i)gnore, (w)ipe, "
756
+ "(b)ackup, (a)bort ",
757
+ ("i", "w", "b", "a"),
758
+ )
759
+ if response == "i":
760
+ create_archive = False
761
+ elif response == "w":
762
+ logger.warning("Deleting %s", display_path(archive_path))
763
+ os.remove(archive_path)
764
+ elif response == "b":
765
+ dest_file = backup_dir(archive_path)
766
+ logger.warning(
767
+ "Backing up %s to %s",
768
+ display_path(archive_path),
769
+ display_path(dest_file),
770
+ )
771
+ shutil.move(archive_path, dest_file)
772
+ elif response == "a":
773
+ sys.exit(-1)
774
+
775
+ if not create_archive:
776
+ return
777
+
778
+ zip_output = zipfile.ZipFile(
779
+ archive_path,
780
+ "w",
781
+ zipfile.ZIP_DEFLATED,
782
+ allowZip64=True,
783
+ )
784
+ with zip_output:
785
+ dir = os.path.normcase(os.path.abspath(self.unpacked_source_directory))
786
+ for dirpath, dirnames, filenames in os.walk(dir):
787
+ for dirname in dirnames:
788
+ dir_arcname = self._get_archive_name(
789
+ dirname,
790
+ parentdir=dirpath,
791
+ rootdir=dir,
792
+ )
793
+ zipdir = zipfile.ZipInfo(dir_arcname + "/")
794
+ zipdir.external_attr = 0x1ED << 16 # 0o755
795
+ zip_output.writestr(zipdir, "")
796
+ for filename in filenames:
797
+ file_arcname = self._get_archive_name(
798
+ filename,
799
+ parentdir=dirpath,
800
+ rootdir=dir,
801
+ )
802
+ filename = os.path.join(dirpath, filename)
803
+ zip_output.write(filename, file_arcname)
804
+
805
+ logger.info("Saved %s", display_path(archive_path))
806
+
807
+ def install(
808
+ self,
809
+ global_options: Optional[Sequence[str]] = None,
810
+ root: Optional[str] = None,
811
+ home: Optional[str] = None,
812
+ prefix: Optional[str] = None,
813
+ warn_script_location: bool = True,
814
+ use_user_site: bool = False,
815
+ pycompile: bool = True,
816
+ ) -> None:
817
+ assert self.req is not None
818
+ scheme = get_scheme(
819
+ self.req.name,
820
+ user=use_user_site,
821
+ home=home,
822
+ root=root,
823
+ isolated=self.isolated,
824
+ prefix=prefix,
825
+ )
826
+
827
+ if self.editable and not self.is_wheel:
828
+ deprecated(
829
+ reason=(
830
+ f"Legacy editable install of {self} (setup.py develop) "
831
+ "is deprecated."
832
+ ),
833
+ replacement=(
834
+ "to add a pyproject.toml or enable --use-pep517, "
835
+ "and use setuptools >= 64. "
836
+ "If the resulting installation is not behaving as expected, "
837
+ "try using --config-settings editable_mode=compat. "
838
+ "Please consult the setuptools documentation for more information"
839
+ ),
840
+ gone_in="25.3",
841
+ issue=11457,
842
+ )
843
+ if self.config_settings:
844
+ logger.warning(
845
+ "--config-settings ignored for legacy editable install of %s. "
846
+ "Consider upgrading to a version of setuptools "
847
+ "that supports PEP 660 (>= 64).",
848
+ self,
849
+ )
850
+ install_editable_legacy(
851
+ global_options=global_options if global_options is not None else [],
852
+ prefix=prefix,
853
+ home=home,
854
+ use_user_site=use_user_site,
855
+ name=self.req.name,
856
+ setup_py_path=self.setup_py_path,
857
+ isolated=self.isolated,
858
+ build_env=self.build_env,
859
+ unpacked_source_directory=self.unpacked_source_directory,
860
+ )
861
+ self.install_succeeded = True
862
+ return
863
+
864
+ assert self.is_wheel
865
+ assert self.local_file_path
866
+
867
+ install_wheel(
868
+ self.req.name,
869
+ self.local_file_path,
870
+ scheme=scheme,
871
+ req_description=str(self.req),
872
+ pycompile=pycompile,
873
+ warn_script_location=warn_script_location,
874
+ direct_url=self.download_info if self.is_direct else None,
875
+ requested=self.user_supplied,
876
+ )
877
+ self.install_succeeded = True
878
+
879
+
880
+ def check_invalid_constraint_type(req: InstallRequirement) -> str:
881
+ # Check for unsupported forms
882
+ problem = ""
883
+ if not req.name:
884
+ problem = "Unnamed requirements are not allowed as constraints"
885
+ elif req.editable:
886
+ problem = "Editable requirements are not allowed as constraints"
887
+ elif req.extras:
888
+ problem = "Constraints cannot have extras"
889
+
890
+ if problem:
891
+ deprecated(
892
+ reason=(
893
+ "Constraints are only allowed to take the form of a package "
894
+ "name and a version specifier. Other forms were originally "
895
+ "permitted as an accident of the implementation, but were "
896
+ "undocumented. The new implementation of the resolver no "
897
+ "longer supports these forms."
898
+ ),
899
+ replacement="replacing the constraint with a requirement",
900
+ # No plan yet for when the new resolver becomes default
901
+ gone_in=None,
902
+ issue=8210,
903
+ )
904
+
905
+ return problem
906
+
907
+
908
+ def _has_option(options: Values, reqs: List[InstallRequirement], option: str) -> bool:
909
+ if getattr(options, option, None):
910
+ return True
911
+ for req in reqs:
912
+ if getattr(req, option, None):
913
+ return True
914
+ return False
915
+
916
+
917
+ def check_legacy_setup_py_options(
918
+ options: Values,
919
+ reqs: List[InstallRequirement],
920
+ ) -> None:
921
+ has_build_options = _has_option(options, reqs, "build_options")
922
+ has_global_options = _has_option(options, reqs, "global_options")
923
+ if has_build_options or has_global_options:
924
+ deprecated(
925
+ reason="--build-option and --global-option are deprecated.",
926
+ issue=11859,
927
+ replacement="to use --config-settings",
928
+ gone_in="25.3",
929
+ )
930
+ logger.warning(
931
+ "Implying --no-binary=:all: due to the presence of "
932
+ "--build-option / --global-option. "
933
+ )
934
+ options.format_control.disallow_binaries()
.mcp-venv/lib/python3.13/site-packages/pip/_internal/req/req_set.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from collections import OrderedDict
3
+ from typing import Dict, List
4
+
5
+ from pip._vendor.packaging.utils import canonicalize_name
6
+
7
+ from pip._internal.req.req_install import InstallRequirement
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class RequirementSet:
13
+ def __init__(self, check_supported_wheels: bool = True) -> None:
14
+ """Create a RequirementSet."""
15
+
16
+ self.requirements: Dict[str, InstallRequirement] = OrderedDict()
17
+ self.check_supported_wheels = check_supported_wheels
18
+
19
+ self.unnamed_requirements: List[InstallRequirement] = []
20
+
21
+ def __str__(self) -> str:
22
+ requirements = sorted(
23
+ (req for req in self.requirements.values() if not req.comes_from),
24
+ key=lambda req: canonicalize_name(req.name or ""),
25
+ )
26
+ return " ".join(str(req.req) for req in requirements)
27
+
28
+ def __repr__(self) -> str:
29
+ requirements = sorted(
30
+ self.requirements.values(),
31
+ key=lambda req: canonicalize_name(req.name or ""),
32
+ )
33
+
34
+ format_string = "<{classname} object; {count} requirement(s): {reqs}>"
35
+ return format_string.format(
36
+ classname=self.__class__.__name__,
37
+ count=len(requirements),
38
+ reqs=", ".join(str(req.req) for req in requirements),
39
+ )
40
+
41
+ def add_unnamed_requirement(self, install_req: InstallRequirement) -> None:
42
+ assert not install_req.name
43
+ self.unnamed_requirements.append(install_req)
44
+
45
+ def add_named_requirement(self, install_req: InstallRequirement) -> None:
46
+ assert install_req.name
47
+
48
+ project_name = canonicalize_name(install_req.name)
49
+ self.requirements[project_name] = install_req
50
+
51
+ def has_requirement(self, name: str) -> bool:
52
+ project_name = canonicalize_name(name)
53
+
54
+ return (
55
+ project_name in self.requirements
56
+ and not self.requirements[project_name].constraint
57
+ )
58
+
59
+ def get_requirement(self, name: str) -> InstallRequirement:
60
+ project_name = canonicalize_name(name)
61
+
62
+ if project_name in self.requirements:
63
+ return self.requirements[project_name]
64
+
65
+ raise KeyError(f"No project with the name {name!r}")
66
+
67
+ @property
68
+ def all_requirements(self) -> List[InstallRequirement]:
69
+ return self.unnamed_requirements + list(self.requirements.values())
70
+
71
+ @property
72
+ def requirements_to_install(self) -> List[InstallRequirement]:
73
+ """Return the list of requirements that need to be installed.
74
+
75
+ TODO remove this property together with the legacy resolver, since the new
76
+ resolver only returns requirements that need to be installed.
77
+ """
78
+ return [
79
+ install_req
80
+ for install_req in self.all_requirements
81
+ if not install_req.constraint and not install_req.satisfied_by
82
+ ]
.mcp-venv/lib/python3.13/site-packages/pip/_internal/req/req_uninstall.py ADDED
@@ -0,0 +1,636 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+ import os
3
+ import sys
4
+ import sysconfig
5
+ from importlib.util import cache_from_source
6
+ from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Set, Tuple
7
+
8
+ from pip._internal.exceptions import LegacyDistutilsInstall, UninstallMissingRecord
9
+ from pip._internal.locations import get_bin_prefix, get_bin_user
10
+ from pip._internal.metadata import BaseDistribution
11
+ from pip._internal.utils.compat import WINDOWS
12
+ from pip._internal.utils.egg_link import egg_link_path_from_location
13
+ from pip._internal.utils.logging import getLogger, indent_log
14
+ from pip._internal.utils.misc import ask, normalize_path, renames, rmtree
15
+ from pip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory
16
+ from pip._internal.utils.virtualenv import running_under_virtualenv
17
+
18
+ logger = getLogger(__name__)
19
+
20
+
21
+ def _script_names(
22
+ bin_dir: str, script_name: str, is_gui: bool
23
+ ) -> Generator[str, None, None]:
24
+ """Create the fully qualified name of the files created by
25
+ {console,gui}_scripts for the given ``dist``.
26
+ Returns the list of file names
27
+ """
28
+ exe_name = os.path.join(bin_dir, script_name)
29
+ yield exe_name
30
+ if not WINDOWS:
31
+ return
32
+ yield f"{exe_name}.exe"
33
+ yield f"{exe_name}.exe.manifest"
34
+ if is_gui:
35
+ yield f"{exe_name}-script.pyw"
36
+ else:
37
+ yield f"{exe_name}-script.py"
38
+
39
+
40
+ def _unique(
41
+ fn: Callable[..., Generator[Any, None, None]],
42
+ ) -> Callable[..., Generator[Any, None, None]]:
43
+ @functools.wraps(fn)
44
+ def unique(*args: Any, **kw: Any) -> Generator[Any, None, None]:
45
+ seen: Set[Any] = set()
46
+ for item in fn(*args, **kw):
47
+ if item not in seen:
48
+ seen.add(item)
49
+ yield item
50
+
51
+ return unique
52
+
53
+
54
+ @_unique
55
+ def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]:
56
+ """
57
+ Yield all the uninstallation paths for dist based on RECORD-without-.py[co]
58
+
59
+ Yield paths to all the files in RECORD. For each .py file in RECORD, add
60
+ the .pyc and .pyo in the same directory.
61
+
62
+ UninstallPathSet.add() takes care of the __pycache__ .py[co].
63
+
64
+ If RECORD is not found, raises an error,
65
+ with possible information from the INSTALLER file.
66
+
67
+ https://packaging.python.org/specifications/recording-installed-packages/
68
+ """
69
+ location = dist.location
70
+ assert location is not None, "not installed"
71
+
72
+ entries = dist.iter_declared_entries()
73
+ if entries is None:
74
+ raise UninstallMissingRecord(distribution=dist)
75
+
76
+ for entry in entries:
77
+ path = os.path.join(location, entry)
78
+ yield path
79
+ if path.endswith(".py"):
80
+ dn, fn = os.path.split(path)
81
+ base = fn[:-3]
82
+ path = os.path.join(dn, base + ".pyc")
83
+ yield path
84
+ path = os.path.join(dn, base + ".pyo")
85
+ yield path
86
+
87
+
88
+ def compact(paths: Iterable[str]) -> Set[str]:
89
+ """Compact a path set to contain the minimal number of paths
90
+ necessary to contain all paths in the set. If /a/path/ and
91
+ /a/path/to/a/file.txt are both in the set, leave only the
92
+ shorter path."""
93
+
94
+ sep = os.path.sep
95
+ short_paths: Set[str] = set()
96
+ for path in sorted(paths, key=len):
97
+ should_skip = any(
98
+ path.startswith(shortpath.rstrip("*"))
99
+ and path[len(shortpath.rstrip("*").rstrip(sep))] == sep
100
+ for shortpath in short_paths
101
+ )
102
+ if not should_skip:
103
+ short_paths.add(path)
104
+ return short_paths
105
+
106
+
107
+ def compress_for_rename(paths: Iterable[str]) -> Set[str]:
108
+ """Returns a set containing the paths that need to be renamed.
109
+
110
+ This set may include directories when the original sequence of paths
111
+ included every file on disk.
112
+ """
113
+ case_map = {os.path.normcase(p): p for p in paths}
114
+ remaining = set(case_map)
115
+ unchecked = sorted({os.path.split(p)[0] for p in case_map.values()}, key=len)
116
+ wildcards: Set[str] = set()
117
+
118
+ def norm_join(*a: str) -> str:
119
+ return os.path.normcase(os.path.join(*a))
120
+
121
+ for root in unchecked:
122
+ if any(os.path.normcase(root).startswith(w) for w in wildcards):
123
+ # This directory has already been handled.
124
+ continue
125
+
126
+ all_files: Set[str] = set()
127
+ all_subdirs: Set[str] = set()
128
+ for dirname, subdirs, files in os.walk(root):
129
+ all_subdirs.update(norm_join(root, dirname, d) for d in subdirs)
130
+ all_files.update(norm_join(root, dirname, f) for f in files)
131
+ # If all the files we found are in our remaining set of files to
132
+ # remove, then remove them from the latter set and add a wildcard
133
+ # for the directory.
134
+ if not (all_files - remaining):
135
+ remaining.difference_update(all_files)
136
+ wildcards.add(root + os.sep)
137
+
138
+ return set(map(case_map.__getitem__, remaining)) | wildcards
139
+
140
+
141
+ def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str]]:
142
+ """Returns a tuple of 2 sets of which paths to display to user
143
+
144
+ The first set contains paths that would be deleted. Files of a package
145
+ are not added and the top-level directory of the package has a '*' added
146
+ at the end - to signify that all it's contents are removed.
147
+
148
+ The second set contains files that would have been skipped in the above
149
+ folders.
150
+ """
151
+
152
+ will_remove = set(paths)
153
+ will_skip = set()
154
+
155
+ # Determine folders and files
156
+ folders = set()
157
+ files = set()
158
+ for path in will_remove:
159
+ if path.endswith(".pyc"):
160
+ continue
161
+ if path.endswith("__init__.py") or ".dist-info" in path:
162
+ folders.add(os.path.dirname(path))
163
+ files.add(path)
164
+
165
+ _normcased_files = set(map(os.path.normcase, files))
166
+
167
+ folders = compact(folders)
168
+
169
+ # This walks the tree using os.walk to not miss extra folders
170
+ # that might get added.
171
+ for folder in folders:
172
+ for dirpath, _, dirfiles in os.walk(folder):
173
+ for fname in dirfiles:
174
+ if fname.endswith(".pyc"):
175
+ continue
176
+
177
+ file_ = os.path.join(dirpath, fname)
178
+ if (
179
+ os.path.isfile(file_)
180
+ and os.path.normcase(file_) not in _normcased_files
181
+ ):
182
+ # We are skipping this file. Add it to the set.
183
+ will_skip.add(file_)
184
+
185
+ will_remove = files | {os.path.join(folder, "*") for folder in folders}
186
+
187
+ return will_remove, will_skip
188
+
189
+
190
+ class StashedUninstallPathSet:
191
+ """A set of file rename operations to stash files while
192
+ tentatively uninstalling them."""
193
+
194
+ def __init__(self) -> None:
195
+ # Mapping from source file root to [Adjacent]TempDirectory
196
+ # for files under that directory.
197
+ self._save_dirs: Dict[str, TempDirectory] = {}
198
+ # (old path, new path) tuples for each move that may need
199
+ # to be undone.
200
+ self._moves: List[Tuple[str, str]] = []
201
+
202
+ def _get_directory_stash(self, path: str) -> str:
203
+ """Stashes a directory.
204
+
205
+ Directories are stashed adjacent to their original location if
206
+ possible, or else moved/copied into the user's temp dir."""
207
+
208
+ try:
209
+ save_dir: TempDirectory = AdjacentTempDirectory(path)
210
+ except OSError:
211
+ save_dir = TempDirectory(kind="uninstall")
212
+ self._save_dirs[os.path.normcase(path)] = save_dir
213
+
214
+ return save_dir.path
215
+
216
+ def _get_file_stash(self, path: str) -> str:
217
+ """Stashes a file.
218
+
219
+ If no root has been provided, one will be created for the directory
220
+ in the user's temp directory."""
221
+ path = os.path.normcase(path)
222
+ head, old_head = os.path.dirname(path), None
223
+ save_dir = None
224
+
225
+ while head != old_head:
226
+ try:
227
+ save_dir = self._save_dirs[head]
228
+ break
229
+ except KeyError:
230
+ pass
231
+ head, old_head = os.path.dirname(head), head
232
+ else:
233
+ # Did not find any suitable root
234
+ head = os.path.dirname(path)
235
+ save_dir = TempDirectory(kind="uninstall")
236
+ self._save_dirs[head] = save_dir
237
+
238
+ relpath = os.path.relpath(path, head)
239
+ if relpath and relpath != os.path.curdir:
240
+ return os.path.join(save_dir.path, relpath)
241
+ return save_dir.path
242
+
243
+ def stash(self, path: str) -> str:
244
+ """Stashes the directory or file and returns its new location.
245
+ Handle symlinks as files to avoid modifying the symlink targets.
246
+ """
247
+ path_is_dir = os.path.isdir(path) and not os.path.islink(path)
248
+ if path_is_dir:
249
+ new_path = self._get_directory_stash(path)
250
+ else:
251
+ new_path = self._get_file_stash(path)
252
+
253
+ self._moves.append((path, new_path))
254
+ if path_is_dir and os.path.isdir(new_path):
255
+ # If we're moving a directory, we need to
256
+ # remove the destination first or else it will be
257
+ # moved to inside the existing directory.
258
+ # We just created new_path ourselves, so it will
259
+ # be removable.
260
+ os.rmdir(new_path)
261
+ renames(path, new_path)
262
+ return new_path
263
+
264
+ def commit(self) -> None:
265
+ """Commits the uninstall by removing stashed files."""
266
+ for save_dir in self._save_dirs.values():
267
+ save_dir.cleanup()
268
+ self._moves = []
269
+ self._save_dirs = {}
270
+
271
+ def rollback(self) -> None:
272
+ """Undoes the uninstall by moving stashed files back."""
273
+ for p in self._moves:
274
+ logger.info("Moving to %s\n from %s", *p)
275
+
276
+ for new_path, path in self._moves:
277
+ try:
278
+ logger.debug("Replacing %s from %s", new_path, path)
279
+ if os.path.isfile(new_path) or os.path.islink(new_path):
280
+ os.unlink(new_path)
281
+ elif os.path.isdir(new_path):
282
+ rmtree(new_path)
283
+ renames(path, new_path)
284
+ except OSError as ex:
285
+ logger.error("Failed to restore %s", new_path)
286
+ logger.debug("Exception: %s", ex)
287
+
288
+ self.commit()
289
+
290
+ @property
291
+ def can_rollback(self) -> bool:
292
+ return bool(self._moves)
293
+
294
+
295
+ class UninstallPathSet:
296
+ """A set of file paths to be removed in the uninstallation of a
297
+ requirement."""
298
+
299
+ def __init__(self, dist: BaseDistribution) -> None:
300
+ self._paths: Set[str] = set()
301
+ self._refuse: Set[str] = set()
302
+ self._pth: Dict[str, UninstallPthEntries] = {}
303
+ self._dist = dist
304
+ self._moved_paths = StashedUninstallPathSet()
305
+ # Create local cache of normalize_path results. Creating an UninstallPathSet
306
+ # can result in hundreds/thousands of redundant calls to normalize_path with
307
+ # the same args, which hurts performance.
308
+ self._normalize_path_cached = functools.lru_cache(normalize_path)
309
+
310
+ def _permitted(self, path: str) -> bool:
311
+ """
312
+ Return True if the given path is one we are permitted to
313
+ remove/modify, False otherwise.
314
+
315
+ """
316
+ # aka is_local, but caching normalized sys.prefix
317
+ if not running_under_virtualenv():
318
+ return True
319
+ return path.startswith(self._normalize_path_cached(sys.prefix))
320
+
321
+ def add(self, path: str) -> None:
322
+ head, tail = os.path.split(path)
323
+
324
+ # we normalize the head to resolve parent directory symlinks, but not
325
+ # the tail, since we only want to uninstall symlinks, not their targets
326
+ path = os.path.join(self._normalize_path_cached(head), os.path.normcase(tail))
327
+
328
+ if not os.path.exists(path):
329
+ return
330
+ if self._permitted(path):
331
+ self._paths.add(path)
332
+ else:
333
+ self._refuse.add(path)
334
+
335
+ # __pycache__ files can show up after 'installed-files.txt' is created,
336
+ # due to imports
337
+ if os.path.splitext(path)[1] == ".py":
338
+ self.add(cache_from_source(path))
339
+
340
+ def add_pth(self, pth_file: str, entry: str) -> None:
341
+ pth_file = self._normalize_path_cached(pth_file)
342
+ if self._permitted(pth_file):
343
+ if pth_file not in self._pth:
344
+ self._pth[pth_file] = UninstallPthEntries(pth_file)
345
+ self._pth[pth_file].add(entry)
346
+ else:
347
+ self._refuse.add(pth_file)
348
+
349
+ def remove(self, auto_confirm: bool = False, verbose: bool = False) -> None:
350
+ """Remove paths in ``self._paths`` with confirmation (unless
351
+ ``auto_confirm`` is True)."""
352
+
353
+ if not self._paths:
354
+ logger.info(
355
+ "Can't uninstall '%s'. No files were found to uninstall.",
356
+ self._dist.raw_name,
357
+ )
358
+ return
359
+
360
+ dist_name_version = f"{self._dist.raw_name}-{self._dist.raw_version}"
361
+ logger.info("Uninstalling %s:", dist_name_version)
362
+
363
+ with indent_log():
364
+ if auto_confirm or self._allowed_to_proceed(verbose):
365
+ moved = self._moved_paths
366
+
367
+ for_rename = compress_for_rename(self._paths)
368
+
369
+ for path in sorted(compact(for_rename)):
370
+ moved.stash(path)
371
+ logger.verbose("Removing file or directory %s", path)
372
+
373
+ for pth in self._pth.values():
374
+ pth.remove()
375
+
376
+ logger.info("Successfully uninstalled %s", dist_name_version)
377
+
378
+ def _allowed_to_proceed(self, verbose: bool) -> bool:
379
+ """Display which files would be deleted and prompt for confirmation"""
380
+
381
+ def _display(msg: str, paths: Iterable[str]) -> None:
382
+ if not paths:
383
+ return
384
+
385
+ logger.info(msg)
386
+ with indent_log():
387
+ for path in sorted(compact(paths)):
388
+ logger.info(path)
389
+
390
+ if not verbose:
391
+ will_remove, will_skip = compress_for_output_listing(self._paths)
392
+ else:
393
+ # In verbose mode, display all the files that are going to be
394
+ # deleted.
395
+ will_remove = set(self._paths)
396
+ will_skip = set()
397
+
398
+ _display("Would remove:", will_remove)
399
+ _display("Would not remove (might be manually added):", will_skip)
400
+ _display("Would not remove (outside of prefix):", self._refuse)
401
+ if verbose:
402
+ _display("Will actually move:", compress_for_rename(self._paths))
403
+
404
+ return ask("Proceed (Y/n)? ", ("y", "n", "")) != "n"
405
+
406
+ def rollback(self) -> None:
407
+ """Rollback the changes previously made by remove()."""
408
+ if not self._moved_paths.can_rollback:
409
+ logger.error(
410
+ "Can't roll back %s; was not uninstalled",
411
+ self._dist.raw_name,
412
+ )
413
+ return
414
+ logger.info("Rolling back uninstall of %s", self._dist.raw_name)
415
+ self._moved_paths.rollback()
416
+ for pth in self._pth.values():
417
+ pth.rollback()
418
+
419
+ def commit(self) -> None:
420
+ """Remove temporary save dir: rollback will no longer be possible."""
421
+ self._moved_paths.commit()
422
+
423
+ @classmethod
424
+ def from_dist(cls, dist: BaseDistribution) -> "UninstallPathSet":
425
+ dist_location = dist.location
426
+ info_location = dist.info_location
427
+ if dist_location is None:
428
+ logger.info(
429
+ "Not uninstalling %s since it is not installed",
430
+ dist.canonical_name,
431
+ )
432
+ return cls(dist)
433
+
434
+ normalized_dist_location = normalize_path(dist_location)
435
+ if not dist.local:
436
+ logger.info(
437
+ "Not uninstalling %s at %s, outside environment %s",
438
+ dist.canonical_name,
439
+ normalized_dist_location,
440
+ sys.prefix,
441
+ )
442
+ return cls(dist)
443
+
444
+ if normalized_dist_location in {
445
+ p
446
+ for p in {sysconfig.get_path("stdlib"), sysconfig.get_path("platstdlib")}
447
+ if p
448
+ }:
449
+ logger.info(
450
+ "Not uninstalling %s at %s, as it is in the standard library.",
451
+ dist.canonical_name,
452
+ normalized_dist_location,
453
+ )
454
+ return cls(dist)
455
+
456
+ paths_to_remove = cls(dist)
457
+ develop_egg_link = egg_link_path_from_location(dist.raw_name)
458
+
459
+ # Distribution is installed with metadata in a "flat" .egg-info
460
+ # directory. This means it is not a modern .dist-info installation, an
461
+ # egg, or legacy editable.
462
+ setuptools_flat_installation = (
463
+ dist.installed_with_setuptools_egg_info
464
+ and info_location is not None
465
+ and os.path.exists(info_location)
466
+ # If dist is editable and the location points to a ``.egg-info``,
467
+ # we are in fact in the legacy editable case.
468
+ and not info_location.endswith(f"{dist.setuptools_filename}.egg-info")
469
+ )
470
+
471
+ # Uninstall cases order do matter as in the case of 2 installs of the
472
+ # same package, pip needs to uninstall the currently detected version
473
+ if setuptools_flat_installation:
474
+ if info_location is not None:
475
+ paths_to_remove.add(info_location)
476
+ installed_files = dist.iter_declared_entries()
477
+ if installed_files is not None:
478
+ for installed_file in installed_files:
479
+ paths_to_remove.add(os.path.join(dist_location, installed_file))
480
+ # FIXME: need a test for this elif block
481
+ # occurs with --single-version-externally-managed/--record outside
482
+ # of pip
483
+ elif dist.is_file("top_level.txt"):
484
+ try:
485
+ namespace_packages = dist.read_text("namespace_packages.txt")
486
+ except FileNotFoundError:
487
+ namespaces = []
488
+ else:
489
+ namespaces = namespace_packages.splitlines(keepends=False)
490
+ for top_level_pkg in [
491
+ p
492
+ for p in dist.read_text("top_level.txt").splitlines()
493
+ if p and p not in namespaces
494
+ ]:
495
+ path = os.path.join(dist_location, top_level_pkg)
496
+ paths_to_remove.add(path)
497
+ paths_to_remove.add(f"{path}.py")
498
+ paths_to_remove.add(f"{path}.pyc")
499
+ paths_to_remove.add(f"{path}.pyo")
500
+
501
+ elif dist.installed_by_distutils:
502
+ raise LegacyDistutilsInstall(distribution=dist)
503
+
504
+ elif dist.installed_as_egg:
505
+ # package installed by easy_install
506
+ # We cannot match on dist.egg_name because it can slightly vary
507
+ # i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
508
+ # XXX We use normalized_dist_location because dist_location my contain
509
+ # a trailing / if the distribution is a zipped egg
510
+ # (which is not a directory).
511
+ paths_to_remove.add(normalized_dist_location)
512
+ easy_install_egg = os.path.split(normalized_dist_location)[1]
513
+ easy_install_pth = os.path.join(
514
+ os.path.dirname(normalized_dist_location),
515
+ "easy-install.pth",
516
+ )
517
+ paths_to_remove.add_pth(easy_install_pth, "./" + easy_install_egg)
518
+
519
+ elif dist.installed_with_dist_info:
520
+ for path in uninstallation_paths(dist):
521
+ paths_to_remove.add(path)
522
+
523
+ elif develop_egg_link:
524
+ # PEP 660 modern editable is handled in the ``.dist-info`` case
525
+ # above, so this only covers the setuptools-style editable.
526
+ with open(develop_egg_link) as fh:
527
+ link_pointer = os.path.normcase(fh.readline().strip())
528
+ normalized_link_pointer = paths_to_remove._normalize_path_cached(
529
+ link_pointer
530
+ )
531
+ assert os.path.samefile(
532
+ normalized_link_pointer, normalized_dist_location
533
+ ), (
534
+ f"Egg-link {develop_egg_link} (to {link_pointer}) does not match "
535
+ f"installed location of {dist.raw_name} (at {dist_location})"
536
+ )
537
+ paths_to_remove.add(develop_egg_link)
538
+ easy_install_pth = os.path.join(
539
+ os.path.dirname(develop_egg_link), "easy-install.pth"
540
+ )
541
+ paths_to_remove.add_pth(easy_install_pth, dist_location)
542
+
543
+ else:
544
+ logger.debug(
545
+ "Not sure how to uninstall: %s - Check: %s",
546
+ dist,
547
+ dist_location,
548
+ )
549
+
550
+ if dist.in_usersite:
551
+ bin_dir = get_bin_user()
552
+ else:
553
+ bin_dir = get_bin_prefix()
554
+
555
+ # find distutils scripts= scripts
556
+ try:
557
+ for script in dist.iter_distutils_script_names():
558
+ paths_to_remove.add(os.path.join(bin_dir, script))
559
+ if WINDOWS:
560
+ paths_to_remove.add(os.path.join(bin_dir, f"{script}.bat"))
561
+ except (FileNotFoundError, NotADirectoryError):
562
+ pass
563
+
564
+ # find console_scripts and gui_scripts
565
+ def iter_scripts_to_remove(
566
+ dist: BaseDistribution,
567
+ bin_dir: str,
568
+ ) -> Generator[str, None, None]:
569
+ for entry_point in dist.iter_entry_points():
570
+ if entry_point.group == "console_scripts":
571
+ yield from _script_names(bin_dir, entry_point.name, False)
572
+ elif entry_point.group == "gui_scripts":
573
+ yield from _script_names(bin_dir, entry_point.name, True)
574
+
575
+ for s in iter_scripts_to_remove(dist, bin_dir):
576
+ paths_to_remove.add(s)
577
+
578
+ return paths_to_remove
579
+
580
+
581
+ class UninstallPthEntries:
582
+ def __init__(self, pth_file: str) -> None:
583
+ self.file = pth_file
584
+ self.entries: Set[str] = set()
585
+ self._saved_lines: Optional[List[bytes]] = None
586
+
587
+ def add(self, entry: str) -> None:
588
+ entry = os.path.normcase(entry)
589
+ # On Windows, os.path.normcase converts the entry to use
590
+ # backslashes. This is correct for entries that describe absolute
591
+ # paths outside of site-packages, but all the others use forward
592
+ # slashes.
593
+ # os.path.splitdrive is used instead of os.path.isabs because isabs
594
+ # treats non-absolute paths with drive letter markings like c:foo\bar
595
+ # as absolute paths. It also does not recognize UNC paths if they don't
596
+ # have more than "\\sever\share". Valid examples: "\\server\share\" or
597
+ # "\\server\share\folder".
598
+ if WINDOWS and not os.path.splitdrive(entry)[0]:
599
+ entry = entry.replace("\\", "/")
600
+ self.entries.add(entry)
601
+
602
+ def remove(self) -> None:
603
+ logger.verbose("Removing pth entries from %s:", self.file)
604
+
605
+ # If the file doesn't exist, log a warning and return
606
+ if not os.path.isfile(self.file):
607
+ logger.warning("Cannot remove entries from nonexistent file %s", self.file)
608
+ return
609
+ with open(self.file, "rb") as fh:
610
+ # windows uses '\r\n' with py3k, but uses '\n' with py2.x
611
+ lines = fh.readlines()
612
+ self._saved_lines = lines
613
+ if any(b"\r\n" in line for line in lines):
614
+ endline = "\r\n"
615
+ else:
616
+ endline = "\n"
617
+ # handle missing trailing newline
618
+ if lines and not lines[-1].endswith(endline.encode("utf-8")):
619
+ lines[-1] = lines[-1] + endline.encode("utf-8")
620
+ for entry in self.entries:
621
+ try:
622
+ logger.verbose("Removing entry: %s", entry)
623
+ lines.remove((entry + endline).encode("utf-8"))
624
+ except ValueError:
625
+ pass
626
+ with open(self.file, "wb") as fh:
627
+ fh.writelines(lines)
628
+
629
+ def rollback(self) -> bool:
630
+ if self._saved_lines is None:
631
+ logger.error("Cannot roll back changes to %s, none were made", self.file)
632
+ return False
633
+ logger.debug("Rolling %s back to previous state", self.file)
634
+ with open(self.file, "wb") as fh:
635
+ fh.writelines(self._saved_lines)
636
+ return True
.mcp-venv/lib/python3.13/site-packages/pip/_internal/resolution/__init__.py ADDED
File without changes
.mcp-venv/lib/python3.13/site-packages/pip/_internal/resolution/base.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Callable, List, Optional
2
+
3
+ from pip._internal.req.req_install import InstallRequirement
4
+ from pip._internal.req.req_set import RequirementSet
5
+
6
+ InstallRequirementProvider = Callable[
7
+ [str, Optional[InstallRequirement]], InstallRequirement
8
+ ]
9
+
10
+
11
+ class BaseResolver:
12
+ def resolve(
13
+ self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
14
+ ) -> RequirementSet:
15
+ raise NotImplementedError()
16
+
17
+ def get_installation_order(
18
+ self, req_set: RequirementSet
19
+ ) -> List[InstallRequirement]:
20
+ raise NotImplementedError()