megfile 3.1.6__py3-none-any.whl → 4.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. megfile/cli.py +12 -7
  2. megfile/config.py +34 -44
  3. megfile/fs.py +169 -11
  4. megfile/fs_path.py +183 -259
  5. megfile/hdfs.py +106 -5
  6. megfile/hdfs_path.py +34 -90
  7. megfile/http.py +50 -1
  8. megfile/http_path.py +27 -65
  9. megfile/interfaces.py +1 -8
  10. megfile/lib/base_prefetch_reader.py +62 -78
  11. megfile/lib/combine_reader.py +5 -0
  12. megfile/lib/glob.py +3 -6
  13. megfile/lib/hdfs_prefetch_reader.py +7 -7
  14. megfile/lib/http_prefetch_reader.py +6 -6
  15. megfile/lib/s3_buffered_writer.py +67 -64
  16. megfile/lib/s3_cached_handler.py +1 -2
  17. megfile/lib/s3_limited_seekable_writer.py +3 -7
  18. megfile/lib/s3_memory_handler.py +1 -2
  19. megfile/lib/s3_pipe_handler.py +1 -2
  20. megfile/lib/s3_prefetch_reader.py +15 -20
  21. megfile/lib/s3_share_cache_reader.py +8 -5
  22. megfile/pathlike.py +397 -401
  23. megfile/s3.py +118 -17
  24. megfile/s3_path.py +150 -224
  25. megfile/sftp.py +300 -10
  26. megfile/sftp_path.py +46 -322
  27. megfile/smart.py +33 -27
  28. megfile/smart_path.py +9 -14
  29. megfile/stdio.py +1 -1
  30. megfile/stdio_path.py +2 -2
  31. megfile/utils/__init__.py +11 -4
  32. megfile/version.py +1 -1
  33. {megfile-3.1.6.dist-info → megfile-4.0.0.dist-info}/METADATA +7 -7
  34. megfile-4.0.0.dist-info/RECORD +52 -0
  35. {megfile-3.1.6.dist-info → megfile-4.0.0.dist-info}/WHEEL +1 -1
  36. {megfile-3.1.6.dist-info → megfile-4.0.0.dist-info}/top_level.txt +0 -2
  37. docs/conf.py +0 -65
  38. megfile-3.1.6.dist-info/RECORD +0 -55
  39. scripts/convert_results_to_sarif.py +0 -91
  40. scripts/generate_file.py +0 -344
  41. {megfile-3.1.6.dist-info → megfile-4.0.0.dist-info}/LICENSE +0 -0
  42. {megfile-3.1.6.dist-info → megfile-4.0.0.dist-info}/LICENSE.pyre +0 -0
  43. {megfile-3.1.6.dist-info → megfile-4.0.0.dist-info}/entry_points.txt +0 -0
megfile/pathlike.py CHANGED
@@ -216,6 +216,8 @@ class FileEntry(NamedTuple):
216
216
 
217
217
 
218
218
  class BasePath:
219
+ protocol = ""
220
+
219
221
  def __init__(self, path: "PathLike"):
220
222
  self.path = str(path)
221
223
 
@@ -229,7 +231,7 @@ class BasePath:
229
231
  return str(self).encode()
230
232
 
231
233
  def __fspath__(self) -> str:
232
- return self.path
234
+ return self.as_uri()
233
235
 
234
236
  def __hash__(self) -> int:
235
237
  return hash(fspath(self))
@@ -237,207 +239,9 @@ class BasePath:
237
239
  def __eq__(self, other_path: "BasePath") -> bool:
238
240
  return fspath(self) == fspath(other_path)
239
241
 
