base.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. from __future__ import annotations
  2. import sys
  3. from collections import OrderedDict
  4. from importlib.metadata import entry_points
  5. from typing import TYPE_CHECKING
  6. if TYPE_CHECKING:
  7. from collections.abc import Sequence
  8. from python_discovery import PythonInfo
  9. from virtualenv.config.cli.parser import VirtualEnvConfigParser, VirtualEnvOptions
  10. importlib_metadata_version = ()
  11. class PluginLoader:
  12. _OPTIONS = None
  13. _ENTRY_POINTS = None
  14. @classmethod
  15. def entry_points_for(cls, key: str) -> OrderedDict[str, type]:
  16. if sys.version_info >= (3, 10) or importlib_metadata_version >= (3, 6):
  17. return OrderedDict((e.name, e.load()) for e in cls.entry_points().select(group=key)) # ty: ignore[unresolved-attribute]
  18. return OrderedDict((e.name, e.load()) for e in cls.entry_points().get(key, {})) # ty: ignore[unresolved-attribute]
  19. @staticmethod
  20. def entry_points() -> object:
  21. if PluginLoader._ENTRY_POINTS is None:
  22. PluginLoader._ENTRY_POINTS = entry_points()
  23. return PluginLoader._ENTRY_POINTS
  24. class ComponentBuilder(PluginLoader):
  25. def __init__(
  26. self, interpreter: PythonInfo, parser: VirtualEnvConfigParser, name: str, possible: dict[str, type]
  27. ) -> None:
  28. self.interpreter = interpreter
  29. self.name = name
  30. self._impl_class = None
  31. self.possible = possible
  32. self.parser = parser.add_argument_group(title=name)
  33. self.add_selector_arg_parse(name, list(self.possible))
  34. @classmethod
  35. def options(cls, key: str) -> OrderedDict[str, type]:
  36. if cls._OPTIONS is None:
  37. cls._OPTIONS = cls.entry_points_for(key)
  38. return cls._OPTIONS
  39. def add_selector_arg_parse(self, name: str, choices: Sequence[str]) -> None:
  40. raise NotImplementedError
  41. def handle_selected_arg_parse(self, options: VirtualEnvOptions) -> str:
  42. selected = getattr(options, self.name)
  43. if selected not in self.possible:
  44. msg = f"No implementation for {self.interpreter}"
  45. raise RuntimeError(msg)
  46. self._impl_class = self.possible[selected]
  47. self.populate_selected_argparse(selected, options.app_data)
  48. return selected
  49. def populate_selected_argparse(self, selected: str, app_data: object) -> None:
  50. self.parser.description = f"options for {self.name} {selected}"
  51. assert self._impl_class is not None # noqa: S101 # Set by handle_selected_arg_parse
  52. self._impl_class.add_parser_arguments(self.parser, self.interpreter, app_data) # ty: ignore[unresolved-attribute]
  53. def create(self, options: VirtualEnvOptions) -> object:
  54. assert self._impl_class is not None # noqa: S101 # Set by handle_selected_arg_parse
  55. return self._impl_class(options, self.interpreter)
  56. __all__ = [
  57. "ComponentBuilder",
  58. "PluginLoader",
  59. ]