virtualenv 21.2.2__tar.gz → 21.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. {virtualenv-21.2.2 → virtualenv-21.2.4}/PKG-INFO +1 -1
  2. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/pip_install/base.py +29 -1
  3. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/acquire.py +63 -0
  4. virtualenv-21.2.4/src/virtualenv/seed/wheels/embed/__init__.py +119 -0
  5. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/periodic_update.py +12 -2
  6. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/zipapp.py +14 -16
  7. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/version.py +2 -2
  8. virtualenv-21.2.4/tasks/upgrade_wheels.py +265 -0
  9. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/embed/test_bootstrap_link_via_app_data.py +51 -0
  10. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/wheels/test_acquire.py +33 -0
  11. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/wheels/test_bundle.py +64 -1
  12. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/wheels/test_periodic_update.py +17 -4
  13. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/test_util.py +28 -0
  14. virtualenv-21.2.2/src/virtualenv/seed/wheels/embed/__init__.py +0 -60
  15. virtualenv-21.2.2/tasks/upgrade_wheels.py +0 -157
  16. {virtualenv-21.2.2 → virtualenv-21.2.4}/.gitignore +0 -0
  17. {virtualenv-21.2.2 → virtualenv-21.2.4}/LICENSE +0 -0
  18. {virtualenv-21.2.2 → virtualenv-21.2.4}/README.md +0 -0
  19. {virtualenv-21.2.2 → virtualenv-21.2.4}/pyproject.toml +0 -0
  20. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/__init__.py +0 -0
  21. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/__main__.py +0 -0
  22. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/__init__.py +0 -0
  23. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/activator.py +0 -0
  24. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/bash/__init__.py +0 -0
  25. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/bash/activate.sh +0 -0
  26. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/batch/__init__.py +0 -0
  27. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/batch/activate.bat +0 -0
  28. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/batch/deactivate.bat +0 -0
  29. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/batch/pydoc.bat +0 -0
  30. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/cshell/__init__.py +0 -0
  31. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/cshell/activate.csh +0 -0
  32. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/fish/__init__.py +0 -0
  33. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/fish/activate.fish +0 -0
  34. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/nushell/__init__.py +0 -0
  35. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/nushell/activate.nu +0 -0
  36. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/powershell/__init__.py +0 -0
  37. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/powershell/activate.ps1 +0 -0
  38. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/python/__init__.py +0 -0
  39. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/python/activate_this.py +0 -0
  40. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/activation/via_template.py +0 -0
  41. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/__init__.py +0 -0
  42. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/base.py +0 -0
  43. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/na.py +0 -0
  44. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/read_only.py +0 -0
  45. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/via_disk_folder.py +0 -0
  46. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/app_data/via_tempdir.py +0 -0
  47. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/__init__.py +0 -0
  48. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/cli/__init__.py +0 -0
  49. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/cli/parser.py +0 -0
  50. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/convert.py +0 -0
  51. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/env_var.py +0 -0
  52. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/config/ini.py +0 -0
  53. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/__init__.py +0 -0
  54. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/creator.py +0 -0
  55. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/debug.py +0 -0
  56. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/describe.py +0 -0
  57. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/pyenv_cfg.py +0 -0
  58. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/__init__.py +0 -0
  59. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/_virtualenv.py +0 -0
  60. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/api.py +0 -0
  61. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/__init__.py +0 -0
  62. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/builtin_way.py +0 -0
  63. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/cpython/__init__.py +0 -0
  64. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/cpython/common.py +0 -0
  65. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py +0 -0
  66. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/cpython/mac_os.py +0 -0
  67. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/graalpy/__init__.py +0 -0
  68. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/pypy/__init__.py +0 -0
  69. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/pypy/common.py +0 -0
  70. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/pypy/pypy3.py +0 -0
  71. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/ref.py +0 -0
  72. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/rustpython/__init__.py +0 -0
  73. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/builtin/via_global_self_do.py +0 -0
  74. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/store.py +0 -0
  75. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/create/via_global_ref/venv.py +0 -0
  76. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/__init__.py +0 -0
  77. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/builtin.py +0 -0
  78. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/cached_py_info.py +0 -0
  79. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/discover.py +0 -0
  80. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/py_info.py +0 -0
  81. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/discovery/py_spec.py +0 -0
  82. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/info.py +0 -0
  83. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/py.typed +0 -0
  84. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/report.py +0 -0
  85. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/__init__.py +0 -0
  86. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/__init__.py +0 -0
  87. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/activators.py +0 -0
  88. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/base.py +0 -0
  89. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/creators.py +0 -0
  90. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/discovery.py +0 -0
  91. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/plugin/seeders.py +0 -0
  92. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/run/session.py +0 -0
  93. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/__init__.py +0 -0
  94. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/__init__.py +0 -0
  95. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/base_embed.py +0 -0
  96. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/pip_invoke.py +0 -0
  97. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/__init__.py +0 -0
  98. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/pip_install/__init__.py +0 -0
  99. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/pip_install/copy.py +0 -0
  100. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/pip_install/symlink.py +0 -0
  101. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/embed/via_app_data/via_app_data.py +0 -0
  102. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/seeder.py +0 -0
  103. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/__init__.py +0 -0
  104. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/bundle.py +0 -0
  105. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/embed/pip-25.0.1-py3-none-any.whl +0 -0
  106. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/embed/pip-26.0.1-py3-none-any.whl +0 -0
  107. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/embed/setuptools-75.3.4-py3-none-any.whl +0 -0
  108. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/embed/setuptools-82.0.1-py3-none-any.whl +0 -0
  109. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/embed/wheel-0.45.1-py3-none-any.whl +0 -0
  110. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/seed/wheels/util.py +0 -0
  111. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/__init__.py +0 -0
  112. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/error.py +0 -0
  113. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/lock.py +0 -0
  114. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/path/__init__.py +0 -0
  115. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/path/_permission.py +0 -0
  116. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/path/_sync.py +0 -0
  117. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/path/_win.py +0 -0
  118. {virtualenv-21.2.2 → virtualenv-21.2.4}/src/virtualenv/util/subprocess/__init__.py +0 -0
  119. {virtualenv-21.2.2 → virtualenv-21.2.4}/tasks/__main__zipapp.py +0 -0
  120. {virtualenv-21.2.2 → virtualenv-21.2.4}/tasks/make_zipapp.py +0 -0
  121. {virtualenv-21.2.2 → virtualenv-21.2.4}/tasks/release.py +0 -0
  122. {virtualenv-21.2.2 → virtualenv-21.2.4}/tasks/update_embedded.py +0 -0
  123. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/conftest.py +0 -0
  124. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/integration/test_cachedir_tag.py +0 -0
  125. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/integration/test_race_condition_simulation.py +0 -0
  126. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/integration/test_run_int.py +0 -0
  127. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/integration/test_zipapp.py +0 -0
  128. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/types.py +0 -0
  129. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/conftest.py +0 -0
  130. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_activation_support.py +0 -0
  131. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_activator.py +0 -0
  132. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_bash.py +0 -0
  133. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_batch.py +0 -0
  134. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_csh.py +0 -0
  135. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_fish.py +0 -0
  136. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_nushell.py +0 -0
  137. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_powershell.py +0 -0
  138. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/activation/test_python_activator.py +0 -0
  139. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/config/cli/test_help_formatter.py +0 -0
  140. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/config/cli/test_parser.py +0 -0
  141. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/config/test___main__.py +0 -0
  142. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/config/test_env_var.py +0 -0
  143. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/config/test_ini.py +0 -0
  144. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/conftest.py +0 -0
  145. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/console_app/demo/__init__.py +0 -0
  146. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/console_app/demo/__main__.py +0 -0
  147. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/console_app/setup.cfg +0 -0
  148. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/console_app/setup.py +0 -0
  149. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/test_creator.py +0 -0
  150. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/test_interpreters.py +0 -0
  151. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/_test_race_condition_helper.py +0 -0
  152. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/conftest.py +0 -0
  153. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/cpython/conftest.py +0 -0
  154. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_embed.json +0 -0
  155. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_free_threaded.json +0 -0
  156. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_posix.py +0 -0
  157. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_win.py +0 -0
  158. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/pypy/deb_pypy37.json +0 -0
  159. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/pypy/deb_pypy38.json +0 -0
  160. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/pypy/portable_pypy38.json +0 -0
  161. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/pypy/test_pypy3.py +0 -0
  162. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/rustpython/rustpython_posix.json +0 -0
  163. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/rustpython/rustpython_windows.json +0 -0
  164. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/rustpython/test_rustpython.py +0 -0
  165. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/testing/__init__.py +0 -0
  166. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/testing/helpers.py +0 -0
  167. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/testing/path.py +0 -0
  168. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/builtin/testing/py_info.py +0 -0
  169. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/greet/greet2.c +0 -0
  170. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/greet/greet3.c +0 -0
  171. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/greet/setup.py +0 -0
  172. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/test_api.py +0 -0
  173. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/test_build_c_ext.py +0 -0
  174. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/create/via_global_ref/test_race_condition.py +0 -0
  175. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/discovery/test_discovery.py +0 -0
  176. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/embed/test_base_embed.py +0 -0
  177. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/embed/test_pip_invoke.py +0 -0
  178. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/wheels/test_acquire_find_wheel.py +0 -0
  179. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/seed/wheels/test_wheels_util.py +0 -0
  180. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/test_file_limit.py +0 -0
  181. {virtualenv-21.2.2 → virtualenv-21.2.4}/tests/unit/test_run.py +0 -0
  182. {virtualenv-21.2.2 → virtualenv-21.2.4}/tox.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtualenv
3
- Version: 21.2.2
3
+ Version: 21.2.4
4
4
  Summary: Virtual Python Environment builder
5
5
  Project-URL: Documentation, https://virtualenv.pypa.io
6
6
  Project-URL: Homepage, https://github.com/pypa/virtualenv
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import ntpath
4
5
  import os
6
+ import posixpath
5
7
  import re
6
8
  import zipfile
7
9
  from abc import ABC, abstractmethod
@@ -21,6 +23,24 @@ if TYPE_CHECKING:
21
23
  LOGGER = logging.getLogger(__name__)
22
24
 
23
25
 
26
+ def _safe_extract_zip(zip_ref: zipfile.ZipFile, target_dir: Path) -> None:
27
+ # Guard against zip slip: a wheel is a zip and a tampered entry name (absolute path or one containing ``..``)
28
+ # could escape ``target_dir``.
29
+ base = target_dir.resolve()
30
+ for info in zip_ref.infolist():
31
+ name = info.filename
32
+ if name.startswith(("/", "\\")) or ntpath.isabs(name) or posixpath.isabs(name):
33
+ msg = f"refusing to extract absolute path entry from wheel: {name!r}"
34
+ raise RuntimeError(msg)
35
+ candidate = (base / name).resolve()
36
+ try:
37
+ candidate.relative_to(base)
38
+ except ValueError as exc:
39
+ msg = f"refusing to extract entry escaping target directory: {name!r}"
40
+ raise RuntimeError(msg) from exc
41
+ zip_ref.extractall(str(target_dir))
42
+
43
+
24
44
  class PipInstall(ABC):