240
- def is_dir(self, followlinks: bool = False) -> bool:
241
- """Return True if the path points to a directory."""
242
- raise NotImplementedError('method "is_dir" not implemented: %r' % self)
243
-
244
- def is_file(self, followlinks: bool = False) -> bool:
245
- """Return True if the path points to a regular file."""
246
- raise NotImplementedError('method "is_file" not implemented: %r' % self)
247
-
248
- def is_symlink(self) -> bool:
249
- return False
250
-
251
- def access(self, mode: Access) -> bool:
252
- """Return True if the path has access permission described by mode."""
253
- raise NotImplementedError('method "access" not implemented: %r' % self)
254
-
255
- def exists(self, followlinks: bool = False) -> bool:
256
- """Whether the path points to an existing file or directory."""
257
- raise NotImplementedError('method "exists" not implemented: %r' % self)
258
-
259
- def listdir(self) -> List[str]:
260
- """Return the names of the entries in the directory the path points to."""
261
- raise NotImplementedError('method "listdir" not implemented: %r' % self)
262
-
263
- def scandir(self) -> Iterator[FileEntry]:
264
- """
265
- Return an iterator of FileEntry objects corresponding to the entries
266
- in the directory.
267
- """
268
- raise NotImplementedError('method "scandir" not implemented: %r' % self)
269
-
270
- def getsize(self, follow_symlinks: bool = True) -> int:
271
- """Return the size, in bytes."""
272
- raise NotImplementedError('method "getsize" not implemented: %r' % self)
273
-
274
- def getmtime(self, follow_symlinks: bool = True) -> float:
275
- """Return the time of last modification."""
276
- raise NotImplementedError('method "getmtime" not implemented: %r' % self)
277
-
278
- def stat(self, follow_symlinks=True) -> StatResult:
279
- """Get the status of the path."""
280
- raise NotImplementedError('method "stat" not implemented: %r' % self)
281
-
282
- def remove(self, missing_ok: bool = False) -> None:
283
- """Remove (delete) the file."""
284
- raise NotImplementedError('method "remove" not implemented: %r' % self)
285
-
286
- def unlink(self, missing_ok: bool = False) -> None:
287
- """Remove (delete) the file."""
288
- raise NotImplementedError('method "unlink" not implemented: %r' % self)
289
-
290
- def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None:
291
- """Create a directory."""
292
- raise NotImplementedError('method "mkdir" not implemented: %r' % self)
293
-
294
- def rmdir(self) -> None:
295
- """Remove (delete) the directory."""
296
- raise NotImplementedError('method "rmdir" not implemented: %r' % self)
297
-
298
- def open(self, mode: str = "r", **kwargs) -> IO:
299
- """Open the file with mode."""
300
- raise NotImplementedError('method "open" not implemented: %r' % self)
301
-
302
- def walk(
303
- self, followlinks: bool = False
304
- ) -> Iterator[Tuple[str, List[str], List[str]]]:
305
- """Generate the file names in a directory tree by walking the tree."""
306
- raise NotImplementedError('method "walk" not implemented: %r' % self)
307
-
308
- def scan(self, missing_ok: bool = True, followlinks: bool = False) -> Iterator[str]:
309
- """Iterate through the files in the directory."""
310
- raise NotImplementedError('method "scan" not implemented: %r' % self)
311
-
312
- def scan_stat(
313
- self, missing_ok: bool = True, followlinks: bool = False
314
- ) -> Iterator[FileEntry]:
315
- """Iterate through the files in the directory, with file stat."""
316
- raise NotImplementedError('method "scan_stat" not implemented: %r' % self)
317
-
318
- def glob(
319
- self: Self, pattern: str, recursive: bool = True, missing_ok: bool = True
320
- ) -> List[Self]:
321
- """Return files whose paths match the glob pattern."""
322
- raise NotImplementedError('method "glob" not implemented: %r' % self)
323
-
324
- def iglob(
325
- self: Self, pattern: str, recursive: bool = True, missing_ok: bool = True
326
- ) -> Iterator[Self]:
327
- """Return an iterator of files whose paths match the glob pattern."""
328
- raise NotImplementedError('method "iglob" not implemented: %r' % self)
329
-
330
- def glob_stat(
331
- self, pattern: str, recursive: bool = True, missing_ok: bool = True
332
- ) -> Iterator[FileEntry]:
333
- """Return an iterator of files with stat whose paths match the glob pattern."""
334
- raise NotImplementedError('method "glob_stat" not implemented: %r' % self)
335
-
336
- def load(self) -> BinaryIO:
337
- """Read all content in binary."""
338
- raise NotImplementedError('method "load" not implemented: %r' % self)
339
-
340
- def save(self, file_object: BinaryIO):
341
- """Write the opened binary stream to the path."""
342
- raise NotImplementedError('method "save" not implemented: %r' % self)
343
-
344
- def joinpath(self: Self, *other_paths: "PathLike") -> Self:
345
- """Join or or more path."""
346
- raise NotImplementedError('method "joinpath" not implemented: %r' % self)
347
-
348
- def abspath(self):
349
- """Return a normalized absolute version of the path."""
350
- raise NotImplementedError('method "abspath" not implemented: %r' % self)
351
-
352
- def realpath(self):
353
- """Return the canonical path of the path."""
354
- raise NotImplementedError('method "realpath" not implemented: %r' % self)
355
-
356
- def relpath(self, start=None):
357
- """Return the relative path."""
358
- raise NotImplementedError('method "relpath" not implemented: %r' % self)
359
-
360
- def is_absolute(self) -> bool:
361
- """Return True if the path is an absolute pathname."""
362
- raise NotImplementedError('method "is_absolute" not implemented: %r' % self)
363
-
364
- def is_mount(self) -> bool:
365
- """Return True if the path is a mount point."""
366
- raise NotImplementedError('method "is_mount" not implemented: %r' % self)
367
-
368
- def resolve(self):
369
- """Alias of realpath."""
370
- raise NotImplementedError('method "resolve" not implemented: %r' % self)
371
-
372
- def touch(self):
373
- with self.open("w"):
374
- pass
375
-
376
- # TODO: will be deleted in next version
377
- def is_link(self) -> bool:
378
- return self.is_symlink()
379
-
380
- def makedirs(self, exist_ok: bool = False) -> None:
381
- """
382
- Recursive directory creation function. Like mkdir(), but makes all
383
- intermediate-level directories needed to contain the leaf directory.
384
- """
385
- self.mkdir(parents=True, exist_ok=exist_ok)
386
-
387
-
388
- PathLike = Union[str, BasePath, _PathLike]
389
-
390
-
391
- class BaseURIPath(BasePath):
392
- # #####
393
- # TODO: Backwards compatible API, will be removed in megfile 1.0
394
- @classmethod
395
- def get_protocol(self) -> Optional[str]:
396
- pass # pragma: no cover
397
-
398
- @classproperty
399
- def protocol(cls) -> str:
400
- return cls.get_protocol() or ""
401
-
402
- def make_uri(self) -> str:
403
- return self.path_with_protocol
404
-
405
- def as_uri(self) -> str:
406
- return self.make_uri()
407
-
408
- # #####
409
-
410
- @cached_property
411
- def path_with_protocol(self) -> str:
412
- """Return path with protocol, like file:///root, s3://bucket/key"""
413
- path = self.path
414
- protocol_prefix = self.protocol + "://" # pyre-ignore[58]
415
- if path.startswith(protocol_prefix):
416
- return path
417
- return protocol_prefix + path.lstrip("/")
418
-
419
- @cached_property
420
- def path_without_protocol(self) -> str:
421
- """
422
- Return path without protocol, example: if path is s3://bucket/key,
423
- return bucket/key
424
- """
425
- path = self.path
426
- protocol_prefix = self.protocol + "://" # pyre-ignore[58]
427
- if path.startswith(protocol_prefix):
428
- path = path[len(protocol_prefix) :]
429
- return path
430
-
431
- def as_posix(self) -> str:
432
- """Return a string representation of the path with forward slashes (/)"""
433
- return self.path_with_protocol
434
-
435
- def __fspath__(self) -> str:
436
- return self.as_uri()
437
-
438
- def __lt__(self, other_path: "BaseURIPath") -> bool:
439
- if not isinstance(other_path, BaseURIPath):
440
- raise TypeError("%r is not 'URIPath'" % other_path)
242
+ def __lt__(self, other_path: "BasePath") -> bool:
243
+ if not isinstance(other_path, BasePath):
244
+ raise TypeError("%r is not 'BasePath'" % other_path)
441
245
  if self.protocol != other_path.protocol:
