local.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. from __future__ import annotations
  2. import os
  3. import sys
  4. from inspect import ismemberdescriptor
  5. from pathlib import Path
  6. from pathlib import PosixPath
  7. from pathlib import WindowsPath
  8. from typing import IO
  9. from typing import Any
  10. from typing import Collection
  11. from typing import MutableMapping
  12. from urllib.parse import SplitResult
  13. from upath._protocol import compatible_protocol
  14. from upath.core import UPath
  15. __all__ = [
  16. "LocalPath",
  17. "FilePath",
  18. "PosixUPath",
  19. "WindowsUPath",
  20. ]
  21. _LISTDIR_WORKS_ON_FILES: bool | None = None
  22. def _check_listdir_works_on_files() -> bool:
  23. global _LISTDIR_WORKS_ON_FILES
  24. from fsspec.implementations.local import LocalFileSystem
  25. fs = LocalFileSystem()
  26. try:
  27. fs.ls(__file__)
  28. except NotADirectoryError:
  29. _LISTDIR_WORKS_ON_FILES = w = False
  30. else:
  31. _LISTDIR_WORKS_ON_FILES = w = True
  32. return w
  33. class LocalPath(UPath):
  34. __slots__ = ()
  35. @property
  36. def path(self):
  37. sep = self._flavour.sep
  38. if self.drive:
  39. return f"/{super().path}".replace(sep, "/")
  40. return super().path.replace(sep, "/")
  41. @property
  42. def _url(self):
  43. return SplitResult(self.protocol, "", self.path, "", "")
  44. class FilePath(LocalPath):
  45. __slots__ = ()
  46. def iterdir(self):
  47. if _LISTDIR_WORKS_ON_FILES is None:
  48. _check_listdir_works_on_files()
  49. if _LISTDIR_WORKS_ON_FILES and self.is_file():
  50. raise NotADirectoryError(f"{self}")
  51. return super().iterdir()
  52. _pathlib_py312_ignore = {
  53. "__slots__",
  54. "__module__",
  55. "__new__",
  56. "__init__",
  57. "_from_parts",
  58. "_from_parsed_parts",
  59. "with_segments",
  60. }
  61. def _set_class_attributes(
  62. type_dict: MutableMapping[str, Any],
  63. src: type[Path],
  64. *,
  65. ignore: Collection[str] = frozenset(_pathlib_py312_ignore),
  66. ) -> None:
  67. """helper function to assign all methods/attrs from src to a class dict"""
  68. visited = set()
  69. for cls in src.__mro__:
  70. if cls is object:
  71. continue
  72. for attr, func_or_value in cls.__dict__.items():
  73. if ismemberdescriptor(func_or_value):
  74. continue
  75. if attr in ignore or attr in visited:
  76. continue
  77. else:
  78. visited.add(attr)
  79. type_dict[attr] = func_or_value
  80. def _upath_init(inst: PosixUPath | WindowsUPath) -> None:
  81. """helper to initialize the PosixPath/WindowsPath instance with UPath attrs"""
  82. inst._protocol = ""
  83. inst._storage_options = {}
  84. if sys.version_info < (3, 10) and hasattr(inst, "_init"):
  85. inst._init()
  86. class PosixUPath(PosixPath, LocalPath): # type: ignore[misc]
  87. __slots__ = ()
  88. # assign all PosixPath methods/attrs to prevent multi inheritance issues
  89. _set_class_attributes(locals(), src=PosixPath)
  90. def open( # type: ignore[override]
  91. self,
  92. mode="r",
  93. buffering=-1,
  94. encoding=None,
  95. errors=None,
  96. newline=None,
  97. **fsspec_kwargs,
  98. ) -> IO[Any]:
  99. if fsspec_kwargs:
  100. return super(LocalPath, self).open(
  101. mode=mode,
  102. buffering=buffering,
  103. encoding=encoding,
  104. errors=errors,
  105. newline=newline,
  106. **fsspec_kwargs,
  107. )
  108. else:
  109. return PosixPath.open(self, mode, buffering, encoding, errors, newline)
  110. if sys.version_info < (3, 12):
  111. def __new__(
  112. cls, *args, protocol: str | None = None, **storage_options: Any
  113. ) -> PosixUPath:
  114. if os.name == "nt":
  115. raise NotImplementedError(
  116. f"cannot instantiate {cls.__name__} on your system"
  117. )
  118. if not compatible_protocol("", *args):
  119. raise ValueError("can't combine incompatible UPath protocols")
  120. obj = super().__new__(cls, *args)
  121. obj._protocol = ""
  122. return obj # type: ignore[return-value]
  123. def __init__(
  124. self, *args, protocol: str | None = None, **storage_options: Any
  125. ) -> None:
  126. super(Path, self).__init__()
  127. self._drv, self._root, self._parts = type(self)._parse_args(args)
  128. _upath_init(self)
  129. def _make_child(self, args):
  130. if not compatible_protocol(self._protocol, *args):
  131. raise ValueError("can't combine incompatible UPath protocols")
  132. return super()._make_child(args)
  133. @classmethod
  134. def _from_parts(cls, *args, **kwargs):
  135. obj = super(Path, cls)._from_parts(*args, **kwargs)
  136. _upath_init(obj)
  137. return obj
  138. @classmethod
  139. def _from_parsed_parts(cls, drv, root, parts):
  140. obj = super(Path, cls)._from_parsed_parts(drv, root, parts)
  141. _upath_init(obj)
  142. return obj
  143. @property
  144. def path(self) -> str:
  145. return PosixPath.__str__(self)
  146. class WindowsUPath(WindowsPath, LocalPath): # type: ignore[misc]
  147. __slots__ = ()
  148. # assign all WindowsPath methods/attrs to prevent multi inheritance issues
  149. _set_class_attributes(locals(), src=WindowsPath)
  150. def open( # type: ignore[override]
  151. self,
  152. mode="r",
  153. buffering=-1,
  154. encoding=None,
  155. errors=None,
  156. newline=None,
  157. **fsspec_kwargs,
  158. ) -> IO[Any]:
  159. if fsspec_kwargs:
  160. return super(LocalPath, self).open(
  161. mode=mode,
  162. buffering=buffering,
  163. encoding=encoding,
  164. errors=errors,
  165. newline=newline,
  166. **fsspec_kwargs,
  167. )
  168. else:
  169. return WindowsPath.open(self, mode, buffering, encoding, errors, newline)
  170. if sys.version_info < (3, 12):
  171. def __new__(
  172. cls, *args, protocol: str | None = None, **storage_options: Any
  173. ) -> WindowsUPath:
  174. if os.name != "nt":
  175. raise NotImplementedError(
  176. f"cannot instantiate {cls.__name__} on your system"
  177. )
  178. if not compatible_protocol("", *args):
  179. raise ValueError("can't combine incompatible UPath protocols")
  180. obj = super().__new__(cls, *args)
  181. obj._protocol = ""
  182. return obj # type: ignore[return-value]
  183. def __init__(
  184. self, *args, protocol: str | None = None, **storage_options: Any
  185. ) -> None:
  186. super(Path, self).__init__()
  187. self._drv, self._root, self._parts = self._parse_args(args)
  188. _upath_init(self)
  189. def _make_child(self, args):
  190. if not compatible_protocol(self._protocol, *args):
  191. raise ValueError("can't combine incompatible UPath protocols")
  192. return super()._make_child(args)
  193. @classmethod
  194. def _from_parts(cls, *args, **kwargs):
  195. obj = super(Path, cls)._from_parts(*args, **kwargs)
  196. _upath_init(obj)
  197. return obj
  198. @classmethod
  199. def _from_parsed_parts(cls, drv, root, parts):
  200. obj = super(Path, cls)._from_parsed_parts(drv, root, parts)
  201. _upath_init(obj)
  202. return obj
  203. @property
  204. def path(self) -> str:
  205. return WindowsPath.as_posix(self)