25
45
  def __init__(self, wheel: Path, creator: Creator, image_folder: Path) -> None:
26
46
  self._wheel = wheel
@@ -49,11 +69,19 @@ class PipInstall(ABC):
49
69
  LOGGER.debug("generated console scripts %s", " ".join(i.name for i in consoles))
50
70
 
51
71
  def build_image(self) -> None:
72
+ """Extract the seed wheel into the image directory and fix up its RECORD file.
73
+
74
+ Each archive entry is validated before extraction so a tampered wheel cannot escape the image directory via an
75
+ absolute path or ``..`` traversal.
76
+
77
+ :raises RuntimeError: if the wheel contains an entry that would land outside the image directory.
78
+
79
+ """
52
80
  # 1. first extract the wheel
53
81
  LOGGER.debug("build install image for %s to %s", self._wheel.name, self._image_dir)
54
82
  with zipfile.ZipFile(str(self._wheel)) as zip_ref:
55
83
  self._shorten_path_if_needed(zip_ref)
56
- zip_ref.extractall(str(self._image_dir))
84
+ _safe_extract_zip(zip_ref, self._image_dir)
57
85
  self._extracted = True
58
86
  # 2. now add additional files not present in the distribution
59
87
  new_files = self._generate_new_files()
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
+ import re
6
7
  import sys
7
8
  from operator import eq, lt
8
9
  from pathlib import Path
@@ -18,6 +19,34 @@ if TYPE_CHECKING:
18
19
 
19
20
  LOGGER = logging.getLogger(__name__)
20
21
 
22
+ # PEP 503 normalized distribution name. Anything outside this character set on the way to ``pip download`` means
23
+ # somebody is smuggling pip options or extras, so reject it before we build the command line.
24
+ _DISTRIBUTION_RE = re.compile(
25
+ r"""
26
+ ^
27
+ (?P<name>
28
+ [A-Za-z0-9] # must start with an alnum
29
+ (?:[A-Za-z0-9._-]* # inner chars: alnum plus . _ -
30
+ [A-Za-z0-9])? # must also end with an alnum (unless length is 1)
31
+ )
32
+ $
33
+ """,
34
+ re.VERBOSE,
35
+ )
36
+
37
+ # Version specifier that matches what ``Version.as_version_spec`` emits: either empty, ``==<ver>`` or ``<<ver>`` where
38
+ # ``<ver>`` is a subset of PEP 440 public versions. Kept deliberately strict so a crafted version cannot inject pip
39
+ # flags.
40
+ _VERSION_SPEC_RE = re.compile(
41
+ r"""
42
+ ^
43
+ (?P<operator>==|<) # only the operators Version.as_version_spec can emit
44
+ (?P<version>[A-Za-z0-9._+!-]+) # PEP 440 public-version character set, no whitespace
45
+ $
46
+ """,
47
+ re.VERBOSE,
48
+ )
49
+
21
50
 