442
246
  raise TypeError(
443
247
  "'<' not supported between instances of %r and %r"
@@ -445,9 +249,9 @@ class BaseURIPath(BasePath):
445
249
  )
446
250
  return fspath(self) < fspath(other_path)
447
251
 
448
- def __le__(self, other_path: "BaseURIPath") -> bool:
449
- if not isinstance(other_path, BaseURIPath):
450
- raise TypeError("%r is not 'URIPath'" % other_path)
252
+ def __le__(self, other_path: "BasePath") -> bool:
253
+ if not isinstance(other_path, BasePath):
254
+ raise TypeError("%r is not 'BasePath'" % other_path)
451
255
  if self.protocol != other_path.protocol:
452
256
  raise TypeError(
453
257
  "'<=' not supported between instances of %r and %r"
@@ -455,9 +259,9 @@ class BaseURIPath(BasePath):
455
259
  )
456
260
  return str(self) <= str(other_path)
457
261
 
458
- def __gt__(self, other_path: "BaseURIPath") -> bool:
459
- if not isinstance(other_path, BaseURIPath):
460
- raise TypeError("%r is not 'URIPath'" % other_path)
262
+ def __gt__(self, other_path: "BasePath") -> bool:
263
+ if not isinstance(other_path, BasePath):
264
+ raise TypeError("%r is not 'BasePath'" % other_path)
461
265
  if self.protocol != other_path.protocol:
462
266
  raise TypeError(
463
267
  "'>' not supported between instances of %r and %r"
@@ -465,9 +269,9 @@ class BaseURIPath(BasePath):
465
269
  )
466
270
  return str(self) > str(other_path)
467
271
 
468
- def __ge__(self, other_path: "BaseURIPath") -> bool:
469
- if not isinstance(other_path, BaseURIPath):
470
- raise TypeError("%r is not 'URIPath'" % other_path)
272
+ def __ge__(self, other_path: "BasePath") -> bool:
273
+ if not isinstance(other_path, BasePath):
274
+ raise TypeError("%r is not 'BasePath'" % other_path)
471
275
  if self.protocol != other_path.protocol:
472
276
  raise TypeError(
473
277
  "'>=' not supported between instances of %r and %r"
@@ -475,27 +279,39 @@ class BaseURIPath(BasePath):
475
279
  )
476
280
  return str(self) >= str(other_path)
477
281
 
478
- @classproperty
479
- def drive(self) -> str:
480
- return ""
282
+ def __truediv__(self: Self, other_path: "PathLike") -> Self:
283
+ raise NotImplementedError('method "__truediv__" not implemented: %r' % self)
481
284
 
482
- @classproperty
483
- def root(self) -> str:
484
- return self.protocol + "://" # pyre-ignore[58]
285
+ @cached_property
286
+ def path_with_protocol(self) -> str:
287
+ """Return path with protocol, like file:///root, s3://bucket/key"""
288
+ path = self.path
289
+ protocol_prefix = self.protocol + "://"
290
+ if path.startswith(protocol_prefix):
291
+ return path
292
+ return protocol_prefix + path.lstrip("/")
485
293
 
486
- @classproperty
487
- def anchor(self) -> str:
488
- return self.root # pyre-ignore[7]
294
+ @cached_property
295
+ def path_without_protocol(self) -> str:
296
+ """
297
+ Return path without protocol, example: if path is s3://bucket/key,
298
+ return bucket/key
299
+ """
300
+ path = self.path
301
+ protocol_prefix = self.protocol + "://"
302
+ if path.startswith(protocol_prefix):
303
+ path = path[len(protocol_prefix) :]
304
+ return path
489
305
 
