m2m模型翻译
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

402 lines
10 KiB

6 months ago
  1. import io
  2. import posixpath
  3. import zipfile
  4. import itertools
  5. import contextlib
  6. import pathlib
  7. import re
  8. import fnmatch
  9. from .py310compat import text_encoding
  10. __all__ = ['Path']
  11. def _parents(path):
  12. """
  13. Given a path with elements separated by
  14. posixpath.sep, generate all parents of that path.
  15. >>> list(_parents('b/d'))
  16. ['b']
  17. >>> list(_parents('/b/d/'))
  18. ['/b']
  19. >>> list(_parents('b/d/f/'))
  20. ['b/d', 'b']
  21. >>> list(_parents('b'))
  22. []
  23. >>> list(_parents(''))
  24. []
  25. """
  26. return itertools.islice(_ancestry(path), 1, None)
  27. def _ancestry(path):
  28. """
  29. Given a path with elements separated by
  30. posixpath.sep, generate all elements of that path
  31. >>> list(_ancestry('b/d'))
  32. ['b/d', 'b']
  33. >>> list(_ancestry('/b/d/'))
  34. ['/b/d', '/b']
  35. >>> list(_ancestry('b/d/f/'))
  36. ['b/d/f', 'b/d', 'b']
  37. >>> list(_ancestry('b'))
  38. ['b']
  39. >>> list(_ancestry(''))
  40. []
  41. """
  42. path = path.rstrip(posixpath.sep)
  43. while path and path != posixpath.sep:
  44. yield path
  45. path, tail = posixpath.split(path)
  46. _dedupe = dict.fromkeys
  47. """Deduplicate an iterable in original order"""
  48. def _difference(minuend, subtrahend):
  49. """
  50. Return items in minuend not in subtrahend, retaining order
  51. with O(1) lookup.
  52. """
  53. return itertools.filterfalse(set(subtrahend).__contains__, minuend)
  54. class InitializedState:
  55. """
  56. Mix-in to save the initialization state for pickling.
  57. """
  58. def __init__(self, *args, **kwargs):
  59. self.__args = args
  60. self.__kwargs = kwargs
  61. super().__init__(*args, **kwargs)
  62. def __getstate__(self):
  63. return self.__args, self.__kwargs
  64. def __setstate__(self, state):
  65. args, kwargs = state
  66. super().__init__(*args, **kwargs)
  67. class CompleteDirs(InitializedState, zipfile.ZipFile):
  68. """
  69. A ZipFile subclass that ensures that implied directories
  70. are always included in the namelist.
  71. >>> list(CompleteDirs._implied_dirs(['foo/bar.txt', 'foo/bar/baz.txt']))
  72. ['foo/', 'foo/bar/']
  73. >>> list(CompleteDirs._implied_dirs(['foo/bar.txt', 'foo/bar/baz.txt', 'foo/bar/']))
  74. ['foo/']
  75. """
  76. @staticmethod
  77. def _implied_dirs(names):
  78. parents = itertools.chain.from_iterable(map(_parents, names))
  79. as_dirs = (p + posixpath.sep for p in parents)
  80. return _dedupe(_difference(as_dirs, names))
  81. def namelist(self):
  82. names = super().namelist()
  83. return names + list(self._implied_dirs(names))
  84. def _name_set(self):
  85. return set(self.namelist())
  86. def resolve_dir(self, name):
  87. """
  88. If the name represents a directory, return that name
  89. as a directory (with the trailing slash).
  90. """
  91. names = self._name_set()
  92. dirname = name + '/'
  93. dir_match = name not in names and dirname in names
  94. return dirname if dir_match else name
  95. def getinfo(self, name):
  96. """
  97. Supplement getinfo for implied dirs.
  98. """
  99. try:
  100. return super().getinfo(name)
  101. except KeyError:
  102. if not name.endswith('/') or name not in self._name_set():
  103. raise
  104. return zipfile.ZipInfo(filename=name)
  105. @classmethod
  106. def make(cls, source):
  107. """
  108. Given a source (filename or zipfile), return an
  109. appropriate CompleteDirs subclass.
  110. """
  111. if isinstance(source, CompleteDirs):
  112. return source
  113. if not isinstance(source, zipfile.ZipFile):
  114. return cls(source)
  115. # Only allow for FastLookup when supplied zipfile is read-only
  116. if 'r' not in source.mode:
  117. cls = CompleteDirs
  118. source.__class__ = cls
  119. return source
  120. class FastLookup(CompleteDirs):
  121. """
  122. ZipFile subclass to ensure implicit
  123. dirs exist and are resolved rapidly.
  124. """
  125. def namelist(self):
  126. with contextlib.suppress(AttributeError):
  127. return self.__names
  128. self.__names = super().namelist()
  129. return self.__names
  130. def _name_set(self):
  131. with contextlib.suppress(AttributeError):
  132. return self.__lookup
  133. self.__lookup = super()._name_set()
  134. return self.__lookup
  135. def _extract_text_encoding(encoding=None, *args, **kwargs):
  136. # stacklevel=3 so that the caller of the caller see any warning.
  137. return text_encoding(encoding, 3), args, kwargs
  138. class Path:
  139. """
  140. A pathlib-compatible interface for zip files.
  141. Consider a zip file with this structure::
  142. .
  143. a.txt
  144. b
  145. c.txt
  146. d
  147. e.txt
  148. >>> data = io.BytesIO()
  149. >>> zf = zipfile.ZipFile(data, 'w')
  150. >>> zf.writestr('a.txt', 'content of a')
  151. >>> zf.writestr('b/c.txt', 'content of c')
  152. >>> zf.writestr('b/d/e.txt', 'content of e')
  153. >>> zf.filename = 'mem/abcde.zip'
  154. Path accepts the zipfile object itself or a filename
  155. >>> root = Path(zf)
  156. From there, several path operations are available.
  157. Directory iteration (including the zip file itself):
  158. >>> a, b = root.iterdir()
  159. >>> a
  160. Path('mem/abcde.zip', 'a.txt')
  161. >>> b
  162. Path('mem/abcde.zip', 'b/')
  163. name property:
  164. >>> b.name
  165. 'b'
  166. join with divide operator:
  167. >>> c = b / 'c.txt'
  168. >>> c
  169. Path('mem/abcde.zip', 'b/c.txt')
  170. >>> c.name
  171. 'c.txt'
  172. Read text:
  173. >>> c.read_text(encoding='utf-8')
  174. 'content of c'
  175. existence:
  176. >>> c.exists()
  177. True
  178. >>> (b / 'missing.txt').exists()
  179. False
  180. Coercion to string:
  181. >>> import os
  182. >>> str(c).replace(os.sep, posixpath.sep)
  183. 'mem/abcde.zip/b/c.txt'
  184. At the root, ``name``, ``filename``, and ``parent``
  185. resolve to the zipfile. Note these attributes are not
  186. valid and will raise a ``ValueError`` if the zipfile
  187. has no filename.
  188. >>> root.name
  189. 'abcde.zip'
  190. >>> str(root.filename).replace(os.sep, posixpath.sep)
  191. 'mem/abcde.zip'
  192. >>> str(root.parent)
  193. 'mem'
  194. """
  195. __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})"
  196. def __init__(self, root, at=""):
  197. """
  198. Construct a Path from a ZipFile or filename.
  199. Note: When the source is an existing ZipFile object,
  200. its type (__class__) will be mutated to a
  201. specialized type. If the caller wishes to retain the
  202. original type, the caller should either create a
  203. separate ZipFile object or pass a filename.
  204. """
  205. self.root = FastLookup.make(root)
  206. self.at = at
  207. def __eq__(self, other):
  208. """
  209. >>> Path(zipfile.ZipFile(io.BytesIO(), 'w')) == 'foo'
  210. False
  211. """
  212. if self.__class__ is not other.__class__:
  213. return NotImplemented
  214. return (self.root, self.at) == (other.root, other.at)
  215. def __hash__(self):
  216. return hash((self.root, self.at))
  217. def open(self, mode='r', *args, pwd=None, **kwargs):
  218. """
  219. Open this entry as text or binary following the semantics
  220. of ``pathlib.Path.open()`` by passing arguments through
  221. to io.TextIOWrapper().
  222. """
  223. if self.is_dir():
  224. raise IsADirectoryError(self)
  225. zip_mode = mode[0]
  226. if not self.exists() and zip_mode == 'r':
  227. raise FileNotFoundError(self)
  228. stream = self.root.open(self.at, zip_mode, pwd=pwd)
  229. if 'b' in mode:
  230. if args or kwargs:
  231. raise ValueError("encoding args invalid for binary operation")
  232. return stream
  233. # Text mode:
  234. encoding, args, kwargs = _extract_text_encoding(*args, **kwargs)
  235. return io.TextIOWrapper(stream, encoding, *args, **kwargs)
  236. @property
  237. def name(self):
  238. return pathlib.Path(self.at).name or self.filename.name
  239. @property
  240. def suffix(self):
  241. return pathlib.Path(self.at).suffix or self.filename.suffix
  242. @property
  243. def suffixes(self):
  244. return pathlib.Path(self.at).suffixes or self.filename.suffixes
  245. @property
  246. def stem(self):
  247. return pathlib.Path(self.at).stem or self.filename.stem
  248. @property
  249. def filename(self):
  250. return pathlib.Path(self.root.filename).joinpath(self.at)
  251. def read_text(self, *args, **kwargs):
  252. encoding, args, kwargs = _extract_text_encoding(*args, **kwargs)
  253. with self.open('r', encoding, *args, **kwargs) as strm:
  254. return strm.read()
  255. def read_bytes(self):
  256. with self.open('rb') as strm:
  257. return strm.read()
  258. def _is_child(self, path):
  259. return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/")
  260. def _next(self, at):
  261. return self.__class__(self.root, at)
  262. def is_dir(self):
  263. return not self.at or self.at.endswith("/")
  264. def is_file(self):
  265. return self.exists() and not self.is_dir()
  266. def exists(self):
  267. return self.at in self.root._name_set()
  268. def iterdir(self):
  269. if not self.is_dir():
  270. raise ValueError("Can't listdir a file")
  271. subs = map(self._next, self.root.namelist())
  272. return filter(self._is_child, subs)
  273. def match(self, path_pattern):
  274. return pathlib.Path(self.at).match(path_pattern)
  275. def is_symlink(self):
  276. """
  277. Return whether this path is a symlink. Always false (python/cpython#82102).
  278. """
  279. return False
  280. def _descendants(self):
  281. for child in self.iterdir():
  282. yield child
  283. if child.is_dir():
  284. yield from child._descendants()
  285. def glob(self, pattern):
  286. if not pattern:
  287. raise ValueError(f"Unacceptable pattern: {pattern!r}")
  288. matches = re.compile(fnmatch.translate(pattern)).fullmatch
  289. return (
  290. child
  291. for child in self._descendants()
  292. if matches(str(child.relative_to(self)))
  293. )
  294. def rglob(self, pattern):
  295. return self.glob(f'**/{pattern}')
  296. def relative_to(self, other, *extra):
  297. return posixpath.relpath(str(self), str(other.joinpath(*extra)))
  298. def __str__(self):
  299. return posixpath.join(self.root.filename, self.at)
  300. def __repr__(self):
  301. return self.__repr.format(self=self)
  302. def joinpath(self, *other):
  303. next = posixpath.join(self.at, *other)
  304. return self._next(self.root.resolve_dir(next))
  305. __truediv__ = joinpath
  306. @property
  307. def parent(self):
  308. if not self.at:
  309. return self.filename.parent
  310. parent_at = posixpath.dirname(self.at.rstrip('/'))
  311. if parent_at:
  312. parent_at += '/'
  313. return self._next(parent_at)