22
51
  def get_wheel( # noqa: PLR0913
23
52
  distribution: str,
@@ -63,6 +92,26 @@ def download_wheel( # noqa: PLR0913
63
92
  to_folder: Path,
64
93
  env: dict[str, str],
65
94
  ) -> Wheel:
95
+ """Invoke ``pip download`` in a subprocess to fetch a seed wheel.
96
+
97
+ :param distribution: PEP 503 normalized project name; rejected if it contains anything other than
98
+ ``[A-Za-z0-9._-]``.
99
+ :param version_spec: optional version specifier of the form ``==<ver>`` or ``<<ver>`` as emitted by
100
+ :func:`Version.as_version_spec`, or ``None``/empty for the latest compatible release.
101
+ :param for_py_version: major.minor Python version to pass through to ``pip --python-version``.
102
+ :param search_dirs: additional directories to treat as a local wheel index when bootstrapping pip.
103
+ :param app_data: application data store used to locate the embedded pip wheel.
104
+ :param to_folder: directory the downloaded wheel is written into.
105
+ :param env: environment mapping passed through to the subprocess.
106
+
107
+ :returns: the downloaded :class:`Wheel`.
108
+
109
+ :raises ValueError: if ``distribution`` or ``version_spec`` fail the strict allow-list check.
110
+ :raises CalledProcessError: if ``pip download`` exits with a non-zero status.
111
+
112
+ """
113
+ _check_distribution(distribution)
114
+ _check_version_spec(version_spec)
66
115
  to_download = f"{distribution}{version_spec or ''}"
67
116
  LOGGER.debug("download wheel %s %s to %s", to_download, for_py_version, to_folder)
68
117
  cmd = [
@@ -143,6 +192,20 @@ def pip_wheel_env_run(search_dirs: list[Path], app_data: AppData, env: dict[str,
143
192
  return env
144
193
 
145
194
 
195
+ def _check_distribution(distribution: str) -> None:
196
+ if not _DISTRIBUTION_RE.fullmatch(distribution):
197
+ msg = f"refusing to download wheel for suspicious distribution name: {distribution!r}"
198
+ raise ValueError(msg)
199
+
200
+
201
+ def _check_version_spec(version_spec: str | None) -> None:
202
+ if not version_spec:
203
+ return
204
+ if not _VERSION_SPEC_RE.fullmatch(version_spec):
205
+ msg = f"refusing to download wheel with suspicious version spec: {version_spec!r}"
206
+ raise ValueError(msg)
207
+
208
+
146
209
  __all__ = [
147
210
  "download_wheel",
148
211
  "get_wheel",
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import zipfile
5
+ from pathlib import Path
6
+
7
+ from virtualenv.info import IS_ZIPAPP, ROOT
8
+ from virtualenv.seed.wheels.util import Wheel
9
+
10
+ BUNDLE_FOLDER = Path(__file__).absolute().parent
11
+ BUNDLE_SUPPORT = {
12
+ "3.8": {
13
+ "pip": "pip-25.0.1-py3-none-any.whl",
14
+ "setuptools": "setuptools-75.3.4-py3-none-any.whl",
15
+ "wheel": "wheel-0.45.1-py3-none-any.whl",
16
+ },
17
+ "3.9": {
18
+ "pip": "pip-26.0.1-py3-none-any.whl",
19
+ "setuptools": "setuptools-82.0.1-py3-none-any.whl",
20
+ },
21
+ "3.10": {
22
+ "pip": "pip-26.0.1-py3-none-any.whl",
23
+ "setuptools": "setuptools-82.0.1-py3-none-any.whl",
24
+ },
25
+ "3.11": {
26
+ "pip": "pip-26.0.1-py3-none-any.whl",
27
+ "setuptools": "setuptools-82.0.1-py3-none-any.whl",
28
+ },
29
+ "3.12": {
30
+ "pip": "pip-26.0.1-py3-none-any.whl",
31
+ "setuptools": "setuptools-82.0.1-py3-none-any.whl",
32
+ },
33
+ "3.13": {
34
+ "pip": "pip-26.0.1-py3-none-any.whl",
35
+ "setuptools": "setuptools-82.0.1-py3-none-any.whl",
36
+ },
37
+ "3.14": {
38
+ "pip": "pip-26.0.1-py3-none-any.whl",
39
+ "setuptools": "setuptools-82.0.1-py3-none-any.whl",
40
+ },
41
+ "3.15": {
42
+ "pip": "pip-26.0.1-py3-none-any.whl",
43
+ "setuptools": "setuptools-82.0.1-py3-none-any.whl",
44
+ },
45
+ }
46
+ MAX = "3.8"
47
+
48
+ # SHA-256 of every bundled wheel. Verified on load so a corrupted or tampered wheel on disk fails loud instead of
49
+ # being handed to pip. Generated together with ``BUNDLE_SUPPORT`` by ``tasks/upgrade_wheels.py``.
50
+ BUNDLE_SHA256 = {
51
+ "pip-25.0.1-py3-none-any.whl": "c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f",
52
+ "pip-26.0.1-py3-none-any.whl": "bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b",
53
+ "setuptools-75.3.4-py3-none-any.whl": "2dd50a7f42dddfa1d02a36f275dbe716f38ed250224f609d35fb60a09593d93e",
54
+ "setuptools-82.0.1-py3-none-any.whl": "a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb",
55
+ "wheel-0.45.1-py3-none-any.whl": "708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248",
56
+ }
57
+
58
+ _VERIFIED_WHEELS: set[str] = set()
59
+
60
+
61
+ def get_embed_wheel(distribution: str, for_py_version: str) -> Wheel | None:
62
+ """Return the bundled wheel that ships with virtualenv for a given distribution and Python version.
63
+
64
+ :param distribution: project name of the seed package, for example ``pip`` or ``setuptools``.
65
+ :param for_py_version: major.minor Python version string the environment will be created for.
66
+
67
+ :returns: a :class:`Wheel` pointing at the verified bundled file, or ``None`` when no wheel is bundled for the
68
+ requested combination.
69
+
70
+ :raises RuntimeError: if the bundled wheel on disk fails SHA-256 verification.
71
+
72
+ """
73
+ mapping = BUNDLE_SUPPORT.get(for_py_version, {}) or BUNDLE_SUPPORT[MAX]
74
+ wheel_file = mapping.get(distribution)
75
+ if wheel_file is None:
76
+ return None
77
+ path = BUNDLE_FOLDER / wheel_file
78
+ _verify_bundled_wheel(path)
79
+ return Wheel.from_path(path)
80
+
81
+
82
+ def _verify_bundled_wheel(path: Path) -> None:
83
+ name = path.name
84
+ if name in _VERIFIED_WHEELS:
85
+ return
86
+ expected = BUNDLE_SHA256.get(name)
87
+ if expected is None:
88
+ msg = f"bundled wheel {name} has no recorded sha256 in BUNDLE_SHA256"
89
+ raise RuntimeError(msg)
90
+ actual = _hash_bundled_wheel(path)
91
+ if actual != expected:
92
+ msg = f"bundled wheel {name} sha256 mismatch: expected {expected}, got {actual}"
93
+ raise RuntimeError(msg)
94
+ _VERIFIED_WHEELS.add(name)
95
+
96
+
97
+ def _hash_bundled_wheel(path: Path) -> str:
98
+ # ``path`` is under the package directory; when virtualenv runs from a zipapp the wheel lives inside the
99
+ # archive and cannot be opened as a regular file, so read the bytes straight from the zipapp entry.
100
+ digest = hashlib.sha256()
101
+ if IS_ZIPAPP:
102
+ entry = path.resolve().relative_to(Path(ROOT).resolve()).as_posix()
103
+ with zipfile.ZipFile(ROOT, "r") as archive, archive.open(entry) as stream:
104
+ for chunk in iter(lambda: stream.read(1 << 20), b""):
105
+ digest.update(chunk)
106
+ else:
107
+ with path.open("rb") as stream:
108
+ for chunk in iter(lambda: stream.read(1 << 20), b""):
109
+ digest.update(chunk)
110
+ return digest.hexdigest()
111
+
112
+
113
+ __all__ = [
114
+ "BUNDLE_FOLDER",
115
+ "BUNDLE_SHA256",
116
+ "BUNDLE_SUPPORT",
117
+ "MAX",
118
+ "get_embed_wheel",
119
+ ]
@@ -360,10 +360,20 @@ def release_date_for_wheel_path(dest: Path) -> datetime | None:
360
360
  return None
361
361
 
362
362
 
363
+ #: Opt-in escape hatch to restore the pre-2026 behavior of falling back to an unverified HTTPS context when the
364
+ #: verified request fails. Off by default: a failed TLS handshake on the PyPI metadata lookup now aborts the update
365
+ #: instead of silently downgrading, because the response drives which wheel version virtualenv thinks is up to date.
366
+ _INSECURE_FALLBACK_ENV = "VIRTUALENV_PERIODIC_UPDATE_INSECURE"
367
+
368
+
363
369
  def _request_context() -> Generator[ssl.SSLContext | None, None, None]:
364
370
  yield None
365
- # fallback to non verified HTTPS (the information we request is not sensitive, so fallback)
366
- yield ssl._create_unverified_context() # noqa: S323, SLF001
371
+ if os.environ.get(_INSECURE_FALLBACK_ENV):
372
+ LOGGER.warning(
373
+ "falling back to unverified HTTPS for PyPI metadata because %s is set",
374
+ _INSECURE_FALLBACK_ENV,
375
+ )
376
+ yield ssl._create_unverified_context() # noqa: S323, SLF001
367
377
 
368
378
 
369
379
  _PYPI_CACHE = {}
@@ -1,14 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- import os
5
4
  import zipfile
6
- from typing import TYPE_CHECKING
5
+ from pathlib import Path
7
6
 
8
- from virtualenv.info import IS_WIN, ROOT
9
-
10
- if TYPE_CHECKING:
11
- from pathlib import Path
7
+ from virtualenv.info import ROOT
12
8
 
13
9
  LOGGER = logging.getLogger(__name__)
14
10
 
@@ -29,16 +25,18 @@ def extract(full_path: str | Path, dest: Path) -> None:
29
25
 
30
26
 
31
27
  def _get_path_within_zip(full_path: str | Path) -> str:
32
- full_path = os.path.realpath(os.path.abspath(str(full_path)))
33
- prefix = f"{ROOT}{os.sep}"
34
- if not full_path.startswith(prefix):
35
- msg = f"full_path={full_path} should start with prefix={prefix}."
36
- raise RuntimeError(msg)
37
- sub_file = full_path[len(prefix) :]
38
- if IS_WIN:
39
- # paths are always UNIX separators, even on Windows, though __file__ still follows platform default
40
- sub_file = sub_file.replace(os.sep, "/")
41
- return sub_file
28
+ # Use Path.relative_to so symlinks and ``..`` segments cannot slip through a string ``startswith`` check. The zipapp
29
+ # root is a real file we own so ``resolve`` is safe; anything that does not resolve under ROOT is a bug or an
30
+ # attempt to escape the archive and we refuse it.
31
+ resolved = Path(full_path).resolve()
32
+ root = Path(ROOT).resolve()
33
+ try:
34
+ relative = resolved.relative_to(root)
35
+ except ValueError as exc:
36
+ msg = f"full_path={resolved} should be within ROOT={root}"
37
+ raise RuntimeError(msg) from exc
38
+ # Zip entries always use forward slashes regardless of platform.
39
+ return relative.as_posix()
42
40
 
43
41
 
44
42
  __all__ = [
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '21.2.2'
22
- __version_tuple__ = version_tuple = (21, 2, 2)
21
+ __version__ = version = '21.2.4'
22
+ __version_tuple__ = version_tuple = (21, 2, 4)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,265 @@
1
+ """Helper script to rebuild virtualenv_support. Downloads the wheel files using pip."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import hashlib
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from collections import OrderedDict, defaultdict
12
+ from pathlib import Path
13
+ from tempfile import TemporaryDirectory
14
+ from textwrap import dedent
15
+ from threading import Thread
16
+ from typing import NoReturn
17
+
18
+ STRICT = "UPGRADE_ADVISORY" not in os.environ
19
+
20
+ BUNDLED = ["pip", "setuptools", "wheel"]
21
+ SUPPORT = [(3, i) for i in range(8, 16)]
22
+ DEST = Path(__file__).resolve().parents[1] / "src" / "virtualenv" / "seed" / "wheels" / "embed"
23
+
24
+
25
+ def run() -> NoReturn:
26
+ if "--regen" in sys.argv[1:]:
27
+ render_init()
28
+ raise SystemExit(0)
29
+ old_batch = {i.name for i in DEST.iterdir() if i.suffix == ".whl"}
30
+ with TemporaryDirectory() as temp:
31
+ folders = _download_all(Path(temp))
32
+ new_batch = {i.name: i for f in folders for i in Path(f).iterdir()}
33
+ new_packages = new_batch.keys() - old_batch
34
+ remove_packages = old_batch - new_batch.keys()
35
+ _sync_dest(new_packages, remove_packages, new_batch)
36
+ added = collect_package_versions(new_packages)
37
+ removed = collect_package_versions(remove_packages)
38
+ outcome = (1 if STRICT else 0) if (added or removed) else 0
39
+ print(f"Outcome {outcome} added {added} removed {removed}") # noqa: T201
40
+ _write_changelog(added, removed)
41
+ render_init(folders=folders)
42
+ raise SystemExit(outcome)
43
+
44
+
45
+ def _download_all(temp_path: Path) -> dict[Path, str]:
46
+ folders: dict[Path, str] = {}
47
+ targets: list[Thread] = []
48
+ for support in SUPPORT:
49
+ support_ver = ".".join(str(i) for i in support)
50
+ into = temp_path / support_ver
51
+ into.mkdir()
52
+ folders[into] = support_ver
53
+ for package in BUNDLED:
54
+ if package == "wheel" and support >= (3, 9):
55
+ continue
56
+ thread = Thread(target=download, args=(support_ver, str(into), package))
57
+ targets.append(thread)
58
+ thread.start()
59
+ for thread in targets:
60
+ thread.join()
61
+ return folders
62
+
63
+
64
+ def _sync_dest(new_packages: set[str], remove_packages: set[str], new_batch: dict[str, Path]) -> None:
65
+ for package in remove_packages:
66
+ (DEST / package).unlink()
67
+ for package in new_packages:
68
+ shutil.copy2(str(new_batch[package]), DEST / package)
69
+
70
+
71
+ def _write_changelog(added: dict[str, list[str]], removed: dict[str, list[str]]) -> None:
72
+ lines = ["Upgrade embedded wheels:", ""]
73
+ for key, versions in added.items():
74
+ text = f"* {key} to {fmt_version(versions)}"
75
+ if key in removed:
76
+ rem = ", ".join(f"``{i}``" for i in removed[key])
77
+ text += f" from {rem}"
78
+ del removed[key]
79
+ lines.append(text)
80
+ for key, versions in removed.items():
81
+ lines.append(f"Removed {key} of {fmt_version(versions)}")
82
+ lines.append("")
83
+ changelog = "\n".join(lines)
84
+ print(changelog) # noqa: T201
85
+ if len(lines) >= 4: # noqa: PLR2004
86
+ (Path(__file__).parents[1] / "docs" / "changelog" / "u.bugfix.rst").write_text(changelog, encoding="utf-8")
87
+
88
+
89
+ def render_init(folders: dict[Path, str] | None = None) -> None:
90
+ """Write ``embed/__init__.py`` from the wheels currently in DEST.
91
+
92
+ When called from ``run()`` after a download round, ``folders`` maps each per-python-version temp folder to its
93
+ version string, which is how support for a wheel is determined. When called with ``--regen`` there are no downloaded
94
+ folders — the existing ``BUNDLE_SUPPORT`` from the current ``__init__.py`` is used so regeneration is deterministic.
95
+
96
+ """
97
+ if folders is None:
98
+ support_table = _support_table_from_existing_init()
99
+ else:
100
+ present = {i.name: i for f in folders for i in Path(f).iterdir() if i.suffix == ".whl"}
101
+ support_table = OrderedDict((".".join(str(j) for j in i), []) for i in SUPPORT)
102
+ for package in sorted(present):
103
+ for folder, version in sorted(folders.items()):
104
+ if (folder / package).exists():
105
+ support_table[version].append(package)
106
+ support_table = OrderedDict((k, OrderedDict((i.split("-")[0], i) for i in v)) for k, v in support_table.items())
107
+ wheel_names = sorted({wheel for mapping in support_table.values() for wheel in mapping.values()})
108
+ sha_table = OrderedDict((name, _sha256(DEST / name)) for name in wheel_names)
109
+ nl = "\n"
110
+ bundle = "".join(
111
+ f"\n {v!r}: {{{nl}{''.join(f' {p!r}: {f!r},{nl}' for p, f in line.items())} }},"
112
+ for v, line in support_table.items()
113
+ )
114
+ sha_block = "".join(f"\n {name!r}: {digest!r}," for name, digest in sha_table.items())
115
+ msg = dedent(
116
+ f"""
117
+ from __future__ import annotations
118
+
119
+ import hashlib
120
+ import zipfile
121
+ from pathlib import Path
122
+
123
+ from virtualenv.info import IS_ZIPAPP, ROOT
124
+ from virtualenv.seed.wheels.util import Wheel
125
+
126
+ BUNDLE_FOLDER = Path(__file__).absolute().parent
127
+ BUNDLE_SUPPORT = {{ {bundle} }}
128
+ MAX = {next(iter(support_table.keys()))!r}
129
+
130
+ # SHA-256 of every bundled wheel. Verified on load so a corrupted or tampered wheel on disk fails loud instead of
131
+ # being handed to pip. Generated together with ``BUNDLE_SUPPORT`` by ``tasks/upgrade_wheels.py``.
132
+ BUNDLE_SHA256 = {{ {sha_block} }}
133
+
134
+ _VERIFIED_WHEELS: set[str] = set()
135
+
136
+
137
+ def get_embed_wheel(distribution: str, for_py_version: str) -> Wheel | None:
138
+ \"\"\"Return the bundled wheel that ships with virtualenv for a given distribution and Python version.
139
+
140
+ :param distribution: project name of the seed package, for example ``pip`` or ``setuptools``.
141
+ :param for_py_version: major.minor Python version string the environment will be created for.
142
+
143
+ :returns: a :class:`Wheel` pointing at the verified bundled file, or ``None`` when no wheel is bundled for the
144
+ requested combination.
145
+
146
+ :raises RuntimeError: if the bundled wheel on disk fails SHA-256 verification.
147
+
148
+ \"\"\"
149
+ mapping = BUNDLE_SUPPORT.get(for_py_version, {{}}) or BUNDLE_SUPPORT[MAX]
150
+ wheel_file = mapping.get(distribution)
151
+ if wheel_file is None:
152
+ return None
153
+ path = BUNDLE_FOLDER / wheel_file
154
+ _verify_bundled_wheel(path)
155
+ return Wheel.from_path(path)
156
+
157
+
158
+ def _verify_bundled_wheel(path: Path) -> None:
159
+ name = path.name
160
+ if name in _VERIFIED_WHEELS:
161
+ return
162
+ expected = BUNDLE_SHA256.get(name)
163
+ if expected is None:
164
+ msg = f"bundled wheel {{name}} has no recorded sha256 in BUNDLE_SHA256"
165
+ raise RuntimeError(msg)
166
+ actual = _hash_bundled_wheel(path)
167
+ if actual != expected:
168
+ msg = f"bundled wheel {{name}} sha256 mismatch: expected {{expected}}, got {{actual}}"
169
+ raise RuntimeError(msg)
170
+ _VERIFIED_WHEELS.add(name)
171
+
172
+
173
+ def _hash_bundled_wheel(path: Path) -> str:
174
+ # ``path`` is under the package directory; when virtualenv runs from a zipapp the wheel lives inside the
175
+ # archive and cannot be opened as a regular file, so read the bytes straight from the zipapp entry.
176
+ digest = hashlib.sha256()
177
+ if IS_ZIPAPP:
178
+ entry = path.resolve().relative_to(Path(ROOT).resolve()).as_posix()
179
+ with zipfile.ZipFile(ROOT, "r") as archive, archive.open(entry) as stream:
180
+ for chunk in iter(lambda: stream.read(1 << 20), b""):
181
+ digest.update(chunk)
182
+ else:
183
+ with path.open("rb") as stream:
184
+ for chunk in iter(lambda: stream.read(1 << 20), b""):
185
+ digest.update(chunk)
186
+ return digest.hexdigest()
187
+
188
+
189
+ __all__ = [
190
+ "BUNDLE_FOLDER",
191
+ "BUNDLE_SHA256",
192
+ "BUNDLE_SUPPORT",
193
+ "MAX",
194
+ "get_embed_wheel",
195
+ ]
196
+
197
+ """,
198
+ )
199
+ dest_target = DEST / "__init__.py"
200
+ dest_target.write_text(msg, encoding="utf-8")
201
+ subprocess.run([sys.executable, "-m", "ruff", "check", str(dest_target), "--fix", "--unsafe-fixes"], check=False)
202
+ subprocess.run([sys.executable, "-m", "ruff", "format", str(dest_target), "--preview"], check=False)
203
+
204
+
205
+ def _support_table_from_existing_init() -> OrderedDict[str, OrderedDict[str, str]]:
206
+ source = (DEST / "__init__.py").read_text(encoding="utf-8")
207
+ tree = ast.parse(source)
208
+ for node in tree.body:
209
+ if isinstance(node, ast.Assign) and any(
210
+ isinstance(t, ast.Name) and t.id == "BUNDLE_SUPPORT" for t in node.targets
211
+ ):
212
+ bundle_support = ast.literal_eval(node.value)
213
+ return OrderedDict(
214
+ (version, OrderedDict(sorted(mapping.items()))) for version, mapping in bundle_support.items()
215
+ )
216
+ msg = f"BUNDLE_SUPPORT not found in {DEST / '__init__.py'}"
217
+ raise RuntimeError(msg)
218
+
219
+
220
+ def _sha256(path: Path) -> str:
221
+ digest = hashlib.sha256()
222
+ with path.open("rb") as stream:
223
+ for chunk in iter(lambda: stream.read(1 << 20), b""):
224
+ digest.update(chunk)
225
+ return digest.hexdigest()
226
+
227
+
228
+ def fmt_version(versions: list[str]) -> str:
229
+ return ", ".join(f"``{v}``" for v in versions)
230
+
231
+
232
+ def collect_package_versions(new_packages: set[str]) -> dict[str, list[str]]:
233
+ result = defaultdict(list)
234
+ for package in new_packages:
235
+ split = package.split("-")
236
+ if len(split) < 2: # noqa: PLR2004
237
+ raise ValueError(package)
238
+ key, version = split[0:2]
239
+ result[key].append(version)
240
+ return result
241
+
242
+
243
+ def download(python_version: str, dest: str, package: str) -> None:
244
+ subprocess.call(
245
+ [
246
+ sys.executable,
247
+ "-W",
248
+ "ignore::EncodingWarning",
249
+ "-m",
250
+ "pip",
251
+ "--disable-pip-version-check",
252
+ "download",
253
+ "--no-cache-dir",
254
+ "--only-binary=:all:",
255
+ "--python-version",
256
+ python_version,
257
+ "-d",
258
+ dest,
259
+ package,
260
+ ],
261
+ )
262
+
263
+
264
+ if __name__ == "__main__":
265
+ run()