306
+ def as_uri(self) -> str:
307
+ return self.path_with_protocol
490
308
 
491
- class URIPath(BaseURIPath):
492
- def __init__(self, path: "PathLike", *other_paths: "PathLike"):
493
- if len(other_paths) > 0:
494
- path = self.from_path(path).joinpath(*other_paths)
495
- self.path = str(path)
309
+ def as_posix(self) -> str:
310
+ """Return a string representation of the path with forward slashes (/)"""
311
+ return self.path_with_protocol
496
312
 
497
313
  @classmethod
498
- def from_path(cls: Type[Self], path: PathLike) -> Self:
314
+ def from_path(cls: Type[Self], path: "PathLike") -> Self:
499
315
  """Return new instance of this class
500
316
 
501
317
  :param path: new path
@@ -506,7 +322,7 @@ class URIPath(BaseURIPath):
506
322
  return cls(path) # pyre-ignore[19]
507
323
 
508
324
  @classmethod
509
- def from_uri(cls: Type[Self], path: PathLike) -> Self:
325
+ def from_uri(cls: Type[Self], path: "PathLike") -> Self:
510
326
  path = fspath(path)
511
327
  protocol_prefix = cls.protocol + "://"
512
328
  if path[: len(protocol_prefix)] != protocol_prefix:
@@ -515,59 +331,12 @@ class URIPath(BaseURIPath):
515
331
  )
516
332
  return cls.from_path(path[len(protocol_prefix) :])
517
333
 
518
- def __truediv__(self: Self, other_path: PathLike) -> Self:
519
- if isinstance(other_path, BaseURIPath):
520
- if self.protocol != other_path.protocol:
521
- raise TypeError(
522
- "'/' not supported between instances of %r and %r"
523
- % (type(self), type(other_path))
524
- )
525
- elif not isinstance(other_path, str):
526
- raise TypeError("%r is not 'str' nor 'URIPath'" % other_path)
527
- return self.joinpath(other_path)
528
-
529
- def joinpath(self: Self, *other_paths: PathLike) -> Self:
530
- """
531
- Calling this method is equivalent to combining the path
532
- with each of the other arguments in turn
533
- """
534
- return self.from_path(uri_join(str(self), *map(str, other_paths)))
535
-
536
- @cached_property
537
- def parts(self) -> Tuple[str, ...]:
538
- """A tuple giving access to the path’s various components"""
539
- parts = [self.root]
540
- path = self.path_without_protocol
541
- path = path.lstrip("/")
542
- if path != "":
543
- parts.extend(path.split("/"))
544
- return tuple(parts) # pyre-ignore[7]
545
-
546
- @cached_property
547
- def parents(self) -> "URIPathParents":
548
- """
549
- An immutable sequence providing access to the logical ancestors of the path
550
- """
551
- return URIPathParents(self)
552
-
553
- @cached_property
554
- def parent(self: Self) -> Self:
555
- """The logical parent of the path"""
556
- if self.path_without_protocol == "/":
557
- return self
558
- elif len(self.parents) > 0:
559
- return self.parents[0]
560
- return self.from_path("")
561
-
562
334
  @cached_property
563
335
  def name(self) -> str:
564
336
  """
565
337
  A string representing the final path component, excluding the drive and root
