base_embed.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. from __future__ import annotations
  2. import logging
  3. from abc import ABC
  4. from argparse import SUPPRESS
  5. from pathlib import Path
  6. from typing import TYPE_CHECKING
  7. from virtualenv.seed.seeder import Seeder
  8. from virtualenv.seed.wheels import Version
  9. if TYPE_CHECKING:
  10. from argparse import ArgumentParser
  11. from python_discovery import PythonInfo
  12. from virtualenv.app_data.base import AppData
  13. from virtualenv.config.cli.parser import VirtualEnvOptions
  14. LOGGER = logging.getLogger(__name__)
  15. PERIODIC_UPDATE_ON_BY_DEFAULT = True
  16. class BaseEmbed(Seeder, ABC):
  17. def __init__(self, options: VirtualEnvOptions) -> None:
  18. super().__init__(options, enabled=options.no_seed is False)
  19. self.download = options.download
  20. self.extra_search_dir = [i.resolve() for i in options.extra_search_dir if i.exists()]
  21. self.pip_version = options.pip
  22. self.setuptools_version = options.setuptools
  23. # wheel version needs special handling
  24. # on Python > 3.8, the default is None (as in not used)
  25. # so we can differentiate between explicit and implicit none
  26. self.wheel_version = options.wheel or "none"
  27. self.no_pip = options.no_pip
  28. self.no_setuptools = options.no_setuptools
  29. self.no_wheel = options.no_wheel
  30. self.app_data = options.app_data
  31. self.periodic_update = not options.no_periodic_update
  32. if options.py_version[:2] >= (3, 9):
  33. if options.wheel is not None or options.no_wheel:
  34. LOGGER.warning(
  35. "The --no-wheel and --wheel options are deprecated. "
  36. "They have no effect for Python > 3.8 as wheel is no longer "
  37. "bundled in virtualenv.",
  38. )
  39. self.no_wheel = True
  40. if not self.distribution_to_versions():
  41. self.enabled = False
  42. @classmethod
  43. def distributions(cls) -> dict[str, str]:
  44. return {
  45. "pip": Version.bundle,
  46. "setuptools": Version.bundle,
  47. "wheel": Version.bundle,
  48. }
  49. def distribution_to_versions(self) -> dict[str, str]:
  50. return {
  51. distribution: getattr(self, f"{distribution}_version")
  52. for distribution in self.distributions()
  53. if getattr(self, f"no_{distribution}", None) is False and getattr(self, f"{distribution}_version") != "none"
  54. }
  55. @classmethod
  56. def add_parser_arguments(cls, parser: ArgumentParser, interpreter: PythonInfo, app_data: AppData) -> None: # noqa: ARG003
  57. group = parser.add_mutually_exclusive_group()
  58. group.add_argument(
  59. "--no-download",
  60. "--never-download",
  61. dest="download",
  62. action="store_false",
  63. help=f"pass to disable download of the latest {'/'.join(cls.distributions())} from PyPI",
  64. default=True,
  65. )
  66. group.add_argument(
  67. "--download",
  68. dest="download",
  69. action="store_true",
  70. help=f"pass to enable download of the latest {'/'.join(cls.distributions())} from PyPI",
  71. default=False,
  72. )
  73. parser.add_argument(
  74. "--extra-search-dir",
  75. metavar="d",
  76. type=Path,
  77. nargs="+",
  78. help="a path containing wheels to extend the internal wheel list (can be set 1+ times)",
  79. default=[],
  80. )
  81. for distribution, default in cls.distributions().items():
  82. help_ = f"version of {distribution} to install as seed: embed, bundle, none or exact version"
  83. if interpreter.version_info[:2] >= (3, 12) and distribution in {"wheel", "setuptools"}:
  84. default = "none" # noqa: PLW2901
  85. if interpreter.version_info[:2] >= (3, 9) and distribution == "wheel":
  86. default = None # noqa: PLW2901
  87. help_ = SUPPRESS
  88. parser.add_argument(
  89. f"--{distribution}",
  90. dest=distribution,
  91. metavar="version",
  92. help=help_,
  93. default=default,
  94. )
  95. for distribution in cls.distributions():
  96. help_ = f"do not install {distribution}"
  97. if interpreter.version_info[:2] >= (3, 9) and distribution == "wheel":
  98. help_ = SUPPRESS
  99. parser.add_argument(
  100. f"--no-{distribution}",
  101. dest=f"no_{distribution}",
  102. action="store_true",
  103. help=help_,
  104. default=False,
  105. )
  106. parser.add_argument(
  107. "--no-periodic-update",
  108. dest="no_periodic_update",
  109. action="store_true",
  110. help="disable the periodic (once every 14 days) update of the embedded wheels",
  111. default=not PERIODIC_UPDATE_ON_BY_DEFAULT,
  112. )
  113. def __repr__(self) -> str:
  114. result = self.__class__.__name__
  115. result += "("
  116. if self.extra_search_dir:
  117. result += f"extra_search_dir={', '.join(str(i) for i in self.extra_search_dir)},"
  118. result += f"download={self.download},"
  119. for distribution in self.distributions():
  120. if getattr(self, f"no_{distribution}", None):
  121. continue
  122. version = getattr(self, f"{distribution}_version", None)
  123. if version == "none":
  124. continue
  125. ver = f"={version or 'latest'}"
  126. result += f" {distribution}{ver},"
  127. return result[:-1] + ")"
  128. __all__ = [
  129. "BaseEmbed",
  130. ]