base.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. """Application data stored by virtualenv."""
  2. from __future__ import annotations
  3. from abc import ABC, abstractmethod
  4. from contextlib import contextmanager
  5. from typing import TYPE_CHECKING
  6. from virtualenv.info import IS_ZIPAPP
  7. if TYPE_CHECKING:
  8. from collections.abc import Generator
  9. from pathlib import Path
  10. from typing import Any
  11. class AppData(ABC):
  12. """Abstract storage interface for the virtualenv application."""
  13. @abstractmethod
  14. def close(self) -> None:
  15. """Called before virtualenv exits."""
  16. @abstractmethod
  17. def reset(self) -> None:
  18. """Called when the user passes in the reset app data."""
  19. @abstractmethod
  20. def py_info(self, path: Path) -> ContentStore:
  21. """Return a content store for cached interpreter information at the given path.
  22. :param path: the interpreter executable path
  23. :returns: a content store for the cached data
  24. """
  25. raise NotImplementedError
  26. @abstractmethod
  27. def py_info_clear(self) -> None:
  28. """Clear all cached interpreter information."""
  29. raise NotImplementedError
  30. @property
  31. def can_update(self) -> bool:
  32. """``True`` if this app data store supports updating cached content."""
  33. raise NotImplementedError
  34. @abstractmethod
  35. def embed_update_log(self, distribution: str, for_py_version: str) -> ContentStore:
  36. """Return a content store for the embed update log of a distribution.
  37. :param distribution: the package name (e.g. ``pip``)
  38. :param for_py_version: the target Python version string
  39. :returns: a content store for the update log
  40. """
  41. raise NotImplementedError
  42. @property
  43. def house(self) -> Path:
  44. """The root directory of the application data store."""
  45. raise NotImplementedError
  46. @property
  47. def transient(self) -> bool:
  48. """``True`` if this app data store is transient and does not persist across runs."""
  49. raise NotImplementedError
  50. @abstractmethod
  51. def wheel_image(self, for_py_version: str, name: str) -> Path:
  52. """Return the path to a cached wheel image.
  53. :param for_py_version: the target Python version string
  54. :param name: the package name
  55. :returns: the path to the cached wheel
  56. """
  57. raise NotImplementedError
  58. @contextmanager
  59. def ensure_extracted(self, path: Path, to_folder: Path | None = None) -> Generator[Path]:
  60. """Ensure a path is available on disk, extracting from zipapp if needed.
  61. :param path: the path to ensure is available
  62. :param to_folder: optional target directory for extraction
  63. :returns: yields the usable path on disk
  64. """
  65. if IS_ZIPAPP:
  66. with self.extract(path, to_folder) as result:
  67. yield result
  68. else:
  69. yield path
  70. @abstractmethod
  71. @contextmanager
  72. def extract(self, path: Path, to_folder: Path | None) -> Generator[Path]:
  73. """Extract a path from the zipapp to a location on disk.
  74. :param path: the path to extract
  75. :param to_folder: optional target directory
  76. :returns: yields the extracted path
  77. """
  78. raise NotImplementedError
  79. @abstractmethod
  80. @contextmanager
  81. def locked(self, path: Path) -> Generator[None]:
  82. """Acquire an exclusive lock on the given path.
  83. :param path: the path to lock
  84. """
  85. raise NotImplementedError
  86. class ContentStore(ABC):
  87. """A store for reading and writing cached content."""
  88. @abstractmethod
  89. def exists(self) -> bool:
  90. """Check if the stored content exists.
  91. :returns: ``True`` if content exists
  92. """
  93. raise NotImplementedError
  94. @abstractmethod
  95. def read(self) -> Any: # noqa: ANN401
  96. """Read the stored content.
  97. :returns: the stored content
  98. """
  99. raise NotImplementedError
  100. @abstractmethod
  101. def write(self, content: Any) -> None: # noqa: ANN401
  102. """Write content to the store.
  103. :param content: the content to write
  104. """
  105. raise NotImplementedError
  106. @abstractmethod
  107. def remove(self) -> None:
  108. """Remove the stored content."""
  109. raise NotImplementedError
  110. @abstractmethod
  111. @contextmanager
  112. def locked(self) -> Generator[None]:
  113. """Acquire an exclusive lock on this content store."""
  114. __all__ = [
  115. "AppData",
  116. "ContentStore",
  117. ]