566
338
  """
567
- parts = self.parts
568
- if len(parts) == 1 and parts[0] == self.protocol + "://": # pyre-ignore[58]
569
- return ""
570
- return parts[-1]
339
+ return self.path_without_protocol
571
340
 
572
341
  @cached_property
573
342
  def suffix(self) -> str:
@@ -599,18 +368,6 @@ class URIPath(BaseURIPath):
599
368
  def is_reserved(self) -> bool:
600
369
  return False
601
370
 
602
- def match(self, pattern) -> bool:
603
- """
604
- Match this path against the provided glob-style pattern.
605
- Return True if matching is successful, False otherwise
606
- """
607
- match = _compile_pattern(pattern)
608
- for index in range(len(self.parts), 0, -1):
609
- path = "/".join(self.parts[index:])
610
- if match(path) is not None:
611
- return True
612
- return match(self.path_with_protocol) is not None
613
-
614
371
  def is_relative_to(self, *other) -> bool:
615
372
  try:
616
373
  self.relative_to(*other)
@@ -663,74 +420,295 @@ class URIPath(BaseURIPath):
663
420
  other_path = self.from_path(start).path_with_protocol
664
421
  path = self.path_with_protocol
665
422
 
666
- if path.startswith(other_path):
667
- relative = path[len(other_path) :]
668
- relative = relative.lstrip("/")
669
- return relative
423
+ if path.startswith(other_path):
424
+ relative = path[len(other_path) :]
425
+ relative = relative.lstrip("/")
426
+ return relative
427
+
428
+ raise ValueError("%r does not start with %r" % (path, other_path))
429
+
430
+ def is_absolute(self) -> bool:
431
+ return True
432
+
433
+ def is_mount(self) -> bool:
434
+ """Test whether a path is a mount point
435
+
436
+ :returns: True if a path is a mount point, else False
437
+ """
438
+ return False
439
+
440
+ def is_socket(self) -> bool:
441
+ """
442
+ Return True if the path points to a Unix socket (or a symbolic link pointing
443
+ to a Unix socket), False if it points to another kind of file.
444
+
445
+ False is also returned if the path doesn’t exist or is a broken symlink;
446
+ other errors (such as permission errors) are propagated.
447
+ """
448
+ return False
449
+
450
+ def is_fifo(self) -> bool:
451
+ """
452
+ Return True if the path points to a FIFO (or a symbolic link pointing to a
453
+ FIFO), False if it points to another kind of file.
454
+
455
+ False is also returned if the path doesn’t exist or is a broken symlink;
456
+ other errors (such as permission errors) are propagated.
457
+ """
458
+ return False
459
+
460
+ def is_block_device(self) -> bool:
461
+ """
462
+ Return True if the path points to a block device (or a symbolic link pointing
463
+ to a block device), False if it points to another kind of file.
464
+
465
+ False is also returned if the path doesn’t exist or is a broken symlink;
466
+ other errors (such as permission errors) are propagated.
467
+ """
468
+ return False
469
+
470
+ def is_char_device(self) -> bool:
471
+ """
472
+ Return True if the path points to a character device (or a symbolic link
473
+ pointing to a character device), False if it points to another kind of file.
474
+
475
+ False is also returned if the path doesn’t exist or is a broken symlink;
476
+ other errors (such as permission errors) are propagated.
477
+ """
478
+ return False
479
+
480
+ def abspath(self) -> str:
481
+ """Return a normalized absolute version of the path."""
482
+ return self.path_with_protocol
483
+
484
+ def realpath(self) -> str:
485
+ """Return the canonical path of the path."""
486
+ return self.path_with_protocol
487
+
488
+ def resolve(self):
489
+ """Alias of realpath."""
490
+ return self.path_with_protocol
491
+
492
+ def read_bytes(self) -> bytes:
493
+ """Return the binary contents of the pointed-to file as a bytes object"""
494
+ with self.open(mode="rb") as f:
495
+ return f.read() # pytype: disable=bad-return-type
496
+
497
+ def read_text(self) -> str:
498
+ """Return the decoded contents of the pointed-to file as a string"""
499
+ with self.open(mode="r") as f:
500
+ return f.read() # pytype: disable=bad-return-type
501
+
502
+ def rglob(self: Self, pattern) -> List[Self]:
503
+ """
504
+ This is like calling Path.glob() with “**/” added in front of
505
+ the given relative pattern
506
+ """
507
+ if not pattern:
508
+ pattern = ""
509
+ pattern = "**/" + pattern.lstrip("/")
510
+ return self.glob(pattern=pattern)
511
+
512
+ def samefile(self, other_path) -> bool:
513
+ """
514
+ Return whether this path points to the same file
515
+ """
516
+ if hasattr(other_path, "protocol"):
517
+ if other_path.protocol != self.protocol:
518
+ return False
519
+
520
+ stat = self.stat()
521
+ if hasattr(other_path, "stat"):
522
+ other_path_stat = other_path.stat()
523
+ else:
524
+ other_path_stat = self.from_path(other_path).stat()
525
+
526
+ return (
527
+ stat.st_ino == other_path_stat.st_ino
528
+ and stat.st_dev == other_path_stat.st_dev
529
+ )
530
+
531
+ def touch(self):
532
+ with self.open("w"):
533
+ pass
534
+
535
+ def makedirs(self, exist_ok: bool = False) -> None:
536
+ """
537
+ Recursive directory creation function. Like mkdir(), but makes all
538
+ intermediate-level directories needed to contain the leaf directory.
539
+ """
540
+ self.mkdir(parents=True, exist_ok=exist_ok)
541
+
542
+ def write_bytes(self, data: bytes):
543
+ """
544
+ Open the file pointed to in bytes mode, write data to it, and close the file
545
+ """
546
+ with self.open(mode="wb") as f:
547
+ return f.write(data)
548
+
549
+ def write_text(self, data: str, encoding=None, errors=None, newline=None):
550
+ """
551
+ Open the file pointed to in text mode, write data to it, and close the file.
552
+ The optional parameters have the same meaning as in open().
553
+ """
554
+ with self.open(
555
+ mode="w", encoding=encoding, errors=errors, newline=newline
556
+ ) as f:
557
+ return f.write(data)
558
+
559
+ @classproperty
560
+ def drive(self) -> str:
561
+ return ""
562
+
563
+ @classproperty
564
+ def root(self) -> str:
565
+ return self.protocol + "://"
566
+
567
+ @classproperty
568
+ def anchor(self) -> str:
569
+ return self.root # pyre-ignore[7]
570
+
571
+ def joinpath(self: Self, *other_paths: "PathLike") -> Self:
572
+ """
573
+ Calling this method is equivalent to combining the path
574
+ with each of the other arguments in turn
575
+ """
576
+ raise NotImplementedError('method "joinpath" not implemented: %r' % self)
577
+
578
+ @cached_property
579
+ def parts(self) -> Tuple[str, ...]:
580
+ """A tuple giving access to the path’s various components"""
581
+ raise NotImplementedError('property "parts" not implemented: %r' % self)
582
+
583
+ @cached_property
584
+ def parents(self) -> "URIPathParents":
585
+ """
586
+ An immutable sequence providing access to the logical ancestors of the path
587
+ """
588
+ raise NotImplementedError('property "parents" not implemented: %r' % self)
589
+
590
+ @cached_property
591
+ def parent(self: Self) -> Self:
592
+ """The logical parent of the path"""
593
+ raise NotImplementedError('property "parent" not implemented: %r' % self)
594
+
595
+ def is_dir(self, followlinks: bool = False) -> bool:
596
+ """Return True if the path points to a directory."""
597
+ raise NotImplementedError('method "is_dir" not implemented: %r' % self)
598
+
599
+ def is_file(self, followlinks: bool = False) -> bool:
600
+ """Return True if the path points to a regular file."""
601
+ raise NotImplementedError('method "is_file" not implemented: %r' % self)
602
+
603
+ def is_symlink(self) -> bool:
604
+ return False
605
+
606
+ def access(self, mode: Access) -> bool:
607
+ """Return True if the path has access permission described by mode."""
608
+ raise NotImplementedError('method "access" not implemented: %r' % self)
609
+
610
+ def exists(self, followlinks: bool = False) -> bool:
611
+ """Whether the path points to an existing file or directory."""
612
+ raise NotImplementedError('method "exists" not implemented: %r' % self)
613
+
614
+ def listdir(self) -> List[str]:
615
+ """Return the names of the entries in the directory the path points to."""
616
+ raise NotImplementedError('method "listdir" not implemented: %r' % self)
617
+
618
+ def scandir(self) -> Iterator[FileEntry]:
619
+ """
620
+ Return an iterator of FileEntry objects corresponding to the entries
621
+ in the directory.
622
+ """
623
+ raise NotImplementedError('method "scandir" not implemented: %r' % self)
670
624
 
671
- raise ValueError("%r does not start with %r" % (path, other_path))
625
+ def getsize(self, follow_symlinks: bool = True) -> int:
626
+ """Return the size, in bytes."""
627
+ raise NotImplementedError('method "getsize" not implemented: %r' % self)
672
628
 
673
- def is_absolute(self) -> bool:
674
- return True
629
+ def getmtime(self, follow_symlinks: bool = True) -> float:
630
+ """Return the time of last modification."""
631
+ raise NotImplementedError('method "getmtime" not implemented: %r' % self)
675
632
 
676
- def is_mount(self) -> bool:
677
- """Test whether a path is a mount point
633
+ def stat(self, follow_symlinks=True) -> StatResult:
634
+ """Get the status of the path."""
635
+ raise NotImplementedError('method "stat" not implemented: %r' % self)
678
636
 
679
- :returns: True if a path is a mount point, else False
637
+ def lstat(self) -> StatResult:
680
638
  """
681
- return False
682
-
683
- def is_socket(self) -> bool:
639
+ Like stat() but, if the path points to a symbolic link,
640
+ return the symbolic link’s information rather than its target’s.
684
641
  """
685
- Return True if the path points to a Unix socket (or a symbolic link pointing
686
- to a Unix socket), False if it points to another kind of file.
642
+ return self.stat(follow_symlinks=False)
687
643
 
688
- False is also returned if the path doesn’t exist or is a broken symlink;
689
- other errors (such as permission errors) are propagated.
644
+ def match(self, pattern) -> bool:
690
645
  """
691
- return False
692
-
693
- def is_fifo(self) -> bool:
646
+ Match this path against the provided glob-style pattern.
647
+ Return True if matching is successful, False otherwise
694
648
  """
695
- Return True if the path points to a FIFO (or a symbolic link pointing to a
696
- FIFO), False if it points to another kind of file.
649
+ raise NotImplementedError('method "match" not implemented: %r' % self)
697
650
 
698
- False is also returned if the path doesn’t exist or is a broken symlink;
699
- other errors (such as permission errors) are propagated.
700
- """
701
- return False
651
+ def remove(self, missing_ok: bool = False) -> None:
652
+ """Remove (delete) the file."""
653
+ raise NotImplementedError('method "remove" not implemented: %r' % self)
702
654
 
703
- def is_block_device(self) -> bool:
704
- """
705
- Return True if the path points to a block device (or a symbolic link pointing
706
- to a block device), False if it points to another kind of file.
655
+ def unlink(self, missing_ok: bool = False) -> None:
656
+ """Remove (delete) the file."""
657
+ raise NotImplementedError('method "unlink" not implemented: %r' % self)
707
658
 
708
- False is also returned if the path doesn’t exist or is a broken symlink;
709
- other errors (such as permission errors) are propagated.
710
- """
711
- return False
659
+ def mkdir(self, mode=0o777, parents: bool = False, exist_ok: bool = False) -> None:
660
+ """Create a directory."""
661
+ raise NotImplementedError('method "mkdir" not implemented: %r' % self)
712
662
 
713
- def is_char_device(self) -> bool:
714
- """
715
- Return True if the path points to a character device (or a symbolic link
716
- pointing to a character device), False if it points to another kind of file.
663
+ def rmdir(self) -> None:
664
+ """Remove (delete) the directory."""
665
+ raise NotImplementedError('method "rmdir" not implemented: %r' % self)
717
666
 
718
- False is also returned if the path doesn’t exist or is a broken symlink;
719
- other errors (such as permission errors) are propagated.
720
- """
721
- return False
667
+ def open(self, mode: str = "r", **kwargs) -> IO:
668
+ """Open the file with mode."""
669
+ raise NotImplementedError('method "open" not implemented: %r' % self)
722
670
 
723
- def abspath(self) -> str:
724
- """Return a normalized absolute version of the path."""
725
- return self.path_with_protocol
671
+ def walk(
672
+ self, followlinks: bool = False
673
+ ) -> Iterator[Tuple[str, List[str], List[str]]]:
674
+ """Generate the file names in a directory tree by walking the tree."""
675
+ raise NotImplementedError('method "walk" not implemented: %r' % self)
726
676
 
727
- def realpath(self) -> str:
728
- """Return the canonical path of the path."""
729
- return self.path_with_protocol
677
+ def scan(self, missing_ok: bool = True, followlinks: bool = False) -> Iterator[str]:
678
+ """Iterate through the files in the directory."""
679
+ raise NotImplementedError('method "scan" not implemented: %r' % self)
730
680
 
731
- def resolve(self):
732
- """Alias of realpath."""
733
- return self.path_with_protocol
681
+ def scan_stat(
682
+ self, missing_ok: bool = True, followlinks: bool = False
683
+ ) -> Iterator[FileEntry]:
684
+ """Iterate through the files in the directory, with file stat."""
685
+ raise NotImplementedError('method "scan_stat" not implemented: %r' % self)
686
+
687
+ def glob(
688
+ self: Self, pattern: str, recursive: bool = True, missing_ok: bool = True
689
+ ) -> List[Self]:
690
+ """Return files whose paths match the glob pattern."""
691
+ raise NotImplementedError('method "glob" not implemented: %r' % self)
692
+
693
+ def iglob(
694
+ self: Self, pattern: str, recursive: bool = True, missing_ok: bool = True
695
+ ) -> Iterator[Self]:
696
+ """Return an iterator of files whose paths match the glob pattern."""
697
+ raise NotImplementedError('method "iglob" not implemented: %r' % self)
698
+
699
+ def glob_stat(
700
+ self, pattern: str, recursive: bool = True, missing_ok: bool = True
701
+ ) -> Iterator[FileEntry]:
702
+ """Return an iterator of files with stat whose paths match the glob pattern."""
703
+ raise NotImplementedError('method "glob_stat" not implemented: %r' % self)
704
+
705
+ def load(self) -> BinaryIO:
706
+ """Read all content in binary."""
707
+ raise NotImplementedError('method "load" not implemented: %r' % self)
708
+
709
+ def save(self, file_object: BinaryIO):
710
+ """Write the opened binary stream to the path."""
711
+ raise NotImplementedError('method "save" not implemented: %r' % self)
734
712
 
735
713
  def chmod(self, mode: int, *, follow_symlinks: bool = True):
736
714
  raise NotImplementedError(f"'chmod' is unsupported on '{type(self)}'")
@@ -742,17 +720,7 @@ class URIPath(BaseURIPath):
742
720
  """
743
721
  return self.chmod(mode=mode, follow_symlinks=False)
744
722
 
745
- def read_bytes(self) -> bytes:
746
- """Return the binary contents of the pointed-to file as a bytes object"""
747
- with self.open(mode="rb") as f:
748
- return f.read() # pytype: disable=bad-return-type
749
-
750
- def read_text(self) -> str:
751
- """Return the decoded contents of the pointed-to file as a string"""
752
- with self.open(mode="r") as f:
753
- return f.read() # pytype: disable=bad-return-type
754
-
755
- def rename(self: Self, dst_path: PathLike, overwrite: bool = True) -> Self:
723
+ def rename(self: Self, dst_path: "PathLike", overwrite: bool = True) -> Self:
756
724
  """
757
725
  rename file
758
726
 
@@ -761,7 +729,7 @@ class URIPath(BaseURIPath):
761
729
  """
762
730
  raise NotImplementedError(f"'rename' is unsupported on '{type(self)}'")
763
731
 
764
- def replace(self: Self, dst_path: PathLike, overwrite: bool = True) -> Self:
732
+ def replace(self: Self, dst_path: "PathLike", overwrite: bool = True) -> Self:
765
733
  """
766
734
  move file
767
735
 
@@ -770,39 +738,10 @@ class URIPath(BaseURIPath):
770
738
  """
771
739
  return self.rename(dst_path=dst_path, overwrite=overwrite)
772
740
 
773
- def rglob(self: Self, pattern) -> List[Self]:
774
- """
775
- This is like calling Path.glob() with “**/” added in front of
776
- the given relative pattern
777
- """
778
- if not pattern:
779
- pattern = ""
780
- pattern = "**/" + pattern.lstrip("/")
781
- return self.glob(pattern=pattern)
782
-
783
741
  def md5(self, recalculate: bool = False, followlinks: bool = False) -> str:
784
742
  raise NotImplementedError(f"'md5' is unsupported on '{type(self)}'")
785
743
 
786
- def samefile(self, other_path) -> bool:
787
- """
788
- Return whether this path points to the same file
789
- """
790
- if hasattr(other_path, "protocol"):
791
- if other_path.protocol != self.protocol:
792
- return False
793
-
794
- stat = self.stat()
795
- if hasattr(other_path, "stat"):
796
- other_path_stat = other_path.stat()
797
- else:
798
- other_path_stat = self.from_path(other_path).stat()
799
-
800
- return (
801
- stat.st_ino == other_path_stat.st_ino
802
- and stat.st_dev == other_path_stat.st_dev
803
- )
804
-
805
- def symlink(self, dst_path: PathLike) -> None:
744
+ def symlink(self, dst_path: "PathLike") -> None:
806
745
  raise NotImplementedError(f"'symlink' is unsupported on '{type(self)}'")
807
746
 
808
747
  def symlink_to(self, target, target_is_directory=False):
@@ -819,23 +758,6 @@ class URIPath(BaseURIPath):
819
758
  """
820
759
  raise NotImplementedError(f"'hardlink_to' is unsupported on '{type(self)}'")
821
760
 
822
- def write_bytes(self, data: bytes):
823
- """
824
- Open the file pointed to in bytes mode, write data to it, and close the file
825
- """
826
- with self.open(mode="wb") as f:
827
- return f.write(data)
828
-
829
- def write_text(self, data: str, encoding=None, errors=None, newline=None):
830
- """
831
- Open the file pointed to in text mode, write data to it, and close the file.
832
- The optional parameters have the same meaning as in open().
833
- """
834
- with self.open(
835
- mode="w", encoding=encoding, errors=errors, newline=newline
836
- ) as f:
837
- return f.write(data)
838
-
839
761
  def home(self):
840
762
  """Return the home directory
841
763
 
@@ -901,12 +823,86 @@ class URIPath(BaseURIPath):
901
823
  """
902
824
  raise NotImplementedError(f"'utime' is unsupported on '{type(self)}'")
903
825
 
904
- def lstat(self) -> StatResult:
826
+
827
+ PathLike = Union[str, BasePath, _PathLike]
828
+
829
+
830
+ class URIPath(BasePath):
831
+ def __init__(self, path: PathLike, *other_paths: PathLike):
832
+ # if not isinstance(path, PathLike):
833
+ # raise TypeError(f"expected PathLike object, not {type(path)}")
834
+
835
+ if len(other_paths) > 0:
836
+ path = self.from_path(path).joinpath(*other_paths)
837
+ super().__init__(path)
838
+
839
+ def __truediv__(self: Self, other_path: PathLike) -> Self:
840
+ if isinstance(other_path, BasePath):
841
+ if self.protocol != other_path.protocol:
842
+ raise TypeError(
843
+ "'/' not supported between instances of %r and %r"
844
+ % (type(self), type(other_path))
845
+ )
846
+ elif isinstance(other_path, _PathLike):
847
+ other_path = fspath(other_path)
848
+ elif not isinstance(other_path, str):
849
+ raise TypeError("%r is not 'PathLike' object" % other_path)
850
+ return self.joinpath(other_path)
851
+
852
+ def joinpath(self: Self, *other_paths: PathLike) -> Self:
905
853
  """
906
- Like stat() but, if the path points to a symbolic link,
907
- return the symbolic link’s information rather than its target’s.
854
+ Calling this method is equivalent to combining the path
855
+ with each of the other arguments in turn
908
856
  """
909
- return self.stat(follow_symlinks=False)
857
+ return self.from_path(uri_join(str(self), *map(str, other_paths)))
858
+
859
+ @cached_property
860
+ def parts(self) -> Tuple[str, ...]:
861
+ """A tuple giving access to the path’s various components"""
862
+ parts = [self.root]
863
+ path = self.path_without_protocol
864
+ path = path.lstrip("/")
865
+ if path != "":
866
+ parts.extend(path.split("/"))
867
+ return tuple(parts) # pyre-ignore[7]
868
+
869
+ @cached_property
870
+ def parents(self) -> "URIPathParents":
871
+ """
872
+ An immutable sequence providing access to the logical ancestors of the path
873
+ """
874
+ return URIPathParents(self)
875
+
876
+ @cached_property
877
+ def parent(self: Self) -> Self:
878
+ """The logical parent of the path"""
879
+ if self.path_without_protocol == "/":
880
+ return self
881
+ elif len(self.parents) > 0:
882
+ return self.parents[0]
883
+ return self.from_path("")
884
+
885
+ @cached_property
886
+ def name(self) -> str:
887
+ """
888
+ A string representing the final path component, excluding the drive and root
889
+ """
890
+ parts = self.parts
891
+ if len(parts) == 1 and parts[0] == self.protocol + "://":
892
+ return ""
893
+ return parts[-1]
894
+
895
+ def match(self, pattern) -> bool:
896
+ """
897
+ Match this path against the provided glob-style pattern.
898
+ Return True if matching is successful, False otherwise
899
+ """
900
+ match = _compile_pattern(pattern)
901
+ for index in range(len(self.parts), 0, -1):
902
+ path = "/".join(self.parts[index:])
903
+ if match(path) is not None:
904
+ return True
905
+ return match(self.path_with_protocol) is not None
910
906
 
911
907
 
912
908
  class URIPathParents(Sequence):